如何使用Prisma、SQLite和Nest.js与Typescript实现GraphQL服务器

716 阅读13分钟

使用Prisma、SQLite和Nest.js用Typescript实现GraphQL服务器

Nest.js建立在Node.js和Express之上,这可以说是用JavaScript构建服务器的最流行方式。Nest.js是一个基于TypeScript的JavaScript框架,用于开发和扩展后端微服务。

作为TypeScript的静态类型,它允许你做各种JavaScript任务,作为一个逃生舱。这保证了你的代码更加稳固。

此外,它使代码更容易浏览。这是因为所有的东西都是强类型的,对于大型应用程序来说,更具有可扩展性。这提供了一个开箱即用的应用程序架构,使开发人员和团队能够构建高度可测试、可扩展、松散连接和易于维护的应用程序。

Nest.js与其他后端框架一样,被设计为提供类似Angular的体验。因此,当你创建一个新的Nest.js应用程序时,你将被启动到一个类似于普通前端Angular应用程序的环境中。

主要的区别是,你最终创建的将是你的后端服务。你将建立一个服务器,将其与数据库连接,并做所有与基于后端应用程序相关的典型任务。

一览GraphQL

GraphQL是一种查询语言,用于访问和修改API(相互连接的数据源)中的数据。它与各种服务器端语言兼容,包括Next.js。GraphQL允许你查询数据并接收你需要的结果。

你可以使用GraphQL在一个请求中查询众多相关资源。换句话说,你可以跨越关系进行查询。GraphQL还允许你查询相关项目,并收到与查询相同形状的响应。

因此,它被用来从服务器上加载数据到客户端应用程序。与SOAPREST等传统方法和服务相比,它允许你更有效地从API获得数据到你的应用程序。

例如,如果你使用REST来访问两个资源,你需要两个不同的端点来请求每个资源的数据。这也意味着,当收到一个请求时,API会以该实体的整个数据有效载荷进行响应。GraphQL允许你用一个请求来访问许多资源。

GraphQL有一个类型系统,允许你为你的数据定义一个模式。例如,一个由节点和边组成的图可以用来表示你的应用程序中的数据。节点代表对象。边缘代表这些对象之间的关系。这些反映了你的应用程序中的数据和数据之间的关系。

GraphQL的好处是,它允许客户准确地要求他们想要的东西。没有什么更多或更少。此外,由于没有多余的数据,请求和响应都很迅速。

下面是一个例子。

graphql-example

在这个案例中,客户要求得到所有用户的名字。因此,正如你在答案中看到的,我们只得到用户的名字,没有多余的数据。

一览Prisma的情况

Prisma有一个很好的建模语言来创建你的数据库。它还有一个强大的GraphQL ORM(对象关系映射),用于在JavaScript中处理数据库。此外,它使开发带有数据库的GraphQL APIs变得容易。使用GraphQL可以让开发者在他们的应用程序中获取数据,而Prisma简化了连接数据源的过程。

Prisma的优点是与数据库无关。它可以与关系型(SQL)和非关系型(NoSQL)数据库一起工作,包括MySQL、PostgreSQL和MongoDB。这意味着我们可以快速选择一个数据库或在数据库之间交换。不需要改变任何代码(也许是你想设置连接到特定数据库的六行)。

Prisma包装了你的数据库并将其作为GraphQL API公开。因此,它提供了一个GraphQL API,可以用来从实际的数据库中读取和写入,而不管我们使用的是SQL还是NoSQL。

目标

本指南的重点是让一个简单的服务器项目启动和运行。我们将使用Prisma、Nest.js、GraphQL和SQLite等技术作为项目的数据库。我们将创建一个简单的post GraphQL API,并支持所有的CRUD操作。

前提条件

要继续学习这篇文章,必须具备以下条件。

  • 安装了[Node.js]。
  • 在visual studio code或你喜欢的文本编辑器中设置好[Typescript]。
  • 之前有使用TypeScript的知识。

设置TypeScript

要开始设置TypeScript,首先确保Node.js已经安装。然后在你的命令行内运行npm install -g typescript 。最后,你可以通过运行tsc --version 命令来检查Typescript是否已经安装。

安装Nest.js CLI

Nest.js提供了一个命令行界面(CLI),可以通过命令行创建项目和文件。Nest.js框架CLI的主要目的之一是为后端开发人员提供一个模块化的代码结构。

这有助于大规模商业软件架构模式的开发和维护。另外,它还直接提供了依赖性注入,以促进结构良好的程序。

如果你已经安装了它,nest CLI运行以下命令来全局安装它。

npm i -g @nestjs/cli

设置好CLI后,我们可以按时运行命令来初始化一个Nest.js项目。

nest new nest-graphql-prisma-api

上述命令将帮助我们建立项目的脚手架。该命令将生成一个TypeScript启动项目,并提示你项目的详细信息,如名称、描述、版本号(默认为0.0.0),以及作者(可能是你的名字)。

完成这个过程后,你将拥有一个完全配置的Nest.js应用程序。包括安装在node模块目录中的所有依赖项。

在执行上述命令时,你仍然可以选择你想使用的软件包管理器,那就是yarn或npm。

nest-app-scaffolding

一旦安装过程完成,一个包含所有Nest.js依赖项的nest-graphql-prisma-api 文件夹就会在你执行上述命令的路径内创建。

安装Prisma CLI

Prisma CLI允许我们动态地构建应用程序模型。通过初始化新的应用程序资源,生成Prisma客户端,并分析现有的数据库模式。这样,我们就可以轻松地定义我们的项目模式,并将其连接到项目数据库。

Prisma CLI将在新生成的Nest.js应用程序内运行,所以确保你将目录改为nest-graphql-prisma-api

cd nest-graphql-prisma-api

然后你可以运行下面的命令,Prisma将作为这个项目的一个依赖项被全局安装。

npm i -g prisma

这将为你所在的操作系统下载Prisma CLI和Prisma引擎。

定义我们的Prisma数据模型

现在我们可以为我们的项目初始化Prisma模型。

prisma init

上述命令将创建。

  • 一个包含schema.prisma文件的prisma文件夹。在这个文件中,我们将实现posts 模型。

  • 一个*.env*文件。这个文件包含连接到数据库的配置。对于我们的案例,我们将使用SQLite。

默认情况下,.env 文件是为PostgreSQL数据库配置的。因此,要使用SQLite,请按以下方式编辑它。

DATABASE_URL="file:./dev.db"

同时,编辑prisma/schema.prisma文件如下。

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

generator client {
    provider = "prisma-client-js"
}

model Post {
    id        Int      @id @default(autoincrement())
    title     String
    content   String
    published Boolean  @default(false)
    createdAt DateTime @default(now())
}

这将配置SQLite数据库并构建Post 模型。生成器客户端已经为你配置好了,不需要再编辑。

创建第一次迁移

以编程方式设置这个SQL方案的好处是可以跳过几个按键。通过到关系型数据库管理系统(RDMS),创建一个数据库、表,并添加所有必要的字段。

这有助于你将你的项目从开发环境转移到生产环境或在其他开发者之间转移。你的实际模式设置存在于代码中。这使得从一个人到另一个人,或者从你的开发者机器到生产机器的模式转移变得更加容易。而不需要去找一个数据库管理员。

此外,如果你在不同的机器上手动设置模式,你必须确保它们相匹配。你没有一些自动的方法来确保模式的设置是正确的。

如果你犯了一个错误,数据库模式从你的机器到另一台机器不完全相同,你在运行应用程序时可能会出现一些错误。在模式已经设置好的情况下,你可以避免模式的不匹配。这就是为什么数据库迁移很有帮助。

为了启动Prisma数据库迁移以达到我们的开发目的,我们将执行以下命令。

prisma migrate dev --name init

从上面的命令来看,我们是在开发模式下运行我们的第一次迁移,给它取名为init 。如果你想使用一个多字的名字,请确保用下划线或连字符分隔这些字。该命令还将检查我们是否安装了Prisma客户端。如果我们没有,它将自动安装。

sqlite-db-migrations

设置、安装GraphQL包和添加GraphQL

  • @nestjs/graphql:用于提供GraphQL和Nest.js之间的互动。
  • graphql-tools:用于提供一个交互式 GraphQL 游戏场。
  • graphql:用于支持本地 GraphQL 设置。ts-morph,@apollo/gateway,apollo-server-express: 在后台工作,成功启动GraphQl服务器。

要安装上述软件包,请运行这个命令。

npm i @nestjs/graphql graphql-tools graphql ts-morph @apollo/gateway apollo-server-express@^2

生成GraphQL模式

src文件夹中,创建一个posts 文件夹。在posts 中,创建一个文件schema.graphql。在这个文件中,我们将定义GraphQL模式,如下所示。

type Post {
    id: ID!
    title: String!
    content: String!
    published: Boolean!
    createdAt: String!
  }

  input NewPost {
    title: String!
    content: String!
  }

  input UpdatePost {
    id:ID!
    published: Boolean
    title: String
    content: String
  }

  type Query {
    posts: [Post!]!
    post(id: ID!): Post
  }

  type Mutation {
    createPost(input: NewPost): Post!
    updatePost(input: UpdatePost): Post
    deletePost(id: ID!): Post
  }

从上面来看,我们正在声明类型和输入。

src文件夹中,我们将创建一个generate-typings.ts文件,它将使用*@nestjs/graphql.graphql*文件导出类型。在该文件中添加以下内容。

import { GraphQLDefinitionsFactory } from '@nestjs/graphql';
import { join } from 'path';
const definitionsFactory = new GraphQLDefinitionsFactory();
definitionsFactory.generate({
  typePaths: ['./src/**/*.graphql'],
  path: join(process.cwd(), 'src/graphql.ts'),
  outputAs: 'class',
});

从上面的代码块中,类型将从所有*.graphql文件中生成,并导出到src文件夹中的graphql.ts*文件中。要执行这个文件,请打开文本编辑器的终端,用tsc ,即执行它。

tsc src/generate-typings.ts

这个命令将创建一个转写的Typescriptgenerate-typings.js文件。从终端使用node执行该文件。

node src/generate-typings.js

这将在src文件夹中创建一个graphql.ts文件。该文件包含所有可与Nest.js对话的导出文件。

添加Prisma服务

src文件夹中,创建一个prisma.service.ts文件。该文件将以如下方式连接到Prisma客户端。

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

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

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close();
    });
  }
}

onModuleInit函数连接到Prisma客户端,它持有与我们数据库的连接。我们还通过使用一个beforeExit事件监听器来关闭该连接。

添加帖子服务

src/posts文件夹中,创建一个post.service.ts文件,包含如下逻辑。

import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma.service';
import { Post } from '@prisma/client';
import { NewPost, UpdatePost } from 'src/graphql';

@Injectable()
export class PostService {
  constructor(private prisma: PrismaService) {}

  // Get a single post
  async post(id: string): Promise<Post | null> {
    return this.prisma.post.findUnique({
      where: {
        id: parseInt(id),
      },
    });
  }

  // Get multiple posts
  async posts(): Promise<Post[]> {
    return this.prisma.post.findMany({});
  }

  // Create a post
  async createPost(input: NewPost): Promise<Post> {
    return this.prisma.post.create({
      data: input,
    });
  }

  // Update a post
  async updatePost(params: UpdatePost): Promise<Post> {
    const { id, published, title, content } = params;
    return this.prisma.post.update({
      where: {
        id: parseInt(id),
      },
      data: {
        ...(published && { published }),
        ...(title && { title }),
        ...(content && { content }),
      },
    });
  }

  // delete a post
  async deletePost(id: string): Promise<Post> {
    return this.prisma.post.delete({
      where: {
        id: parseInt(id),
      },
    });
  }
}

上面的文件包含了我们需要的所有逻辑,以支持我们的操作。

添加帖子解析器

为了暴露我们的解析器,我们需要在src/posts 中创建一个posts.resolvers.ts文件。这些解析器将如下所示。

import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { PostService } from './posts.service';
import { Post, NewPost, UpdatePost } from 'src/graphql';

@Resolver('Post')
export class PostResolvers {
  constructor(private readonly postService: PostService) {}

  @Query('posts')
  async posts() {
    return this.postService.posts();
  }

  @Query('post')
  async post(@Args('id') args: string) {
    return this.postService.post(args);
  }

  @Mutation('createPost')
  async create(@Args('input') args: NewPost) {
    return this.postService.createPost(args);
  }

  @Mutation('updatePost')
  async update(@Args('input') args: UpdatePost) {
    return this.postService.updatePost(args);
  }

  @Mutation('deletePost')
  async delete(@Args('id') args: string) {
    return this.postService.deletePost(args);
  }
}

上述文件暴露了我们所有的查询和突变。

将Prisma服务、post服务和post解析器与提供者连接起来

为了使我们的解析器和服务能够被访问,我们需要将它们封装在一个模块中作为提供者。因此,我们在src/posts文件夹中创建一个posts.module.ts文件,并添加以下内容。

import { Module } from '@nestjs/common';
import { PostResolvers } from './posts.resolvers';
import { PostService } from './posts.service';
import { PrismaService } from 'src/prisma.service';

@Module({
  providers: [PostResolvers, PostService, PrismaService],
})
export class PostModule {}

在上面的文件中,我们只是把PostResolversPostService作为提供者加入。

添加post Mutations

编辑src/app.module.ts文件如下。

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { PostModule } from './posts/posts.module';

@Module({
  imports: [
    PostModule,
    GraphQLModule.forRoot({
      typePaths: ['./**/*.graphql'],
    }),
  ],
})
export class AppModule {}

在上面的文件中,我们在模块的导入数组中公开了GraphQLModulePostModule。此外,我们还指定了GraphQLModuletypePaths

运行应用程序

在这一点上,我们已经准备好运行我们的应用程序。从你的文本编辑器的终端,执行以下命令来启动应用程序。

npm run start:dev

start-nestjs-dev-application

下面的命令将在开发模式下启动你的项目。如果你遇到了错误,请确保在继续前进之前重新审视这些步骤。

在你的浏览器中,打开一个标签,输入http://localhost:3000/graphql ,打开GraphQL游乐场。

用GraphQL操场测试应用程序

现在,是时候让我们使用API了。我们将按以下步骤来做。

创建一个帖子

在游乐场的左窗格中,输入以下内容。

mutation generatePost{
    createPost(input:{
      title:"A new second title"
      content:"A new second content"
    }){
      id
      title
      content
      published
      createdAt
    }
}

点击中间的播放按钮,观察右边的结果。

creating-a-post

取回帖子

在操场上打开一个单独的标签,在左窗格中输入以下内容。

query GetPosts{
    posts{
      id
      title
      content
      published
      createdAt
    }
}

点击中间的播放按钮,观察右边的结果。

fetching-posts

获取单个帖子

在操场上打开一个单独的标签,在左边的窗格中输入以下内容。

query GetPost{
    post(id:1){
      id
      title
      content
      published
    }
}

在id参数中输入你想得到的帖子的id,然后点击中间的播放按钮。观察右边的结果。

fetching-single-post

更新一个帖子

在操场上打开一个单独的标签,在左窗格中输入以下内容。

mutation updatePost{
    updatePost(input:{
      id:1,
      published:true
    }){
      id
      title
      content
      published
    }
}

输入你要更新的帖子的id。例如,在上面的突变中,我们只是更新发布的字段。你也可以用同样的方法编辑标题和内容字段。点击中间的播放按钮,观察右边窗格中的结果。

updating-a-post

删除一个帖子

在操场上打开一个单独的标签,在左边窗格中输入以下内容。

mutation deletePost{
    deletePost(id:1){
      id
      title
      content
      published
    }
}

在id参数上输入你想删除的帖子的id。然后,点击中间的播放按钮,观察右边的结果。

deleting-a-post

总结

在这一点上,我们已经使用Prisma和Nest.js创建了一个由所有CRUD操作组成的GraphQL API。