使用NestJS和Prisma构建GraphQL服务器

简述

使用Node.js的后端框架NestJS和TypeScript的ORMPrisma来创建GraphQL服务器。
顺便说一下,Prisma也可以用于构建REST API,它并不特化于GraphQL。
然而,Prisma模式定义的结构非常接近GraphQL的SDL(Schema Definition Language = Schema定义语言),而且Prisma Client可以覆盖GraphQL的复杂性,因此我认为它与GraphQL的兼容性非常高。

引入步骤

由于NestJS的文档中分别包含了Prisma和GraphQL的内容,所以我们基本上会按照这些内容进行进展。
链接:
Prisma:https://docs.nestjs.com/recipes/prisma
GraphQL:https://docs.nestjs.com/graphql/quick-start

安装NestJS CLI

$ npm i -g @nestjs/cli

创建一个NestJS项目

$ nest new my-app

安装Prisma CLI和Prisma Client。

$ cd my-app
$ npm install @prisma/client
$ npm install --save-dev prisma

4. Prisma的配置

$ npx prisma init

当执行此命令时,将在根目录下生成prisma/schema.prisma和.env文件。

与数据库建立连接

根据自己准备的数据库编辑prisma/schema.prisma和.env文件。我自己是使用Docker搭建了一个Mysql容器,所以配置如下所示。

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

generator client {
  provider = "prisma-client-js"
}
DATABASE_URL="mysql://root:password@db/development"

※@后面的db是容器名。
※development是表名,可以随意更改为其他合适的名称,没有问题。

version: '3.8'
services:
  server:
    build: .
    volumes:
      - ./:/usr/app
    ports:
      - '3000:3000'
    tty: true
    depends_on:
      - db
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - ./prisma/mysql:/var/lib/mysql

按照公式文件所述,如果想要快速执行,使用SQLite是最简便的方法。

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

generator client {
  provider = "prisma-client-js"
}
DATABASE_URL="file:./dev.db"

6. 定义架构

在schema.prisma中定义模式。这里以创建一个名为User的示例为例。

(↑に追加)
model User {    
  id Int @id @default(autoincrement())  
  registeredAt DateTime?    
  updatedAt DateTime?   
  email String? 
  name String   
} 

7. 将架构应用于数据库。

将在第6步中定义的模式应用到数据库中有两种方法。

Create migrations from your Prisma schema, apply them to the database, generate artifacts (e.g. Prisma Client)
  $ prisma migrate dev --preview-feature

  Push the Prisma schema state to the database
  $ prisma db push --preview-feature

如果执行迁移命令migrate,将在prisma目录下生成一个migrations目录,并在其中为每个迁移生成一个单独的目录。
migration.sql文件中包含了用于应用架构的SQL语句。
migrate还提供了其他命令,如reset和status,请详见以下链接:
https://www.prisma.io/docs/reference/api-reference/command-reference#prisma-migrate-preview

├── prisma
│   ├── migrations
│   │   └── 20201220094108_
│   │       └── migration.sql

如果执行db push,将直接将模式应用到数据库中,而不会生成任何文件。
考虑到rails中更 commonly preferred ridgepole而不是migration,个人觉得可以使用db push。

创建一个处理PrismaClient的PrismaService。

为了使用NestJS处理Prisma Client API,我们将在src文件夹中创建prisma.service.ts。

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient
  implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

安装与GraphQL相关的包。

npm i @nestjs/graphql graphql-tools graphql apollo-server-express

如果使用Fastify,请安装apollo-server-fastify来替代apollo-server-express。另外,在src/main.ts的app处将Fastify进行替换。

const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );

创建对象类型和解析器。

GraphQL的实现有两种方法:Code first和Schema first。这次我们使用了从代码创建模式的Code first方法。

import { ObjectType, Field, Int, HideField } from '@nestjs/graphql';    

@ObjectType()   
export class User { 
  @Field((type) => Int) 
  id: number;   
  @Field({ name: 'registeredAt' })  
  createdAt?: Date; 
  updatedAt?: Date; 
  email: string;    
  @HideField()  
  password: string; 
  name?: string;    
}
import { Resolver, Query } from '@nestjs/graphql';  
import { User } from '../models/user.model';    
import { PrismaService } from '../prisma.service';  

@Resolver((of) => User) 
export class UserResolver { 
  constructor(private prisma: PrismaService) {} 

  @Query((returns) => [User])   
  async users() {   
    return this.prisma.user.findMany(); 
  } 
}

在这个时候,当我们在nest-cli.json的compilerOptions中添加GraphQL插件并使其生效时,可以在一定程度上减少代码量,非常方便。

"compilerOptions": {
    "plugins": [
      {
        "name": "@nestjs/graphql/plugin",
        "options": {
          "typeFileNameSuffix": [".input.ts", ".model.ts"]
        }
      }
    ]
  }

另外,还有一个从在第6步创建的 schema.prisma 中自动生成 NestJS 代码的生成器,但是它并没有得到官方的支持,因此在选择上仍然相当困难。

    • https://github.com/EndyKaufman/typegraphql-prisma-nestjs-example

 

    • https://github.com/wSedlacek/prisma-generators/tree/master/libs/nestjs

 

    https://github.com/unlight/prisma-nestjs-graphql

11. 在AppModule中导入PrismaService、GraphQLModule和UserResolver。

在AppModule中,我们将导入使用8创建的PrismaService,使用9安装的GraphQLModule和使用10创建的UserResolver。

@Module({
  imports: [
    GraphQLModule.forRoot({ 
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),    
      debug: true,  
      playground: true, 
    }),
  ],
  controllers: [AppController],
  providers: [AppService, PrismaService],
})

校验行动

image.png

此外,正如所定义的autoSchemaFile一样,将自动生成一个名为src/schema.gql的模式定义文件。

到最后

查看了Nexus 1.0的发布说明后,我设法考虑如何将Nexus集成进Nest+Prisma,但我个人感觉目前在Nest+Prisma中使用Nexus似乎没有太多意义。
如果此文章有任何错误或者有其他更好的实践方法,请指正。感谢您的阅读。

bannerAds