Prisma 是 Node.js 和 TypeScript 的 开源 ORM。它可用作编写纯 SQL 或使用其他数据库访问工具(如 SQL 查询生成器(如 knex.js)或 ORM(如 TypeORM 和 Sequelize)的 替代 方式。Prisma 目前支持 PostgreSQL、MySQL、SQL Server、SQLite、MongoDB 和 CockroachDB(预览)。
虽然 Prisma 可以与纯 JavaScript 一起使用,但它包含 TypeScript 并提供超出 TypeScript 生态系统中其他 ORM 保证的类型安全级别。您可以在 此处 找到 Prisma 和 TypeORM 的类型安全保证的深入比较。
入门
出于本指南的目的,您将使用 SQLite 数据库来节省设置数据库服务器的开销。请注意,即使您使用的是 PostgreSQL 或 MySQL,您仍然可以遵循本指南 - 您将获得在正确位置使用这些数据库的额外说明。
如果您已经有现有项目并考虑迁移到 Prisma,您可以按照 将 Prisma 添加到现有项目 的指南进行操作。如果您要从 TypeORM 迁移,您可以阅读指南 从 TypeORM 迁移到 Prisma。
创建您的 NestJS 项目
要开始使用,请安装 NestJS CLI 并使用以下命令创建您的应用程序骨架:
$ npm install -g @nestjs/cli
$ nest new hello-prisma
请参阅 第一步 页面,了解有关此命令创建的项目文件的更多信息。另请注意,您现在可以运行 npm start
来启动您的应用程序。在 http://localhost:3000/
运行的 REST API 当前提供在 src/app.controller.ts
中实现的单个路由。在本指南的过程中,您将实现其他路由来存储和检索有关 users 和 posts 的数据。
设置 Prisma
首先将 Prisma CLI 安装为项目中的开发依赖项:
$ cd hello-prisma
$ npm install prisma --save-dev
在以下步骤中,我们将使用 Prisma CLI。作为最佳实践,建议通过在 CLI 前添加 npx
来在本地调用它:
$ npx prisma
如果您使用的是 Yarn,则可以按如下方式安装 Prisma CLI:
$ yarn add prisma --dev
安装后,您可以通过在 CLI 前添加 yarn
来调用它:
$ yarn 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,否则 DATABASE_URL
变量将不会从 .env
中获取。
SQLite 数据库是简单文件;使用 SQLite 数据库不需要服务器。因此,您无需使用 host 和 port 配置连接 URL,只需将其指向本地文件(在本例中称为 dev.db
)。此文件将在下一步中创建。
如果您使用的是 PostgreSQL 或 MySQL,请展开
使用 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 migration dev
命令生成 SQL 文件并直接针对数据库运行它们。在本例中,在现有的 prisma
目录中创建了以下迁移文件:
$ tree prisma
prisma
├── dev.db
├── migrations
│ └── 20201207100915_init
│ └── migration.sql
└── schema.prisma
展开以查看生成的 SQL 语句
安装并生成 Prisma 客户端
Prisma 客户端是一种类型安全的数据库客户端,它是从您的 Prisma 模型定义中生成的。由于采用了这种方法,Prisma 客户端可以公开专门针对您的模型量身定制的 CRUD 操作。
要在您的项目中安装 Prisma 客户端,请在您的终端中运行以下命令:
$ npm install @prisma/client
请注意,在安装过程中,Prisma 会自动为您调用 prisma generate
命令。将来,您需要在每次更改 Prisma 模型后运行此命令以更新生成的 Prisma 客户端。
prisma generate
命令读取你的 Prisma 模式并更新 node_modules/@prisma/client
内生成的 Prisma 客户端库。
在 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 模式对 User
和 Post
模型进行数据库调用。
仍然在 src
目录中,创建一个名为 user.service.ts
的新文件,并向其中添加以下代码:
import { Injectable } from '@nestjs/common'
import { Prisma, User } from '@prisma/client'
import { PrismaService } from './prisma.service'
@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 { Post, Prisma } from '@prisma/client'
import { PrismaService } from './prisma.service'
@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,
})
}
}
您的 UserService
和 PostService
当前包装了 Prisma Client 中可用的 CRUD 查询。在实际应用程序中,服务也是向您的应用程序添加业务逻辑的地方。例如,您可以在 UserService
内部使用一个名为 updatePassword
的方法,该方法负责更新用户的密码。
请记住在应用程序模块中注册新服务。
在主应用程序控制器中实现您的 REST API 路由
最后,您将使用在前面部分中创建的服务来实现应用程序的不同路由。出于本指南的目的,您将把所有路由放入已经存在的 AppController
类中。
将 app.controller.ts
文件的内容替换为以下代码:
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
} from '@nestjs/common'
import { Post as PostModel, User as UserModel } from '@prisma/client'
import { UserService } from './user.service'
import { PostService } from './post.service'
@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) })
}
}
This controller implements the following routes:
GET
/post/:id
: Fetch a single post by itsid
/feed
: Fetch all published posts/filter-posts/:searchString
: Filter posts bytitle
orcontent
POST
/post
: Create a new post- Body:
title: String
(required): The title of the postcontent: String
(optional): The content of the postauthorEmail: String
(required): The email of the user that creates the post
- Body:
/user
: Create a new user- Body:
email: String
(required): The email address of the username: String
(optional): The name of the user
- Body:
PUT
/publish/:id
: Publish a post by itsid
DELETE
/post/:id
: Delete a post by itsid
Summary
在本秘籍中,您学习了如何使用 Prisma 和 NestJS 来实现 REST API。实现 API 路由的控制器正在调用PrismaService
,后者又使用 Prisma Client 向数据库发送查询以满足传入请求的数据需求。
如果您想了解有关使用 NestJS 和 Prisma 的更多信息,请务必查看以下资源: