📝

【備忘録】Nest × Prisma × Typegraphql

2024/06/16に公開

前提

RDBはPostgreSQL。
Nxでmonorepo環境を作成済。

npx create-nx-workspace --preset=nest

必要なパッケージをインストール

npm i @nestjs/apollo @nestjs/graphql @prisma/client express-graphql graphql graphql-fields graphql-scalars nestjs-prisma prisma-case-format type-graphql typegraphql-prisma
npm --save-dev @types/express @types/supertest prisma source-map-support supertest ts-loader tsconfig-paths @types/graphql-fields

Prisma設定

初期化

npx prisma init

Generatorの設定

prisma initで生成されたshema.prismaファイルに追記。

./prisma/shema.prisma
generator client {
  provider = "prisma-client-js"
}

/* 追加 */
generator typegraphql {
  provider            = "typegraphql-prisma"
  output              = "../src/generated/typegraphql-prisma"
  formatGeneratedCode = "prettier"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

DATABASE_URLの書き換え

./.env
DATABASE_URL="postgresql://【ユーザ名】:【パスワード】@【IPアドレス】:5432/【DB名】?schema=public"

project.jsonにコマンドを追加

project.json
{
  ...
  "targets": {
    ...
    "pull": {
      "executor": "nx:run-commands",
      "options": {
        "command": "npx prisma db pull",
        "cwd": "apps/sample"
      }
    },
    "gen": {
      "executor": "nx:run-commands",
      "options": {
        "commands": [
          "prisma-case-format --file prisma/schema.prisma",
          "prisma format",
          "prisma generate"
        ],
        "cwd": "apps/sample"
      },
    }
  }
}

pullを実行するとprisma/schema.prismaにテーブル情報が追加される。
その後、genを実行するとsrc/generatedmodelsresolversが生成される。

Appの設定

context作成

src/context.ts
import { PrismaClient } from '@prisma/client'

export interface Context {
  prisma: PrismaClient
}

export const prisma = new PrismaClient()

AppModuleを書き換える

サンプルを参考に書き換える。
※本当はproviders@/generated/typegraphql-prismaresolvers(自動生成されたすべてのresolver)を指定できるっぽいけど、エラーが出て指定できない...

src/app.module.ts
import { Module } from '@nestjs/common'
import { ApolloDriver } from '@nestjs/apollo'
import { TypeGraphQLModule } from 'typegraphql-nestjs'
import path from 'path'
import { Context } from '@/context'
import { resolvers } from '@/resolvers'
import { prisma } from './context'

@Module({
  imports: [
    TypeGraphQLModule.forRoot({
      driver: ApolloDriver,
      path: '/',
      emitSchemaFile: path.resolve(__dirname, './prisma/schema.graphql'),
      validate: false,
      context: (): Context => ({ prisma }),
    }),
  ],
  providers: [UsersCrudResolver],
})
export class AppModule {}

main.tsを書き換える

src/main.ts
import 'reflect-metadata'
import { NestFactory } from '@nestjs/core'
import { ExpressAdapter, NestExpressApplication } from '@nestjs/platform-express'
import { AppModule } from '@/app.module'

async function main() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule, new ExpressAdapter())
  await app.listen(3000)
}

main().catch(console.error)

カスタムリゾルバ

フィールドの追加

src/resolvers/CustomeUserResolver.ts
import { Ctx, FieldResolver, Resolver, Root } from 'type-graphql'
import { Context } from '@/context'
import { Articles, Users } from '@/generated/typegraphql-prisma'

@Resolver(() => Users /* model */)
export class CustomUsersResolver {
  @FieldResolver(() => [Articles /* 追加するフィールドのmodel */], { nullable: true })
  async articles(@Root() user: Users, @Ctx() { prisma }: Context): Promise<Articles[]> {
    const articles = await prisma.articles.findMany({
      where: { userId: user.userId } // 検索条件
    })
    return articles
  }

  @FieldResolver(() => Users, { nullable: true })
  async fullName(@Root() user: Users, @Ctx() { prisma }: Context): Promise<string> {
    return `${user.lastName} ${user.firstName}`
  }
}

作成したカスタムリゾルバをproviderに追加

src/app.module.ts
...
import { CustomUsersResolver } from '@/resolvers/CustomUsersResolver';

@Module({
  ...
  providers: [UsersCrudResolver, CustomUsersResolver],
})

Discussion