Next.js v13.4.0 からApp Routerが安定版となりました!
注目度も高く今後は利用がドンドン拡大していきそうです。
元々 pages/api
にAPIを生やす機能を使ってApollo Serverを動かす事が出来ました。
App Routerになってapiの作り方も変わったため、
この記事ではApp Routerを用いたNext.jsでApollo Serverを動かす方法をご紹介します!
補足
先に記述をすると apollo-server-integration-next
というライブラリ を使用します。
スター数が2023/05/21現在 89 と少々心もとない…と思われる人もいるかと思いますが、
個別プロジェクトで運用している限り問題なく動いています ☺️
よくNext.js + Apollo Serverで紹介される apollo-server-micro
というパッケージがありまして、
https://www.npmjs.com/package/apollo-server-micro
パッケージ自体 deprecated になる予定なのと
Next.js13とは相性が悪いようで私が試したところうまく動かなかったためapollo-server-integration-next
を利用した、という背景があります。
検証した環境
1 | next | 13.4.3 |
2 | @apollo/server | 4.7.1 |
3 | graphql | 16.6.0 |
4 | @as-integrations/next | 2.0.0 |
Next.js(App Router利用)を動かせるようにする
安定版となったApp Routerを用いてNext.js を動かせるようにします。
インストールは公式ドキュメントを参考にします。
Getting Started: Installation | Next.js
create-next-app
を実行
私は慣れ親しんでいる yarn を使いました。
# npmの場合
$ npx create-next-app@latest
# yarnの場合
$ yarn create next-app
# pnpmの場合
$ pnpm create next-app
create-next-app については
順番に質問がされていきます。
全てデフォルトの選択肢を選んでみます。
余談ですが ✔ Use App Router (recommended)?
という選択肢が出るようになっていますねー!🙌
ファイルの構成は↓のようになりました。
app/
が作られApp Routerが使われている事が分かります!
Next.jsのアプリケーションが動く事を確認
$ yarn run dev
- ready started server on 0.0.0.0:3000, url: http://localhost:3000
Prettier導入(optional)
書きやすさアップのためPrettierを入れておきます。
(気付けばPrettierなしでは書けない身体になってしまった 😂)本筋と逸れるので簡潔に
$ yarn add -D prettier
{
"singleQuote": true,
"semi": false,
"printWidth": 100
}
$ npx prettier --write .
Apollo Serverを動かせるようにする
本題です!
Apollo Server用エンドポイントの作成
ここでは http://localhost:3000/graphql
にApollo Server用のエンドポイントを生やします。
App Router導入前後でのAPI作成の違い
App RouterになってAPIの作成方法が大きく変わりました。
以前はpages/api
配下にファイルを作成する事でファイル名がエンドポイントになってました。
// App Router以前
// http://localhost:3000/api/hello が該当エンドポイントになる
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json({ title: 'Hello, Next.js!' })
}
App Routerでは app/ フォルダ内に更にフォルダを作成し、その中に route.ts を配置する事でエンドポイントとして認識してくれます
// App Router以降
// http://localhost:3000/api/hello が同じく該当エンドポイントになる
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
return NextResponse.json({ title: "Hello, Next.js!" })
}
更にGET・POSTのように関数を分けるようになっています
詳しくは公式ドキュメントを参考下さい
http://localhost:3000/graphql
にアクセスした際に反応させるため、 app/graphql/route.ts
を追加します
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
return NextResponse.json({ title: 'hello GraphQL!' })
}
これで雛形が用意出来ました!
Apollo Serverを動かす
ここまでの状態が整っていれば、Apollo Serverの導入はかなりシンプルに出来ます
Apollo Serverを動かすのに必要なパッケージを追加
$ yarn add @apollo/server graphql
Next.jsのエンドポイントとApollo Serverを繋げるための apollo-server-integration-next を追加
$ yarn add @as-integrations/next
公式ドキュメントを参考に route.ts を記載します!
import { startServerAndCreateNextHandler } from '@as-integrations/next'
import { ApolloServer } from '@apollo/server'
import { gql } from 'graphql-tag'
const resolvers = {
Query: {
hello: () => 'world',
},
}
const typeDefs = gql`
type Query {
hello: String
}
`
const server = new ApolloServer({
resolvers,
typeDefs,
})
const handler = startServerAndCreateNextHandler(server)
export async function GET(request: Request) {
return handler(request)
}
export async function POST(request: Request) {
return handler(request)
}
Next.jsを起動し http://localhost:3000/graphql にアクセスすると
$ yarn run dev
- ready started server on 0.0.0.0:3000, url: http://localhost:3000
# => http://localhost:3000/graphql にアクセス
無事Apollo Serverが導入出来て、playgroundも起動出来ました!🎉
ちなみにコードを変更したらちゃんと即時反映してくれます、めっちゃ優秀✨
もちろん
- schema.graphql を元にquery・mutationを定義する
- query・mutation実行前にtokenを元に検証する
といった事も出来ます。
時間があればまた別記事であげようと思います!
(余談)startServerAndCreateNextHandlerについて
肝となる startServerAndCreateNextHandler
は何をしているのかな?
と気になったので軽く調べてみました。
startServerAndCreateNextHandler
は以下のように定義されています
declare function startServerAndCreateNextHandler<Req extends HandlerRequest = NextApiRequest, Context extends BaseContext = object>(server: ApolloServer<Context>, options?: Options<Req, Context>): {
<HandlerReq extends NextApiRequest>(req: HandlerReq, res: NextApiResponse): Promise<unknown>;
<HandlerReq_1 extends Request | NextRequest>(req: HandlerReq_1, res?: undefined): Promise<Response>;
};
ApolloServerを引数で受け取り、hadler
には2パターンの関数を更に返しています。
const handler = startServerAndCreateNextHandler(server)
2パターンの関数はそれぞれ、App Router以前に適した型 / App Router後に適した型 という事ですね。
// App Router以前のAPIに適した型
<HandlerReq extends NextApiRequest>(req: HandlerReq, res: NextApiResponse): Promise<unknown>;
// App Router後のAPIに適した型
<HandlerReq_1 extends Request | NextRequest>(req: HandlerReq_1, res?: undefined): Promise<Response>;
GET・POSTが呼ばれた際の引数を渡しているので
export async function GET(request: Request) {
return handler(request)
}
export async function POST(request: Request) {
return handler(request)
}
もしhandler
に型を定義するのであれば↓のような形ですね!
const handler: (req: Request | NextRequest, res?: undefined) => Promise<Response> =
startServerAndCreateNextHandler(server)
つまり startServerAndCreateNextHandler
がやっている大まかな事は
- Next.jsのAPIで受け取った情報(例えばheaderなど)をApollo Serverに適した形に変換
- Apollo Serverを実行
- Apollo ServerからのレスポンスをNext.jsのAPIに適した形に変換
という印象です。