Prisma
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类型安全性保证的深入比较。
入门
在本教程中,您将学习如何从头开始使用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模式中的User
和Post
模型进行数据库调用。
在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,
});
}
}
您的UserService
和PostService
当前包装了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
:按title
或content
过滤帖子
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的信息,请务必查看以下资源: