在这节教程里,我们来使用 NestJS、Prisma 和基于 JWT 的认证来创建一个安全的后端程序。我们的应用将包括管理书籍的 CRUD 操作,并且端点会通过 JWT 认证来保护。
先决条件在开始之前,请确保您的机器上已安装了以下软件。
- Node.js 和 npm(建议安装长期稳定版)
- 全局安装 Nest CLI 时,请使用命令
npm install -g @nestjs/cli
- PostgreSQL(或其他支持 Prisma 的数据库)已经运行并且可以访问
首先,使用 Nest CLI 来创建一个NestJS项目:
开新书店
逛书店
第二步:安装依赖项:
接下来,按照以下步骤安装JWT身份验证和Prisma所需的必要依赖项。
npm install @nestjs/jwt @nestjs/passport passport passport-jwt @prisma/client prisma
第 3 步:初始化 Prisma
如果你使用的是 Postgresql 的 Docker 镜像,就在 docker-compose.yml 文件里加上下面这些内容。
version: '3.8'
services:
postgres:
container_name: postgres_container
image: postgres:13
ports:
- 5434:5432
environment:
设置环境变量如下:POSTGRES_USER: postgres, POSTGRES_PASSWORD: 123, POSTGRES_DB: book-store
# 数据库名称: book-store
volumes:
- postgres_data:/var/lib/postgresql/data
更新你的 .env
文件,包含你的数据库连接字符串,例如:DB_CONNECTION_STRING
DATABASE_URL="postgresql://postgres:123@localhost:5434/book-store?schema=public" # 连接到本地PostgreSQL数据库的URL
在你的项目中初始化 Prisma 并配置数据库连接:
npx prisma init
注:此命令用于初始化Prisma项目。
步骤 4:配置 Prisma 模式在 prisma/schema.prisma
文件中添加 User
和 Book
模型:
数据源 db {
提供器 = "postgresql"
url = env("DATABASE_URL")
}
生成器 client {
提供器 = "prisma-client-js"
}
模型 User {
id Int @标识符 @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique
firstName String?
lastName String?
password String
}
模型 Book {
id Int @标识符 @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
description String?
link String
用户ID Int
}
运行 Prisma 迁移命令以将模式应用到数据库中:请注意,Prisma 是一个特定的工具或框架名称。
运行数据库迁移的命令:
npx prisma migrate dev --name init
生成 Prisma 客户端代码:
你可以运行以下命令来生成代码:npx prisma generate
.
生成 Auth 模块、控制器和相关服务:
nest 创建 auth 模块
nest 创建 auth 控制器
nest 创建 auth 服务
设置认证模块:
import { Module } from '@nestjs/common'; // 提供了NestJS的基本功能
import { JwtModule } from '@nestjs/jwt'; // 用于处理JWT的认证
import { PassportModule } from '@nestjs/passport'; // 用于集成Passport中间件
import { AuthService } from './auth.service'; // 提供了认证服务
import { AuthController } from './auth.controller'; // 控制层,处理认证相关的HTTP请求
import { JwtStrategy } from './jwt.strategy'; // JWT认证策略
import { PrismaService } from '../prisma.service'; // 数据库操作服务
/**
* @Module: 定义了一个模块,该模块包含了导入的模块、提供者和控制器。
*/
@Module({
imports: [
PassportModule, // 用于集成Passport中间件
JwtModule.register({
secret: process.env.JWT_SECRET || 'secretKey', // 密钥,如果环境变量中没有设置JWT_SECRET,则使用'秘密密钥'
signOptions: { expiresIn: '60m' }, // 签发选项,设置令牌的有效期为60分钟
}),
],
providers: [AuthService, JwtStrategy, PrismaService], // 提供者列表,包括认证服务、JWT策略和数据库操作服务
controllers: [AuthController], // 控制器列表,处理认证相关的HTTP请求
})
export class AuthModule {} // 定义了一个认证模块
配置 auth.service.ts
文件
实现一个支持注册和登录功能的AuthService:
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma.service';
import * as bcrypt from 'bcrypt';
// 注册为可注入的类
@Injectable()
export class AuthService {
// 构造函数
constructor(
private jwtService: JwtService,
private prisma: PrismaService
) {}
// 验证用户并验证密码
async validateUser(email: string, pass: string): Promise<any> {
const user = await this.prisma.user.findUnique({ where: { email } });
if (user && await bcrypt.compare(pass, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
// 生成并返回登录令牌
async login(user: any) {
const payload = { email: user.email, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
// 注册新用户并返回用户信息(不包括密码)
async register(email: string, pass: string) {
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(pass, salt);
const user = await this.prisma.user.create({
data: {
email,
password: hashedPassword,
},
});
const { password, ...result } = user;
return result;
}
}
设置 auth.controller.ts
在 AuthController 里创建登录和注册的端点:
引入 { Controller, Post, Body } from '@nestjs/common';
引入 { AuthService } from './auth.service';
@Controller('auth')
出口类 AuthController {
constructor(私有 authService: AuthService) {}
@Post('登录')
异步登录(@Body() req) {
返回 this.authService.login(req);
}
@Post('注册')
异步注册(@Body() req) {
返回 this.authService.register(req.email, req.password);
}
}
设置一下jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
/**
* JwtStrategy 类继承了 PassportStrategy,并使用了 Strategy 策略。
* 这个类的主要作用是验证 JWT 令牌并从令牌中提取用户信息。
*/
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
/**
* 构造函数中,设置了从请求头中提取 JWT 令牌的方法、是否忽略过期时间以及密钥。
*/
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 从请求头中提取 JWT 令牌
ignoreExpiration: false, // 不忽略过期时间
secretOrKey: process.env.JWT_SECRET || 'secretKey', // 密钥或密钥环境变量
});
}
/**
* 验证方法,从 JWT 令牌中提取用户 ID 和电子邮件地址。
*/
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}
创建一个认证闸门(jwt-auth.guard.ts),
导入 { Injectable } from '@nestjs/common';
导入 { AuthGuard } from '@nestjs/passport';
@可注入
导出 class JwtAuthGuard extends AuthGuard('jwt') {}
第六步:设置 Prisma 服务端 (Prisma,一个数据库访问层工具)
创建一个名为 prisma.service 的 Prisma 服务来处理与数据库的交互:
从 '@nestjs/common' 导入 { Injectable, OnModuleInit, OnModuleDestroy };
从 '@prisma/client' 导入 { PrismaClient };
@可注入()
导出类 PrismaService 继承自 PrismaClient 并实现 OnModuleInit 和 OnModuleDestroy {
异步 onModuleInit() {
await this.$connect(); // 等待连接数据库
}
异步 onModuleDestroy() {
await this.$disconnect(); // 等待断开数据库连接
}
}
步骤 7:创建书本模块
创建 Books 模块和对应的控制器、服务:
生成一个名为 'books' 的模块:
nest generate module books
这将生成一个名为 'books' 的模块。
生成一个名为 'books' 的控制器:
nest generate controller books
这将生成一个名为 'books' 的控制器。
生成一个名为 'books' 的服务:
nest generate service books
这将生成一个名为 'books' 的服务。
设置 Books 模块(在 books.module.ts 文件中):
import { Module } from '@nestjs/common';
import { BooksService } from './books.service';
import { BooksController } from './books.controller';
import { PrismaService } from '../prisma.service';
@Module({
providers: [BooksService, PrismaService],
controllers: [BooksController]
})
export class BooksModule {}
实现 BooksService(books.service.ts)功能:
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma.service';
import { Book } from '@prisma/client';
@Injectable()
export class BooksService {
constructor(private prisma: PrismaService) {}
/**
* 创建一个新的书籍条目
* @param data 书籍数据,不包含id
* @returns Promise<Book>
*/
async create(data: Omit<Book, 'id'>): Promise<Book> {
return this.prisma.book.create({ data });
}
/**
* 获取用户的所有书籍条目
* @param userId 用户ID
* @returns Promise<Book[]>
*/
async findAll(userId: number): Promise<Book[]> {
return this.prisma.book.findMany({ where: { userId } });
}
/**
* 获取单个书籍条目
* @param id 书籍ID
* @param userId 用户ID
* @returns Promise<Book>
*/
async findOne(id: number, userId: number): Promise<Book> {
return this.prisma.book.findFirst({ where: { id, userId } });
}
/**
* 更新书籍条目
* @param id 书籍ID
* @param data 更新数据
* @param userId 用户ID
* @returns Promise<Book>
*/
async update(id: number, data: Partial<Book>, userId: number): Promise<Book> {
return this.prisma.book.updateMany({
where: { id, userId },
data,
}).then((result) => result.count ? this.prisma.book.findUnique({ where: { id } }) : null);
}
/**
* 删除书籍条目
* @param id 书籍ID
* @param userId 用户ID
* @returns Promise<Book>
*/
async remove(id: number, userId: number): Promise<Book> {
return this.prisma.book.deleteMany({
where: { id, userId },
}).then((result) => result.count ? this.prisma.book.findUnique({ where: { id } }) : null);
}
}
通过 JWT 认证保护 BooksController 的安全。
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Request } from '@nestjs/common';
import { BooksService } from './books.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@Controller('books')
@UseGuards(JwtAuthGuard)
export class BooksController {
constructor(private readonly booksService: BooksService) {}
@Post()
create(@Body() createBookDto, @Request() req) {
return this.booksService.create({ ...createBookDto, userId: req.user.userId });
}
@Get()
findAll(@Request() req) {
return this.booksService.findAll(req.user.userId);
}
@Get(':id')
findOne(@Param('id') id: string, @Request() req) {
return this.booksService.findOne(+id, req.user.userId);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateBookDto, @Request() req) {
return this.booksService.update(+id, updateBookDto, req.user.userId);
}
@Delete(':id')
remove(@Param('id') id: string, @Request() req) {
return this.booksService.remove(+id, req.user.userId);
}
}
第八步:把所有的东西整合起来
确认所有模块都正确导入到主应用程序模块中。
// 导入 NestJS 的 Module 类
import { Module } from '@nestjs/common';
// 导入自定义的认证模块
import { AuthModule } from './auth/auth.module';
// 导入自定义的书籍模块
import { BooksModule } from './books/books.module';
// 创建一个应用模块,该模块依赖于认证模块和书籍模块
@Module({
imports: [AuthModule, BooksModule],
})
export class AppModule {}
运行应用
使用以下命令来运行您的应用程序:
运行开发环境: npm run start:dev
(这将启动开发环境)
在这篇教程中,我们创建了一个使用 Prisma 进行数据库交互和使用 JWT 保护 API 端点的安全 NestJS 应用程序。我们涵盖了设置 Prisma 架构文件、创建用于身份验证和书籍的模块化实现以及保护端点的安全。你现在拥有了一个使用 JWT 进行认证,并具备书籍 CRUD 操作的安全 NestJS 后端。
参考文献 文档 | NestJS - 一个渐进的 Node.js 框架Nest 是一个用于构建高效和可扩展的 Node.js 服务器端应用的框架。它使用了渐进式 JavaScript…docs.nestjs.com Prisma 文档开始使用 Prisma 的官方文档,请点击这里了解更多关于 Prisma 的内容…www.prisma.io JWT.IO - JSON Web Tokens 介绍什么是 JSON Web Tokens,它们的工作原理,何时该用以及为什么使用它们。jwt.ioGitHub : https://github.com/tharindu1998/book-store
Stackademic 🎓解释:Stackademic 结合了 "Stack" 和学术的含义。
读到最后,感谢您的陪伴。在您离开时,
- 请考虑点赞并关注作者! 👏
- 关注我们哦 X | LinkedIn | YouTube | Discord
- 您还可以访问我们的其他平台:In Plain English | CoFeed | Differ
- 更多内容,请访问 Stackademic.com