深入探究:基于 Nest.js 和 Prisma 的后端开发指南
本文将带领你深入探究如何利用 Nest.js 和 Prisma 这两个强大的工具来构建优秀的后端应用。我们将从安装与配置开始,逐步探索如何构建 RESTful API、管理数据模型、实现身份验证与授权、进行错误处理与日志记录等方面的内容。
1. 环境安装与配置
1. 安装 Node.js 和 npm
确保你的计算机上已经安装了 Node.js 和 npm(Node.js 包管理器)。你可以在 Node.js 官网 上下载并安装最新版本的 Node.js。
这里更推荐使用 pnpm,它可以减少磁盘空间的使用,并且在安装依赖时更快。
2. 创建 Nest.js 项目
根据官方文档创建模版
npm i -g @nestjs/cli
nest new nest-prisma-example
3. 安装 Prisma 相关依赖
cd nest-prisma-example
pnpm i prisma
pnpm i -D @prisma/client
4. 配置 Prisma
VSCode 推荐大家安装 prsima 插件。
npx prisma init
命令会在项目根目录生成 prisma/schema.prisma
文件。
打开 schema.prisma
文件,修改 datasource db
数据库配置,我使用的是 PostgreSQL
,使用其他数据库修改 provider
即可。
datasource db {
provider = "postgresql"
url = "postgresql://postgres:postgres@localhost:5432/db_example"
}
多环境情况下可以采用 env("DATABASE_URL")
来使用,参考文档
2. 数据模型定义
1. 定义数据模型
编辑 schema.prisma
文件,使用 Prisma Schema Language 定义你的数据模型。在模型中定义实体、字段、关联等。例如:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
2. 使用 Prisma Client
通过运行以下命令来生成 Prisma Client:
npx prisma generate
这将在你的项目中生成 Prisma Client,你可以使用它来与数据库交互,执行 CRUD 操作等。
通过运行以下命令来生成数据库表结构:
prisma db push --preview-feature
3. 配置 tsconfig.json
{
"paths": {
"@prisma/client": ["./prisma/client"]
}
}
4. 在 Nest.js 中使用 Prisma
在 src/services
目录中,创建一个名为 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();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
在 service
中使用它来执行数据库操作。
import { PrismaService } from './prisma.service';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async findAll(): Promise<User[]> {
return this.prisma.user.findMany();
}
async findById(id: number): Promise<User> {
return this.prisma.user.findUnique({
where: { id },
});
}
async createUser(data: CreateUserDto): Promise<User> {
return this.prisma.user.create({
data,
});
}
async updateUser(id: number, data: UpdateUserDto): Promise<User> {
return this.prisma.user.update({
where: { id },
data,
});
}
async deleteUser(id: number): Promise<User> {
return this.prisma.user.delete({
where: { id },
});
}
}
3. 构建 RESTful API
1. 创建控制器(Controller)
首先,你需要在 src/controllers
创建一个控制器来处理 API 请求。控制器是负责接收 HTTP 请求并返回响应的地方。你可以使用 Nest CLI 来创建一个新的控制器:
nest generate controller your-controller-name
这将在你的项目中创建一个新的控制器文件,并为你生成基本的控制器代码结构。
2. 定义路由(Routes)
在控制器中,使用装饰器 @Get
、@Post
、@Put
、@Delete
等来定义路由,以处理对应的 HTTP 请求。例如:
import { Controller, Get } from '@nestjs/common';
@Controller('user')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
在上面的例子中,@Controller('user')
表示该控制器处理的请求路径为 /user
,@Get()
表示该路由处理 GET 请求,并指定了 findAll()
方法来处理该路由。
3. 编写服务(Service)
控制器通常不处理具体的业务逻辑,而是将请求委托给服务来处理。服务是用于处理业务逻辑的地方,包括数据的获取、处理和返回。在 src/services
你可以使用 Nest CLI 来创建一个新的服务:
nest generate service your-service-name
然后,在控制器中使用 import
语句导入你的服务,并在控制器中调用服务的方法来处理请求。
4. 注册模块(Module)
最后,将控制器和服务注册到相应的模块中。Nest.js 使用模块来组织应用程序的代码,因此你需要在模块中将控制器和服务关联起来。可以通过 @Module()
装饰器来定义一个模块,使用 imports
字段来导入依赖的模块,使用 controllers
字段来注册控制器,使用 providers
字段来注册服务。
// src/app.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './controllers/user/user.controller';
import { UserService } from './services//user/user.service';
@Module({
controllers: [UserController],
providers: [UserService],
})
export class AppModule {}
5. 启动应用程序
启动你的 Nest.js 应用程序,你可以使用 nest start
或者 pnpm start
,然后访问你定义的路由路径来测试你的 RESTful API。
4. 身份验证与授权
1. 安装相关库
pnpm install @nestjs/passport passport passport-local passport-jwt jsonwebtoken
2. 创建本地身份验证策略
// local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
3. 创建 JWT 身份验证策略
// jwt.strategy.ts
import { Strategy, ExtractJwt } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtPayload } from './jwt-payload.interface';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'your-secret-key',
});
}
async validate(payload: JwtPayload): Promise<any> {
const user = await this.authService.validateUserById(payload.sub);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
4. 创建 AuthService
// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.usersService.findByUsername(username);
if (user && user.password === password) {
return user;
}
return null;
}
async validateUserById(id: number): Promise<any> {
return this.usersService.findById(id);
}
async login(user: any): Promise<any> {
const payload = { username: user.username, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
}
5. 创建 AuthModule
// auth.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
PassportModule,
JwtModule.register({
secret: 'your-secret-key',
signOptions: { expiresIn: '1h' },
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
6. 使用 AuthModule
定义 JwtAuthGuard
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
在 controller
文件中使用 @UseGuards()
装饰器来应用认证守卫。
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './jwt-auth.guard'; // 导入你的 JWT 认证守卫
@Controller('user')
export class CatsController {
constructor() {}
@Get()
@UseGuards(JwtAuthGuard) // 应用 JWT 认证守卫
findAll(): string {
return 'This action returns all users';
}
}
5. 错误处理与日志记录
在 Nest.js 中,你可以使用拦截器(Interceptors)来处理全局的错误和日志记录,以及使用异常过滤器(Exception Filters)来处理特定类型的异常。
1. 错误处理
1. 全局错误处理
你可以创建一个全局的拦截器来处理全局的错误和日志记录。这个拦截器会捕获所有的异常并进行处理。
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, BadGatewayException, Logger } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
private logger = new Logger('ErrorsInterceptor');
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError(error => {
this.logger.error(`Error occurred: ${error}`);
return throwError(new BadGatewayException());
}),
);
}
}
然后,将这个拦截器应用到你的应用程序中:
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { ErrorsInterceptor } from './errors.interceptor';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: ErrorsInterceptor,
},
],
})
export class AppModule {}
2. 异常过滤器
如果你想对特定类型的异常进行处理,可以使用异常过滤器。例如,你可以针对 HttpException
进行特定的处理。
import { Catch, ExceptionFilter, HttpException, ArgumentsHost, Logger } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
private logger = new Logger('HttpExceptionFilter');
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
this.logger.error(`HTTP Exception: ${exception.message}`, exception.stack);
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
然后,在你的应用程序中注册这个过滤器:
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { HttpExceptionFilter } from './http-exception.filter';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
2. 日志记录
你可以使用 Nest.js 内置的 Logger 来记录日志。
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class CatsService {
private logger = new Logger('CatsService');
findAll(): string[] {
this.logger.log('Finding all cats...');
return ['Cat1', 'Cat2', 'Cat3'];
}
}
Logger 支持几个不同的日志级别,包括 log()
、error()
、warn()
、debug()
和 verbose()
。你可以根据需要选择适当的日志级别来记录不同类型的信息。
你也可以将日志记录到文件或其他目标,具体取决于你的需求和配置。
6. 小结
综上所述,Nest.js 和 Prisma 的结合为后端开发提供了一个现代化、高效且可维护的解决方案,使得开发者能够快速构建出功能丰富、高性能的应用程序,并且更容易地适应不断变化的业务需求。 现在的 Node.js 可不是以前的 "不适合大型项目" 的 Node.js了。
转载自:https://juejin.cn/post/7357261180492070938