跳到主要内容

Prisma

Prisma是用于Node.js和TypeScript的开源ORM。 它用作编写纯SQL的替代方案,或者使用其他数据库访问工具, 如SQL查询构建器(例如knex.js) 或ORM(例如TypeORMSequelize)。 Prisma目前支持PostgreSQL、MySQL、SQL Server、SQLite、MongoDB和 CockroachDB(预览版)。

虽然Prisma可以与纯JavaScript一起使用,但它支持TypeScript, 并提供了比TypeScript生态系统中其他ORM更高级别的类型安全性保证。 您可以在此处找到Prisma和TypeORM类型安全性保证的深入比较。

备注

如果您想快速了解Prisma的工作原理,可以查看快速入门或 在文档中 阅读介绍prisma-examples 存储库中还有用于RESTGraphQL的即用即用示例。

入门

在本教程中,您将学习如何从头开始使用NestJS和Prisma。 您将构建一个带有REST API的示例NestJS应用程序,该API可以读取和写入数据库中的数据。

出于本指南的目的,您将使用SQLite数据库以避免设置数据库服务器的开销。 请注意,即使您使用PostgreSQL或MySQL,您仍然可以按照本指南操作, 因为您将在适当的位置获得有关使用这些数据库的额外说明。

备注

如果您已经有一个现有项目并考虑迁移到Prisma, 可以按照将Prisma添加到现有项目的指南进行操作。 如果您要从TypeORM迁移, 可以阅读从TypeORM迁移到Prisma的指南。

创建NestJS项目

要开始,请使用以下命令安装NestJS CLI并创建您的应用程序框架:

npm install -g @nestjs/cli
nest new hello-prisma

查看First steps 页面以了解由此命令创建的项目文件的更多信息。 还要注意,您现在可以运行npm start以启动应用程序。 REST API运行在http://localhost:3000/, 当前仅提供一个在src/app.controller.ts中实现的路由。 在本指南的过程中,您将实现其他路由以存储和检索有关用户和帖子的数据。

设置Prisma

首先,在项目中安装Prisma CLI作为开发依赖项:

cd hello-prisma
npm install prisma --save-dev

在以下步骤中,我们将使用Prisma CLI。作为最佳实践,建议通过在其前面加上npx来本地调用CLI:

npx prisma

现在使用Prisma CLI的init命令创建初始Prisma设置:

npx prisma init

此命令将创建一个新的prisma目录,其中包含以下内容:

  • schema.prisma:指定您的数据库连接并包含数据库模式
  • .env:一个dotenv文件,通常用于存储数据库凭据的一组环境变量

设置数据库连接

您的数据库连接在schema.prisma文件中的datasource块中进行配置。 默认情况下,它设置为postgresql,但由于本指南中使用SQLite数据库, 您需要调整datasource块的provider字段为sqlite

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

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

现在,打开.env并调整DATABASE_URL环境变量如下:

DATABASE_URL="file:./dev.db"

确保已配置ConfigModule, 否则将无法从.env中提取DATABASE_URL变量。

SQLite数据库是简单的文件;使用SQLite数据库不需要服务器。 因此,您可以将其指向本地文件,这种情况下文件称为dev.db。此文件将在下一步中创建。

PostgreSQL或MySQL用户请展开

对于PostgreSQL和MySQL,您需要配置连接URL以指向数据库服务器。 您可以在此 了解有关所需连接URL格式的更多信息。

PostgreSQL

如果您使用的是PostgreSQL,您必须调整schema.prisma.env文件如下:

schema.prisma

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

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

.env

DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA"

用大写字母拼写的占位符替换为您的数据库凭据。 请注意,如果您不确定为SCHEMA占位符提供什么值,很可能是默认值public

DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"

如果您想了解如何设置PostgreSQL数据库,可以按照此指南 在Heroku上设置免费的PostgreSQL数据库

MySQL

如果您使用的是MySQL,您必须调整schema.prisma.env文件如下:

schema.prisma

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

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

.env

DATABASE_URL="mysql://USER:PASSWORD@HOST:PORT/DATABASE"

用大写字母拼写的占位符替换为您的数据库凭据。

使用Prisma Migrate创建两个数据库表

在此部分,您将使用Prisma Migrate在数据库中创建两个新表。 Prisma Migrate 根据Prisma模式中的声明性数据模型定义生成SQL迁移文件。 这些迁移文件是完全可定制的,因此您可以配置底层数据库的任何其他功能或包含其他命令,例如用于种子播种的命令。

schema.prisma文件中添加以下两个模型:

model User {
id Int @default(autoincrement()) @id
email String @unique
name String?
posts Post[]
}

model Post {
id Int @default(autoincrement()) @id
title String
content String?
published Boolean? @default(false)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}

有了Prisma模型,您可以生成SQL迁移文件并运行它们以对数据库进行更改。在终端中运行以下命令:

npx prisma migrate dev --name init

prisma migrate dev命令生成SQL文件并直接运行它们以对数据库进行更改。 在这种情况下,在现有的prisma目录中创建了以下迁移文件:

$ tree prisma
prisma
├── dev.db
├── migrations
│ └── 20201207100915_init
│ └── migration.sql
└── schema.prisma

展开以查看生成的SQL语句

在 SQLite 数据库中创建了以下表格:

-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"name" TEXT
);

-- CreateTable
CREATE TABLE "Post" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"content" TEXT,
"published" BOOLEAN DEFAULT false,
"authorId" INTEGER,

FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");

安装和生成Prisma Client

Prisma Client是从Prisma模型定义生成的类型安全数据库客户端。 由于采用了这种方法,Prisma Client可以公开特定于模型的 CRUD操作。

要在项目中安装Prisma Client,请在终端中运行以下命令:

npm install @prisma/client

请注意,在安装过程中,Prisma会自动为您调用prisma generate命令。 将来,您需要在对Prisma模型进行任何更改后运行此命令,以更新生成的Prisma Client。

::: prisma generate命令读取您的Prisma模式并更新node_modules/@prisma/client内部生成的Prisma Client库。 :::

在NestJS服务中使用Prisma Client

现在,您可以使用Prisma Client发送数据库查询。 如果要了解如何使用Prisma Client构建查询, 请查看API文档

在设置NestJS应用程序时,您将希望在服务中抽象出用于数据库查询的Prisma Client API。 要开始,您可以创建一个新的PrismaService,负责实例化PrismaClient并连接到数据库。

src目录中创建一个名为prisma.service.ts的新文件,并将以下代码添加到其中:

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

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

onModuleInit是可选的——如果您省略它,Prisma将在首次调用数据库时进行懒惰连接。

接下来,您可以编写服务,用于从Prisma模式中的UserPost模型进行数据库调用。

src目录中创建一个名为user.service.ts的新文件,并将以下代码添加到其中:

import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { User, Prisma } from '@prisma/client';

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

async user(
userWhereUniqueInput: Prisma.UserWhereUniqueInput,
): Promise<User | null> {
return this.prisma.user.findUnique({
where: userWhereUniqueInput,
});
}

async users(params: {
skip?: number;
take?: number;
cursor?: Prisma.UserWhereUniqueInput;
where?: Prisma.UserWhereInput;
orderBy?: Prisma.UserOrderByWithRelationInput;
}): Promise<User[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.user.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}

async createUser(data: Prisma.UserCreateInput): Promise<User> {
return this.prisma.user.create({
data,
});
}

async updateUser(params: {
where: Prisma.UserWhereUniqueInput;
data: Prisma.UserUpdateInput;
}): Promise<User> {
const { where, data } = params;
return this.prisma.user.update({
data,
where,
});
}

async deleteUser(where: Prisma.UserWhereUniqueInput): Promise<User> {
return this.prisma.user.delete({
where,
});
}
}

请注意,您正在使用Prisma Client生成的类型,以确保由您的服务公开的方法得到正确的类型。 因此,您节省了对模型进行打字和创建额外接口或DTO文件的样板。

现在,对于Post模型,执行相同的操作。

src目录中创建一个名为post.service.ts的新文件,并将以下代码添加到其中:

import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { Post, Prisma } from '@prisma/client';

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

async post(
postWhereUniqueInput: Prisma.PostWhereUniqueInput,
): Promise<Post | null> {
return this.prisma.post.findUnique({
where: postWhereUniqueInput,
});
}

async posts(params: {
skip?: number;
take?: number;
cursor?: Prisma.PostWhereUniqueInput;
where?: Prisma.PostWhereInput;
orderBy?: Prisma.PostOrderByWithRelationInput;
}): Promise<Post[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.post.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}

async createPost(data: Prisma.PostCreateInput): Promise<Post> {
return this.prisma.post.create({
data,
});
}

async updatePost(params: {
where: Prisma.PostWhereUniqueInput;
data: Prisma.PostUpdateInput;
}): Promise<Post> {
const { data, where } = params;
return this.prisma.post.update({
data,
where,
});
}

async deletePost(where: Prisma.PostWhereUniqueInput): Promise<Post> {
return this.prisma.post.delete({
where,
});
}
}

您的UserServicePostService当前包装了Prisma Client中可用的CRUD查询。 在真实的应用程序中,服务还将是添加业务逻辑到您的应用程序的地方。 例如,您可以在UserService中有一个名为updatePassword的方法,负责更新用户的密码。

在主应用程序控制器中实现REST API路由

最后,您将使用在前面部分中创建的服务来实现应用程序的不同路由。 为了本指南的目的,您将所有路由放入已经存在的AppController类中。

用以下代码替换app.controller.ts文件的内容:

import {
Controller,
Get,
Param,
Post,
Body,
Put,
Delete,
} from '@nestjs/common';
import { UserService } from './user.service';
import { PostService } from './post.service';
import { User as UserModel, Post as PostModel } from '@prisma/client';

@Controller()
export class AppController {
constructor(
private readonly userService: UserService,
private readonly postService: PostService,
) {}

@Get('post/:id')
async getPostById(@Param('id') id: string): Promise<PostModel> {
return this.postService.post({ id: Number(id) });
}

@Get('feed')
async getPublishedPosts(): Promise<PostModel[]> {
return this.postService.posts({
where: { published: true },
});
}

@Get('filtered-posts/:searchString')
async getFilteredPosts(
@Param('searchString') searchString: string,
): Promise<PostModel[]> {
return this.postService.posts({
where: {
OR: [
{
title: { contains: searchString },
},
{
content: { contains: searchString },
},
],
},
});
}

@Post('post')
async createDraft(
@Body() postData: { title: string; content?: string; authorEmail: string },
): Promise<PostModel> {
const { title, content, authorEmail } = postData;
return this.postService.createPost({
title,
content,
author: {
connect: { email: authorEmail },
},
});
}

@Post('user')
async signupUser(
@Body() userData: { name?: string; email: string },
): Promise<UserModel> {
return this.userService.createUser(userData);
}

@Put('publish/:id')
async publishPost(@Param('id') id: string): Promise<PostModel> {
return this.postService.updatePost({
where: { id: Number(id) },
data: { published: true },
});
}

@Delete('post/:id')
async deletePost(@Param('id') id: string): Promise<PostModel> {
return this.postService.deletePost({ id: Number(id) });
}
}

此控制器实现以下路由:

GET

  • /post/:id:通过其id获取单个帖子
  • /feed:获取所有已发布的帖子
  • /filter-posts/:searchString:按titlecontent过滤帖子

POST

  • /post:创建新帖子

    • Body:

      • title:String(必需):帖子的标题
      • content:String(可选):帖子的内容
      • authorEmail:String(必需):创建帖子的用户的电子邮件
  • /user: 创建新用户

    • Body:

      • email:String(必需):用户的电子邮件地址
      • name:String(可选):用户的姓名

PUT

  • /publish/:id:通过其id发布帖子

DELETE

  • /post/:id:通过其id删除帖子

摘要

在这个教程中,您学习了如何使用Prisma与NestJS实现REST API。 实现API路由的控制器调用PrismaService, PrismaService又使用Prisma Client向数据库发送查询,以满足传入请求的数据需求。

如果您想了解更多关于使用NestJS与Prisma的信息,请务必查看以下资源: