API Routeを使ってフロントからGraphQLサーバーにアクセスする【Next.js】
はじめに
以下のような方を対象としています.
- GraphQL聞いたことある
- Next.jsでGraphQL使ってみたい
- Apolloサーバーを立てたい
今回作成したリポジトリです.
GraphQLとは何か
GraphQLについては既に分かりやすい記事がたくさん出ているのでリンクを共有することで説明を省略させていただきます.
準備
今回はNext.jsでGraphQLを学んでいくのでNext.jsでプロジェクトを立ち上げます.
以下を実行して,プロジェクト名などを決めます.
yarn create next-app --typescript
-
src
ディレクトリをプロジェクトルートに作成し,pages
ディレクトリとstyles
ディレクトリをsrc/
へ移動させます. - テンプレートコードを削除します.(/src/pages/index.tsxをきれいにする)
最終的には以下のようなコードにします.
import type { NextPage } from 'next'
import Head from 'next/head'
import Link from 'next/link'
const Home: NextPage = () => {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<Link href={`/jobs`}>
<a>jobs</a>
</Link>
</main>
</>
)
}
export default Home
GraphQL APIからデータを取得する
まずは一般に公開されているエンドポイントにアクセスしてデータを取得してきます.
使用したのはPublic Graph APIsで公開されているGraphQL Jobsです.
ここで必要なパッケージをインストールします.graphql-request
とswr
をインストールしてください.
yarn add graphql-request swr
続いてsrc/
ディレクトリにjobs
ディレクトリを用意します.その中にindex.tsx
を用意し,以下のようにします.
import { request, gql } from 'graphql-request'
import useSWR from 'swr'
const API = 'https://api.graphql.jobs/'
const query = gql`
query {
jobs {
id
title
applyUrl
}
}
`
type FetchData = {
jobs: {
id: string
title: string
applyUrl: string
}[]
}
const getJobs = () => {
const { data, error } = useSWR<FetchData>(
query,
// fecther
(query) => request(API, query)
)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return data.jobs.map((job) => (
<div key={job.id}>
<li>{job.title}</li>
<li>{job.applyUrl}</li>
<br />
</div>
))
}
const JobPage = () => (
<>
<h1>Job List</h1>
{getJobs()}
</>
)
export default JobPage
解説
今回取得するデータ型はここで確認できます.この画面でSCHEMAタブをクリックすると様々なデータのデータ型を知ることができます.改良するときはここを見ると良いと思います.
useSWRについては他の記事で分かりやすいものが多いので共有することで省略させていただきます.
この画面が作成できたら早速yarn dev
で立ち上げて確認してみましょう!
localhost:3000
にアクセスし,リンクを踏むとデータをフェッチしていることが確認できます.
またトップページに戻りもう一度アクセスすることでデータがキャッシュされていることも確認できます.
GithubのGraphQL APIからデータを取得する
まずsrc/pages/index.tsx
を以下のように変更します.
import type { NextPage } from 'next'
import Head from 'next/head'
import Link from 'next/link'
const Home: NextPage = () => {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<Link href={`/jobs`}>
<a>jobs</a>
</Link>
+ <br />
+ <Link href={`/issues`}>
+ <a>issues</a>
+ </Link>
</main>
</>
)
}
export default Home
次にGithubのアクセストークンを取得します.
以下の記事を参考にしてください.
トークンのスコープは全て選択して問題ないと思います.
取得したアクセストークンはコピーしてプロジェクトルートの.env.local
に追加します.
NEXT_PUBLIC_GITHUB_PERSONAL_ACCESSTOKEN=XXXXXXXX
そして/pages
ディレクトリに新たにpage/issues
ディレクトリを作成しその中にindex.tsx
を作成します.
import { GraphQLClient, gql } from 'graphql-request'
import useSWR from 'swr'
const API = 'https://api.github.com/graphql' // endpoint
const repositoryOwner = 'vercel' // the repository owner
const repositoryName = 'next.js' // the repository name
const issuesFirst = 100 // the number of issues
const getRepositoryQuery = gql`
query GetRepository(
$repositoryOwner: String!
$repositoryName: String!
$issuesFirst: Int
) {
repository(owner: $repositoryOwner, name: $repositoryName) {
name
issues(first: $issuesFirst) {
edges {
node {
id
title
}
}
}
}
}
`
type FetchData = {
repository: {
name: string
issues: {
edges: {
node: {
id: string
title: string
}
}[]
}
}
}
const getIssues = () => {
// use GraphQLClient to set Header
const client = new GraphQLClient(API, {
headers: {
Authorization:
'bearer ' + process.env.NEXT_PUBLIC_GITHUB_PERSONAL_ACCESSTOKEN,
},
})
const { data, error } = useSWR<FetchData>(
[getRepositoryQuery, repositoryOwner, repositoryName, issuesFirst],
// fetcher
(query, owner, name, first) =>
client.request(query, {
// variables
repositoryOwner: owner,
repositoryName: name,
issuesFirst: first,
})
)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return data.repository.issues.edges.map((issue) => (
<li key={issue.node.id}>{issue.node.title}</li>
))
}
const IssuesPage = () => (
<>
<h1>
{repositoryOwner}/{repositoryName} Issue List
</h1>
{getIssues()}
</>
)
export default IssuesPage
解説
今回はvercelのnext.jsリポジトリのissueを取得してきました.
まず前回と違う点はHeaderが付け加えられていることです.HeaderはGraphQLClient
の第2引数で指定できます.
そしてuseSWRの第2引数で以下のようにしています.
(query, owner, name, first) =>
client.request(query, {
// variables
repositoryOwner: owner,
repositoryName: name,
issuesFirst: first,
})
このvariablesの部分でQueryで指定した変数に値を渡すことができます.
const getRepositoryQuery = gql`
query GetRepository(
$repositoryOwner: String!
$repositoryName: String!
$issuesFirst: Int
) {
repository(owner: $repositoryOwner, name: $repositoryName) {
name
issues(first: $issuesFirst) {
edges {
node {
id
title
}
}
}
}
}
`
前章と同じようにyarn dev
でちゃんと動いているかどうかを確認しましょう!
ページにアクセスするとissueが表示されているはずです.
リポジトリ名を変えれば他のリポジトリについても同様に行えます.
API RouteにApollo Serverをたて,データを取得する
次にNext.jsが提供するAPI RouteにApollo Serverを立て,フロント側からアクセスし,データを取得してみましょう!
今回はapollo-server-microを使用するのでパッケージをインストールします.
yarn add apollo-server-micro
まずスキーマを定義します.
src/apollo
ディレクトリを作成してください.そしてその中にtype-defs.ts
を作成します.
import { gql, Config } from 'apollo-server-micro'
export const typeDefs: Config['typeDefs'] = gql`
type Article {
id: Int
title: String
content: String
}
type Query {
getArticle(id: Int): Article
getArticles: [Article]
}
`
今回はryo_kawamataさんのコードをほぼそのまま利用させていただいています.
続いてリゾルバを作成します.
src/apollo
ディレクトリにresolver.ts
を定義します.
import { Config } from 'apollo-server-micro'
const SAMPLE_DB = {
articles: [
{ id: 1, title: 'foo', content: 'fooooo' },
{ id: 2, title: 'bar', content: 'baaaar' },
{ id: 3, title: 'baz', content: 'baaaaz' },
],
}
const getArticleResolver = (_: any, { id }: { id: number }) =>
SAMPLE_DB.articles?.filter((article) => article.id === id)[0] ?? []
const getArticles = () => SAMPLE_DB.articles
export const resolvers: Config['resolvers'] = {
Query: {
getArticle: getArticleResolver,
getArticles: getArticles,
},
}
ここで実際にDBから取得する処理を書きます.
次にAPI Routeでサーバーを用意します.
src/pages/api
ディレクトリにgraphql.ts
を作成します.
import { NextApiRequest, NextApiResponse } from 'next'
import { ApolloServer } from 'apollo-server-micro'
import Cors from 'micro-cors'
import { typeDefs } from '../../apollo/type-defs'
import { resolvers } from '../../apollo/resolver'
export const config = {
api: {
bodyParser: false,
},
}
const apolloServer = new ApolloServer({ typeDefs, resolvers })
const startServer = apolloServer.start()
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader('Access-Control-Allow-Credentials', 'true')
res.setHeader('Access-Control-Allow-Origin', 'https://example.com')
res.setHeader(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
)
if (req.method === 'OPTIONS') {
res.end()
return false
}
await startServer
await apolloServer.createHandler({
path: '/api/graphql',
})(req, res)
}
export default handler
解説
next.jsのsampleを参考にしました.
またAccess-Control-Allow-Origin
も指定できます.さらにmicro-corsを使うことでCORSの設定もできるようです.
これでフロントエンドからアクセスできるようになりました!🎉
次にフロントエンドの実装をしていきます.
まずsrc/pages/index.tsx
を以下のように変更します.
import type { NextPage } from 'next'
import Head from 'next/head'
import Link from 'next/link'
const Home: NextPage = () => {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<Link href={`/jobs`}>
<a>jobs</a>
</Link>
<br />
<Link href={`/issues`}>
<a>issues</a>
</Link>
+ <br />
+ <Link href={`/articles`}>
+ <a>articles</a>
+ </Link>
</main>
</>
)
}
export default Home
次に/pages
ディレクトリに新たにpage/articles
ディレクトリを作成しその中にindex.tsx
を作成します.実際にデータを取得する処理を追加します.
ここは前章までと同じでuseSWRを用いて実装しています.
import { GraphQLClient, gql } from 'graphql-request'
import useSWR from 'swr'
const API = '/api/graphql'
const getArticlesQuery = gql`
query {
getArticles {
id
title
content
}
}
`
type FetchData = {
getArticles: [
{
id: string
title: string
content: string
}
]
}
const getArticles = () => {
const client = new GraphQLClient(API)
const { data, error } = useSWR<FetchData>(
[getArticlesQuery],
// fetcher
(query) => client.request(query)
)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return data.getArticles.map((article) => (
<li key={article.id}>{article.title}</li>
))
}
const ArticlePage = () => (
<>
<h1>Articles List</h1>
{getArticles()}
</>
)
export default ArticlePage
まずはarticleを全て取得するクエリを実行します.
ここまでできればyarn dev
で立ち上げ,localhost:3000/articles
に遷移するとデータがフェッチされます.
もっと使いやすく
今回はarticleを全て取得するクエリを実行しましたが,IDを指定してarticleを取得するリゾルバも用意しているので前章を参考にしてIDからarticleを取得することもできます.
--省略--
+ <br />
+ <Link href={`/articles/1`}>
+ <a>articles/1</a>
+ </Link>
今までの部分と同様にarticles/1
へのリンクを追加します.
(フォームか何かを設置して自分で変更できるようにするようが実用的かと思いますが)
そして次にpages/articles/[id].tsx
を作成し,以下のようにします.
import { useRouter } from 'next/router'
import { GraphQLClient, gql } from 'graphql-request'
import useSWR from 'swr'
const API = '/api/graphql'
const getArticleById = gql`
query($getArticleId: Int!) {
getArticle(id: $getArticleId) {
id
title
content
}
}
`
type FetchData = {
getArticle: {
id: string
title: string
content: string
}
}
const getArticles = () => {
const router = useRouter()
const { id } = router.query
const client = new GraphQLClient(API)
const { data, error } = useSWR<FetchData>(
[getArticleById],
// fetcher
(query) =>
client.request(query, {
getArticleId: Number(id),
})
)
console.log(data)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <li key={data.getArticle.id}>{data.getArticle.title}</li>
}
const SingleArticlePage = () => (
<>
<h1>Articles List</h1>
{getArticles()}
</>
)
export default SingleArticlePage
解説
Githubのissueを取得した時と同じようにQueryに変数を渡します.
client.request
の第2引数で指定します.
まとめ
実際に手を動かしながら作ってみると気づけることがたくさんありました.まだまだGraphQLでできることはたくさんありますが,同じような入門者の方の助けとなれば嬉しいです.
参考にさせていただいた記事など
Discussion