Prismaを使うとGraphQLが簡単にできるようなので、やってみました。
前回書いた、Prismaとdbdocsを組み合わせて使ってみたという記事の実質続きみたいな感じです。
ジェネレーターを実装する
GraphQLを扱うためにはリゾルバという実装が必要なようですが、これをスキーマ定義から作成するようにします。
既に構築済みのprismaプロジェクトがある場合、typegraphql-prisma
を追加するだけです。
yarn add -D typegraphql typegraphql-prisma
追加したら、schema.prismaに以下のgeneratorセクションを追加します。
generator typegraphql {
provider = "typegraphql-prisma"
}
追加したら以下のコマンドでリゾルバが作成されます。
npx prisma generate
作成されたリゾルバは、デフォルトではnode_modules/@generated/type-graphql
にあります。
GraphQLのサーバーを構築する
本題です。
Apolloを使用してGraphQLサーバーを構築しました。
実装はこちらを参考にしつつ行いました。
import { PrismaClient } from "@prisma/client";
import { ApolloServer, BaseContext } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { resolvers } from "@generated/type-graphql";
import { buildSchema } from "type-graphql";
(async () => {
const schema = await buildSchema({
resolvers,
validate: false,
});
const prisma = new PrismaClient();
interface Context {
prisma: PrismaClient;
}
const server = new ApolloServer<Context>({
schema,
});
const { url } = await startStandaloneServer(server, {
context: async () => ({ prisma }),
listen: { port: 4000 },
});
})();
最新のApolloだと、contextの関数がasyncになっているので注意が必要です。
これで起動してみたら、エラーが起きました。
Looks like you've forgot to provide experimental metadata API polyfill. Please read the installation instruction for more details
同じバグに悩んでいる人は見つかりましたが、解決せず。。
ただ、今までts-nodeで直接実行していたので、以下のスクリプトをpackage.jsonに追加してからyarn start
で実行するようにしました。
"scripts": {
"postinstall": "yarn compile",
"compile": "tsc",
"start": "yarn compile && node ./dist/index.js"
},
一応tsconfig.jsonも公式のものと合わせました。
{
"compilerOptions": {
"rootDirs": ["src"],
"outDir": "dist",
"lib": ["es2020"],
"target": "es2020",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"types": ["node"]
}
}
これで実行しましたが、まだエラーが出ます。
インポートに失敗している?
yarn run v1.22.15
$ npm run compile && node ./dist/index.js
> dbdocs-playground@1.0.0 compile
> tsc
node_modules/type-graphql/dist/errors/ArgumentValidationError.d.ts:1:38 - error TS2307: Cannot find module 'class-validator' or its corresponding type declarations.
1 import type { ValidationError } from "class-validator";
~~~~~~~~~~~~~~~~~
node_modules/type-graphql/dist/schema/build-context.d.ts:2:39 - error TS2307: Cannot find module 'class-validator' or its corresponding type declarations.
2 import type { ValidatorOptions } from "class-validator";
~~~~~~~~~~~~~~~~~
node_modules/type-graphql/dist/utils/emitSchemaDefinitionFile.d.ts:2:10 - error TS2305: Module '"graphql/utilities/printSchema"' has no exported member 'Options'.
2 import { Options as GraphQLPrintSchemaOptions } from "graphql/utilities/printSchema";
~~~~~~~
Found 3 errors in 3 files.
Errors Files
1 node_modules/type-graphql/dist/errors/ArgumentValidationError.d.ts:1
1 node_modules/type-graphql/dist/schema/build-context.d.ts:2
1 node_modules/type-graphql/dist/utils/emitSchemaDefinitionFile.d.ts:2
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
最近prismaがv5.0.0をリリースしたばかりなので、それが原因かもしれません。
一度バージョンを前のメジャーバージョンの最新である4.16.2
に落とします。
(typegraphql-prisma
もprismaのバージョンに強く依存しているので、落とします。)
yarn add prisma@4.16.2 typegraphql-prisma@0.26.0
一応prisma generate
も再実行しておきます。
yarn prisma generate
しかし同じエラーに…
一つのエラーはyarn add class-validator
してあげたら解決しましたが、もう一つのエラーはどうやらGraphQLのバージョンを15に下げないといけないみたいです。
yarn add graphql@15.8.0
下げましたが、やはりエラーになってしまいました。
yarn run v1.22.15
$ yarn compile && node ./dist/index.js
$ tsc
src/generated/type-graphql/helpers.ts(2,27): error TS2307: Cannot find module 'graphql-fields' or its corresponding type declarations.
src/generated/type-graphql/models/Post.ts(2,33): error TS2307: Cannot find module 'graphql-scalars' or its corresponding type declarations.
src/generated/type-graphql/models/Post.ts(7,2): error TS1238: Unable to resolve signature of class decorator when called as an expression.
The runtime will invoke the decorator with 2 arguments, but the decorator expects 1.
src/generated/type-graphql/models/Post.ts(9,4): error TS1240: Unable to resolve signature of property decorator when called as an expression.
Argument of type 'ClassFieldDecoratorContext<Post, number> & { name: "id"; private: false; static: false; }' is not assignable to parameter of type 'string | symbol'.
src/generated/type-graphql/models/Post.ts(14,4): error TS1240: Unable to resolve signature of property decorator when called as an expression.
...長すぎるので以下省略...
最初の方にgraphql-fieldsとgraphql-scalarsというライブラリが必要ということが書いてあるのでインストールしましたが、解決しませんでした。(むしろエラーが増えた)
ここに来て、typegraphql-prisma
の公式のサンプルが合ったことを思い出して、これをクローンして実施してみました。
typegraphql-prisma/examples/1-prototyping at main · MichalLytek/typegraphql-prisma · GitHub
インストールとGraphQLの実装を作成します。
yarn && yarn prisma generate
実行します。
yarn start
無事起動すると以下のメッセージが出ます。
GraphQL is listening on 4000!
クライアントから操作してみる
GraphQLクライアントから操作してみます。
クライアントにはAltairを使用します。
linuxであればsnapからインストールできます
snap install altair
インストールできたら起動してみます。
altair
URLに「http://localhost:4000
」と入力し、Query
の部分にはサンプルリポジトリにあるexamples.graphql
を貼り付けます。
query GetAllUsersAndPosts {
users {
...UserData
posts {
...PostData
}
}
posts {
...PostData
author {
...UserData
}
}
}
query GetSelectedPost {
post(where: { id: "cl63gazie0009prtpf16vmw0i" }) {
...PostData
author {
...UserData
}
}
}
query GetSomeUsers {
users(where: { email: { contains: "prisma" } }, orderBy: { name: desc }) {
...UserData
posts(skip: 1) {
...PostData
}
}
}
mutation UpdatePost {
updateOnePost(
where: { id: "cl63gazie0010prtphdhetz2w" }
data: { published: { set: true } }
) {
...PostData
}
}
mutation AddUser {
createOneUser(data: { email: "test@test.test", name: "Test" }) {
...UserData
}
}
mutation AddUserWithPost {
createOneUser(
data: {
email: "test2@test.test"
name: "Test2"
posts: {
create: {
title: "Test post"
content: "Missing content"
published: false
}
}
}
) {
...UserData
posts {
...PostData
}
}
}
query GetPrismaPostCount {
aggregatePost(where: { title: { contains: "Prisma" } }) {
_count {
_all
}
}
}
fragment UserData on User {
id
email
name
}
fragment PostData on Post {
id
createdAt
updatedAt
published
title
content
}
準備ができました。
早速クエリを実行してみます。
とりあえずユーザーとポストを作成します。
作成できました。
続けて取得のためのクエリを投げてみます。
バッチリですね!
take
、skip
という引数を使用して、ページネーションも可能です。
感想
既存のプロジェクトにtypegraphql-prismaを入れることに苦戦してしまいました。。
GraphQLの利便性とPrismaによる拡張性を兼ね備えることで最強になりました。
とはいえ、独自クエリなどを入れたいケースは追加で実装が必要になると思いますが、基本的にはこれだけで割と網羅できるんではないでしょうか。
こういった技術を活用して仕事を効率化していきたいですね。
追記: 2023-08-03
package.jsonの内容とtsconfig.jsonの内容を変更して、どうにか動かせるようにしました。
tsconfig.json
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"lib": ["es2018", "esnext.asynciterable"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
}
}
package.json
{
"private": true,
"scripts": {
"start": "ts-node --transpile-only ./index.ts",
"generate": "prisma generate",
"seed": "ts-node --transpile-only ./prisma/seed.ts"
},
"dependencies": {
"@apollo/server": "^4.9.0",
"@prisma/client": "5.1.0",
"@types/graphql-fields": "^1.3.4",
"graphql": "^16.6.0",
"graphql-fields": "^2.0.3",
"graphql-scalars": "^1.20.1",
"reflect-metadata": "^0.1.13",
"type-graphql": "2.0.0-beta.1"
},
"devDependencies": {
"@types/node": "^18.11.18",
"prisma": "^5.1.0",
"prisma-dbml-generator": "^0.10.0",
"ts-node": "^10.9.1",
"typegraphql-prisma": "0.27.0",
"typescript": "^4.9.4"
}
}
apolloも最新のバージョンに対応しました。
現状、実装のはじめにリフレクションするためのライブラリを宣言しておかないと動かないみたいですね。
import "reflect-metadata";
import { buildSchema } from "type-graphql";
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import path from "path";
import { PrismaClient } from "@prisma/client";
import { resolvers } from "./src/generated/type-graphql";
interface Context {
prisma: PrismaClient;
}
async function main() {
const schema = await buildSchema({
resolvers,
emitSchemaFile: path.resolve(__dirname, "./generated-schema.graphql"),
validate: false,
});
const prisma = new PrismaClient();
await prisma.$connect();
const server = new ApolloServer({
schema,
});
const { url } = await startStandaloneServer(server, {
context: async (): Promise<Context> => ({ prisma }),
listen: { port: 4000 },
});
console.log(`GraphQL is listening on ${url}!`);
}
main().catch(console.error);
参考
Prismaを使ってサクッとGraphQLのバックエンドを作成する! - FLINTERS Engineer's Blog