Next.js(App Router利用)でApollo Serverを動かす

投稿日
Next.js(App Router利用)でApollo Serverを動かす

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 というライブラリ を使用します。

An Apollo Server integration for use with Next.js
199
23
TypeScript

スター数が2023/05/21現在 89 と少々心もとない…と思われる人もいるかと思いますが、

個別プロジェクトで運用している限り問題なく動いています ☺️





よくNext.js + Apollo Serverで紹介される apollo-server-micro というパッケージがありまして、

https://www.npmjs.com/package/apollo-server-micro


パッケージ自体 deprecated になる予定なのと

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 については

API Reference: create-next-app | Next.js





順番に質問がされていきます。

全てデフォルトの選択肢を選んでみます。

create-next-appを実行

余談ですが ✔ Use App Router (recommended)? という選択肢が出るようになっていますねー!🙌





ファイルの構成は↓のようになりました。

app/が作られApp Routerが使われている事が分かります!

App Routerを有効にし作成されたファイル構成




Next.jsのアプリケーションが動く事を確認

$ yarn run dev
- ready started server on 0.0.0.0:3000, url: http://localhost:3000
Next.jsが動く事を確認

Prettier導入(optional)

書きやすさアップのためPrettierを入れておきます。

(気付けばPrettierなしでは書けない身体になってしまった 😂)

本筋と逸れるので簡潔に

$ yarn add -D prettier
.prettierrc
{
  "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配下にファイルを作成する事でファイル名がエンドポイントになってました。

pages/api/hello.ts
// 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/api/hello/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のように関数を分けるようになっています


詳しくは公式ドキュメントを参考下さい

Routing: Route Handlers | Next.js






http://localhost:3000/graphqlにアクセスした際に反応させるため、 app/graphql/route.ts を追加します

app/graphql/route.ts
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
  return NextResponse.json({ title: 'hello GraphQL!' })
}
http://localhost:3000/graphql でAPIにアクセス出来るようにする

これで雛形が用意出来ました!

Apollo Serverを動かす

ここまでの状態が整っていれば、Apollo Serverの導入はかなりシンプルに出来ます




Apollo Serverを動かすのに必要なパッケージを追加

$ yarn add @apollo/server graphql


Next.jsのエンドポイントとApollo Serverを繋げるための apollo-server-integration-next を追加

apollo-server-integrations/apollo-server-integration-next: An Apollo Server integration for use with Next.js

$ yarn add @as-integrations/next



公式ドキュメントを参考に route.ts を記載します!

app/graphql/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 は以下のように定義されています

startServerAndCreateNextHandler.d.ts
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に適した形に変換

という印象です。

プロフィール画像
Yuki Takara
都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。