NextjsのRoute HandlersにGraphQLサーバー(Apollo)を設定&クライアント側で呼び出し
やりたいこと
GraphQLサーバー&クライアント両方の仕組みを持ったNextjsアプリサンプルを構築するために、Route HandlersにGraphQLサーバー(Apollo)を設定します。
動機
GraphQLは非常に使い勝手が良く、積極的にNextjsに取り入れたいと思っています。また、NextjsにはRoute Handlersという仕組みがあります。src/app/api配下にroute.tsを作成し、GETやPOSTと言う名前でfunctionをexportすると指定したフォルダパスに対応したメソッドのRESTAPIが作成されます。こちらにGraphQLを指定することでGraphQLサーバーを実現します(具体的な方法は後述します)。以前CustomServerでGraphQLサーバー&クライアントを実現したのですが、Nextjsのレンダリング最適化が失われてしまい非常に重いアプリができてしまいました。。ここは公式に則ってRoute HandlersでGraphQLサーバーを実現したいと思います。
公式
Nextjs公式が出しているGraphQL+APIRouteのサンプルはこちらです。
Apolloについては示されていなかったので調べたところ@as-integrations/nextが使えるようです。
@as-integrations/next
Apolloについては@as-integrations/nextが使えるようです。
こちらの公式を見ると
import { startServerAndCreateNextHandler } from "@as-integrations/next";
・・・
const server = new ApolloServer({
resolvers,
typeDefs,
});
export default startServerAndCreateNextHandler(server)
とstartServerAndCreateNextHandlerをexportしてRoute HandlersでこちらをGETとPOSTでreturnしてあげることでGraphQLが利用できるようになるようです。簡単ですね。
サンプル
※GraphQLではDB(postgresqlのneon)に接続するようになっていますがここでは省略します。
src/app/api/graphql/route.ts
import { db } from '@/server/db'
import GraphQL from '@/server/graphql'
const graphql = new GraphQL(db)
export async function GET(req: Request) {
return graphql.handler(req)
}
export async function POST(req: Request) {
return graphql.handler(req)
}
src/server/graphql/index.ts
/* eslint-disable @typescript-eslint/no-unused-vars */
import { ApolloServer } from '@apollo/server'
import { startServerAndCreateNextHandler } from "@as-integrations/next";
import { makeExecutableSchema } from "@graphql-tools/schema";
import type { NeonHttpDatabase } from 'drizzle-orm/neon-http'
import ToDo, {
CreateTodoInputType,
DeleteTodoInputType,
UpdateTodoInputType,
} from '@/server/graphql/model/todo'
import { NextApiRequest, NextApiResponse } from 'next'
class GraphQL {
private todo: ToDo
constructor(db: NeonHttpDatabase<Record<string, never>>) {
this.todo = new ToDo(db)
}
private typeDefs = `
type Todo {
taskId: Int!
contents: String
}
input CreateTodoInput {
contents: String!
}
input UpdateTodoInput {
taskId: Int!
contents: String!
}
input DeleteTodoInput {
taskId: Int!
}
type UpdateTodoOutput {
taskId: Int!
contents: String!
}
type DeleteTodoOutput {
taskId: Int!
}
type Query {
getTodos: [Todo]
}
type Mutation {
createTodo(input: CreateTodoInput): [Todo]
updateTodo(input: UpdateTodoInput): UpdateTodoOutput
deleteTodo(input: DeleteTodoInput): DeleteTodoOutput
}
`
private resolvers = {
Query: {
getTodos: async (_root: unknown, {}, _req: NextApiRequest) => {
return this.todo.getToDos()
},
},
Mutation: {
createTodo: async (
_root: unknown,
params: CreateTodoInputType,
_req: NextApiRequest,
) => {
return this.todo.createToDo(params)
},
updateTodo: async (
_root: unknown,
params: UpdateTodoInputType,
_req: NextApiRequest,
) => {
const res = await this.todo.updateToDo(params)
return params.input
},
deleteTodo: async (
_root: unknown,
params: DeleteTodoInputType,
_req: NextApiRequest,
) => {
await this.todo.deleteToDo(params)
return params.input
},
},
}
public handler = startServerAndCreateNextHandler(
new ApolloServer({
schema: makeExecutableSchema({
typeDefs: this.typeDefs,
resolvers: this.resolvers,
}),
}),
{
context: async (req: NextApiRequest) => req,
},
)
}
export default GraphQL
クライアントコンポーネント
'use client'
import { client } from '@/util/apollo/csrClient'
import { ApolloProvider } from '@apollo/client'
import { Crud } from '@/app/components/pages/labo/nextjs/crud/Crud'
export default function CrudPage() {
return (
<ApolloProvider client={client}>
<Crud />
</ApolloProvider>
)
}
↑のCrudコンポーネントの中で
export function CrudList() {
const { loading, error, data } = useQuery(GetTodosDocument, {
fetchPolicy: 'cache-and-network',
})
return <> ・・・</>
}
こんな感じで呼び出せます。
Discussion