2023.12.31
Next.js + GraphQL Yoga + Pothos + Prisma + GraphQL Code Generatorで開発環境を構築する
概要
Nexusの更新が滞ってしまっている件について、業務上で関わりのあったエンジニアさんから、Pothosが代替ツールとなりそうという話を伺ったのと、個人的にもTypeScriptでGraphQLスキーマを管理できる方が良いと思っているので、以下のフレームワーク、ライブラリでの開発環境を構築してみます。
・Next.js(Production環境を想定してPages Routingを使います)
・GraphQL Yoga
・Pothos
・Prisma
・GraphQL Code Generator
・URQL
手順
Next.jsアプリケーションを作成
適当に yarn create next-app .
する。
なお、App RouterはまだProduction環境で使いたくないので、今回はPages Routerを使います。
パッケージインストール
bash_____terminal_____yarn add @pothos/core @pothos/plugin-prisma @prisma/client graphql graphql-yoga urql
bash_____terminal_____yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-urql prisma
package.jsonを編集
JSON_____package.json_____ "resolutions": {
"jackspeak": "2.1.1"
}
こちらについては以下の記事に理由が書いてあります。
タイミングによっては不要な場合もあると思いますが2023/12/31時点では必要でした。
For - Pothos + GraphQL Code Generatorでrequire('string-width')がERR_REQUIRE_ESMになったら試すこと
編集が終わったらパッケージをインストールし直します。
bash_____terminal_____rm yarn.lock
rm -fr node_modues
yarn
docker関連のファイルを作成
今回はNext.jsとDBのコンテナを立てて進めるため、以下のように/docker/docker-compose.ymlを作成します。
yml_____docker-compose.yml_____version: '3'
volumes:
node_modules:
postgres:
services:
app:
image: node:20
ports:
- 3101:3101
- 5555:5555
volumes:
- ../:/workspace:cached
- node_modules:/workspace/node_modules
working_dir: /workspace
tty: true
db:
image: postgres:14
environment:
POSTGRES_DB: database
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres:/var/lib/postgresql/data
ports:
- 5432:5432
これはお好みですが私はVSCodeのDev Containersで進めるので必要なファイルを作ります。
/.devcontainer/devcontainer.json
JSON_____devcontainer.json_____{
"name": "sample-app-dev-container",
"dockerComposeFile": ["../docker/docker-compose.yml"],
"service": "app",
"workspaceFolder": "/workspace",
"customizations": {
"vscode": {
"extensions": [
"prisma.prisma",
"esbenp.prettier-vscode"
]
}
}
}
docker compose up -d
したらVSCodeのDev Containerからコンテナの中に入って作業します。
prismaを初期化
bash_____terminal_____npx prisma init
.env
の値を編集します。
text_____.env_____DATABASE_URL="postgresql://postgres:password@db:5432/database?schema=public&connect_timeout=300"
schema.prismaを編集
pothosのgeneratorと適当なmodelを追加します。
prisma_____schema.prisma_____generator client {
provider = "prisma-client-js"
}
generator pothos {
provider = "prisma-pothos-types"
clientOutput = ".prisma/client"
output = "../src/generated/pothos-types.ts"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
content String?
published Boolean @default(false)
viewCount Int @default(0)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
generator clientのデフォルトの出力先はnode_modules/.prisma/clientなので、そこに合わせてclientOutputは.prisma/clientにします。yarn prisma:migrate:reset
しておきます。
PrismaClientを作成
schema.prismaに追加したgenerator pothosのclientOutputで設定したディレクトリからPrismaClientをimportしてclientを作成します。
/src/backend/db.ts
TypeScript_____db.ts_____import { PrismaClient } from '../../node_modules/.prisma/client';
export const prisma = new PrismaClient();
Schemaを作成
PothosのSchemaBuilderでschemaを作成します。
/src/backend/builder.ts
TypeScript_____builder.ts_____import SchemaBuilder from '@pothos/core';
import PrismaPlugin from '@pothos/plugin-prisma';
import type PrismaTypes from '@/generated/pothos-types';
import { prisma } from './db';
export const builder = new SchemaBuilder<{
Context: { userId: number };
PrismaTypes: PrismaTypes;
}>({
plugins: [PrismaPlugin],
prisma: {
client: prisma,
},
});
builder.queryType({});
// builder.mutationType({});
クエリフィールドを作成
適当なクエリフィールドを作成します。
/src/backend/types/hello.ts
TypeScript_____hello.ts_____import { builder } from '../builder';
builder.queryField('hello', (t) => {
return t.string({
args: {
name: t.arg.string(),
},
resolve: (parent, { name }) => `hello, ${name || 'World'}`,
});
});
Schemaをエクスポート
作成したクエリフィールドをインポートし、schemaとしてエクスポートします。
/src/backend/schema.ts
TypeScript_____schema.ts_____import { builder } from './builder';
import './types/hello';
export const schema = builder.toSchema();
APIルートを作成
GraphQLのAPIルートを作成します。
/src/pages/api/graphql.ts
TypeScript_____index.tsx_____import { schema } from '@/backend/schema';
import { createYoga } from 'graphql-yoga';
import type { NextApiRequest, NextApiResponse } from 'next';
export type Context = {
res: NextApiResponse;
req: NextApiRequest;
};
export default createYoga<
{
req: NextApiRequest;
res: NextApiResponse;
},
Context
>({
graphqlEndpoint: '/api/graphql',
schema,
context: ({ req, res }) => ({ req, res }),
});
GraphiQLで確認
yarn dev
したら localhost:3000/api/graphql
にアクセスして確認します。
リクエストを作成
動作が確認できたので、フロントエンドからのリクエストの部分を作っていきます。
GraphQL_____hello.graphql_____query Hello {
hello
}
codegen.tsを作成
GraphQL Code Generatorの設定ファイルを作成します。
今回はURQLというGraphQLクライアントをフロントエンドで使用するので、それに合った設定をします。
TypeScript_____codegen.ts_____import { schema } from './src/backend/schema';
import type { CodegenConfig } from '@graphql-codegen/cli';
import { printSchema } from 'graphql';
const config: CodegenConfig = {
schema: printSchema(schema),
overwrite: true,
documents: ['src/graphql/*.graphql'],
emitLegacyCommonJSImports: false,
generates: {
'src/generated/graphql.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-urql'],
config: {
scalars: {
DateTime: 'string',
},
},
},
},
};
export default config;
URQLのclientをprovideする
/src/pages/_app.tsx
tsx_____ _app.tsx_____import '@/styles/globals.css';
import type { AppProps } from 'next/app';
import { cacheExchange, fetchExchange, createClient, Provider } from 'urql';
export default function App({ Component, pageProps }: AppProps) {
const urqlClient = createClient({
exchanges: [cacheExchange, fetchExchange],
url: 'http://localhost:3000/api/graphql',
});
return (
<Provider value={urqlClient}>
<Component {...pageProps} />
</Provider>
);
}
続いて yarn graphql-codegen
します。
text_____terminal_____root@0e2e0a9368a3:/workspace# yarn graphql-codegen
yarn run v1.22.19
$ graphql-codegen
✔ Parse Configuration
✔ Generate outputs
Done in 0.73s.
成功したので、URQLのQueryのReact用Hooksが生成されました。
生成したHooksを使う
最後に生成されたHooksを使って確認してみます。
/src/pages/index.tsx
tsx_____index.tsx_____// ~~
export default function Home() {
const [{data}] = useHelloQuery();
console.log(data?.hello);
// ~~
コンソールでリクエストの結果が確認できました。
まとめ
これで一通り基礎的な環境は構築できたと思うので、これをベースにもう少し触っていきたいです。
環境構築をやるたびに何かしらの勉強になります。