🗨️ 问一下DeepSeek今天要学什么
安装与运行
(typescript)安装express的声明文件
运行(生产)
- 生产模式启动,适用于部署环境、测试生产环境的启动流程(是运行生产环境代码)
- 不监听文件变化
运行(开发)
加快构建速度(使用swc)
构建生产环境代码
术语表
解答
- 为什么说是集成express,因为例如请求、相应对象是相通的
学习日记
D1
- 创建一个模块
- 连接数据库
- 添加Swagger文档
- 错误处理
基础模块
创建一个user
模块(生成模块必须文件和可选的.spec.ts
的测试文件),可使用nest g --help
查看更多命令
- 一般选择
REST API
- 创建的模块会自动更新
app.module.ts
文件
- 此命令不会创建
dto
文件(用来规范客户端与服务端之间的数据格式),需手动创建
- 文件一般被放在如
user/dto/create-user.dto.ts
这里

编辑后的代码示例:
user.controller.ts
(控制器)接收请求,具体的业务逻辑由use.service.ts
文件提供
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { Controller, Get, Post, Body } from '@nestjs/common'; import { UserService } from './user.service'; import { CreateUserDto } from './dto/create-user.dto';
@Controller('user') export class UserController { constructor(private readonly userService: UserService) {}
@Get() getUsers() { return this.userService.getUsers() }
@Post() createUser(@Body() createUserDto: CreateUserDto) { this.userService.createUser(createUserDto) } }
|
use.service.ts
(提供器、服务)处理更为复杂的业务需求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto';
@Injectable() export class UserService { private users = [ { id: 1, name: '小明', age: 18 }, { id: 2, name: '小白', age: 19 } ]
getUsers() { return this.users }
createUser(createUserDto: CreateUserDto) { const newUser = { id: Date.now(), ...createUserDto } this.users.push(newUser) return newUser } }
|
create-user.dto.ts
(Data Transfer Object、数据传输对象)可单独作为一个普通dto来使用
1 2 3 4
| export class CreateUserDto { name: string age: number }
|
还可以结合class-validator
(需要额外安装)来实现更高效的验证
1
| npm install class-validator class-transformer
|
1 2 3 4 5 6 7 8 9 10 11 12
| import { IsString, IsEmail, MinLength } from 'class-validator'
export class CreateUserDto { @IsString() @MinLength(2) name: string
age: number @IsEmail() email: string }
|
连接数据库
使用typeorm
连接数据库
1
| npm install @nestjs/typeorm
|
在app.module.ts
中全局配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { TypeOrmModule } from '@nestjs/typeorm';
@Module({ imports: [ UserModule, AuthModule, TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: '123456', database: 'nestjs', entities: [User], synchronize: true, }), ], controllers: [AppController], providers: [AppService], })
|
通过entities
创建一个用户表,一般在当前模块下创建,如user/entities/user.entity.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity() export class User { @PrimaryGeneratedColumn() id: number;
@Column() name: string;
@Column() age: number;
@Column() passwd: string;
@Column({ default: true }) isActive: boolean;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) createTime: Date; }
|
随后在user.modules.ts
中导入,一旦导入就会自动创建数据表(typeorm配置时,synchronize
设为true
)
1 2 3 4 5 6 7 8
| import { User } from './entities/user.entity';
@Module({ imports: [TypeOrmModule.forFeature([User])], controllers: [UserController], providers: [UserService], exports: [UserService], })
|
通过接口创建一条数据(控制器 -> 服务),在users.service.ts
中通过Repository
来执行,它提供了很多操作数据库的方法(find, create等)。有了这个,在绝大多数情况下不需要写SQL
以下代码中,实际起到作用的是InjectRepository
,Repository
在此只起到类型的作用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserDto } from './dto/create-user.dto'; import { User } from './entities/user.entity';
@Injectable() export class UserService { constructor( @InjectRepository(User) private userRepository: Repository<User>, ) { }
getUsers(): Promise<User[]> { return this.userRepository.find(); }
createUser(createUserDto: CreateUserDto): Promise<User> { const user = this.userRepository.create(createUserDto); return this.userRepository.save(user); }
findOne(username: string) { const user = this.userRepository.findOne({ where: { name: username } })
return Promise.resolve(user) } }
|
添加Swagger文档
1
| npm install @nestjs/swagger
|
修改main.ts
以配置swagger,访问文档http://localhost:3000/api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() { const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder() .setTitle('NestJS学习') .setDescription('NestJS学习API文档') .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document);
await app.listen(3000); }
|
在main.ts
文件中相当于初始化swagger,具体的接口描述可以使用swagger提供的装饰器在具体的接口中使用
1 2 3 4 5 6 7 8 9 10 11 12
| import { ApiTags } from '@nestjs/swagger';
@ApiTags('user') @Controller('user') export class UserController { constructor(private readonly userService: UserService) {}
@Get() getUsers() { return this.userService.getUsers() } }
|
错误处理
创建文件src/filters/http-exception.filter.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common'; import { Response } from 'express';
@Catch() export class HttpExceptionFilter implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>();
response.status(500).json({ message: 'Internal Server Error', error: exception.message, }); } }
|
修改main.ts
以全局注册过滤器
1
| app.useGlobalFilters(new HttpExceptionFilter());
|
D2
- 用户认证(JWT)
- 中间件实战(日志级联)
- 环境配置
- 单元测试入门
- 部署优化(生产环境配置)
JWT
1 2
| npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt npm install @types/passport-jwt @types/bcrypt --save-dev
|
创建认证模块

实现用户注册(需要创建和数据库对应的entity),如:
src/user/entities/user.entity.ts
1 2 3 4 5
| export class User { id: number username: string password: string }
|
修改auth.service.ts
实现登录相关的业务逻辑(获取用户数据,实现成功登录token返回)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt' import * as bcrypt from 'bcrypt' import { UserService } from '../user/user.service';
@Injectable() export class AuthService { constructor(private userService: UserService, private jwtService: JwtService ){}
async validateUser(username: string, pass: string): Promise<any> { const user = await this.userService.findOne(username) if (user && (await bcrypt.compare(pass, user.passwd))) { const { passwd, ...result} = user return result } return null }
async login(user: any) { const payload = { username: user.username, sub: user.id } return { access_token: this.jwtService.sign(payload) } } }
|
修改auth.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { Controller, Body, Post, UnauthorizedException } from '@nestjs/common'; import { AuthService } from './auth.service';
@Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {}
@Post('login') async login(@Body() loginDto: { username: string; password: string }) { const user = await this.authService.validateUser(loginDto.username, loginDto.password) if (!user) throw new UnauthorizedException(); return this.authService.login(user) } }
|
修改auth.module.ts
文件配置JWT模块
1 2 3 4 5 6 7 8 9 10 11 12
| import { JwtModule } from '@nestjs/jwt';
@Module({ imports: [ JwtModule.register({ secret: 'your_secret_key', signOptions: { expiresIn: '1h' }, }), ], providers: [AuthService], }) export class AuthModule {}
|
一般也是使用环境变量进行配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { JwtModule } from '@nestjs/jwt'; import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({ imports: [ JwtModule.registerAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (config: ConfigService) => ({ secret: config.get("JWT_SECRET"), signOptions: { expiresIn: '1h' }, }) }), ], providers: [AuthService], }) export class AuthModule {}
|
中间件
中间件一般会保存到如:
src/middleware/logger.middleware.ts
1 2 3 4 5 6 7 8 9 10
| import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express';
@Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); } }
|
修改app.module.ts
中全局应用中间件
1 2 3 4 5 6 7 8 9 10
| import { NestModule, MiddlewareConsumer } from '@nestjs/common'; import { LoggerMiddleWare } from './middleware/logger.middleware';
export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleWare) .forRoutes('*'); } }
|
环境配置
通过.env
文件来区分不同环境
1
| npm install @nestjs/config
|
修改app.module.ts
进行配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config' import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { UserModule } from './user/user.module'; import { AuthModule } from './auth/auth.module';
@Module({ imports: [ UserModule, AuthModule, ConfigModule.forRoot({ isGlobal: true, envFilePath: `.env.${process.env.NODE_ENV || 'development'}` }), TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (config: ConfigService) => ({ type: 'mysql', host: config.get('DB_HOST'), port: Number(config.get('DB_PORT')), username: config.get('DB_USER'), password: config.get('DB_PASSWORD'), database: config.get('DB_DATABASE'), entities: [User], synchronize: true, }), }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
|
- 多数支持动态配置的都会
useFactory
有这个方法
本地创建两个不同的环境变量文件:.env.development
和 .env.production
,尽量确保文件名和NODE_ENV
的值对应
1 2 3 4 5 6 7 8 9
| # .env.development NODE_ENV=development DATABASE_URL=sqlite:./dev.db JWT_SECRET=development_secret
# .env.production NODE_ENV=production DATABASE_URL=mysql://user:password@localhost:3306/prod_db JWT_SECRET=production_secret
|
也可以在运行的时候指定环境变量:
1
| "start:dev": "NODE_ENV=development nest start --watch"
|
单元测试
单元测试文件在运行nest g resource xx
的时候会自动创建,也可手动创建
例如测试UserService
,修改user.service.spec.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { Test, TestingModule } from '@nestjs/testing'; import { UserService } from './user.service';
describe('UserService', () => { let service: UserService;
beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [UserService], }).compile();
service = module.get<UserService>(UserService); });
it('should be defined', () => { expect(service).toBeDefined(); });
it('show return users', () => { expect(service.getUsers()).toEqual([ { id: 1, name: '小明', age: 18, passwd: '123' }, { id: 2, name: '小白', age: 19, passwd: '123' } ]) }) });
|
运行测试
部署优化
运行npm run start
的时候已经是生产环境,除此之外还可以打包之后再部署(可使用swc、pm2等进行打包)
安装swc
1
| npm install --save-dev @swc/cli @swc/core
|
在项目根目录下创建.swc
文件,并且添加打包命令到 npm-script,如"build": "swc src -d dist"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { "jsc": { "parser": { "syntax": "typescript", "tsx": false, "decorators": true, "dynamicImport": true }, "target": "es2020", "keepClassNames": true, "transform": { "legacyDecorator": true, "decoratorMetadata": true } }, "module": { "type": "commonjs" } }
|
使用docker进行容器部署,在项目根目录创建Dockerfile
文件(必须大写开头)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| FROM node:16
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/main.js"]
|
构建镜像和运行容器
1 2 3 4 5
| # 构建 Docker 镜像 docker build -t my-nest-app .
# 运行 Docker 容器 docker run -p 3000:3000 my-nest-app
|
使用nginx进行反向代理(处理跨域的好方法),当访问yourdomain.com/api
时代理到本地的3000
端口
1 2 3 4 5 6 7 8 9 10 11
| server { listen 80; server_name yourdomain.com;
location /api { proxy_pass http://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
|
D3
- 权限守卫
- 文件上传与静态资源托管
- WebSocket实时通信
权限守卫
创建角色装饰器,新建文件src/auth/roles.decorator.ts
:
1 2 3
| import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
|
实现守卫,新建文件src/auth/roles.guard.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common' import { Reflector } from '@nestjs/core'
@Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.get<string[]>( 'roles', context.getHandler(), ); if (!requiredRoles) return true;
const request = context.switchToHttp().getRequest(); const user = request.user; return requiredRoles.some((role) => user.roles?.includes(role)); } }
|
在控制器中使用守卫,如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { Roles } from '../auth/roles.decorator'; import { RolesGuard } from '../auth/roles.guard';
@Controller('users') @UseGuards(RolesGuard) export class UserController { @Get('admin') @Roles('admin') getAdminData() { return { message: 'Admin data' }; } }
|
文件上传与静态资源托管
文件上传
需要外安装依赖
1
| npm install @nestjs/platform-express multer
|
1
| npm install @types/multer --save-dev
|
创建文件上传控制器:src/files/files.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { Controller, Post, UseInterceptors, UploadedFile } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { diskStorage } from 'multer'; import { extname } from 'path';
@Controller('files') export class FilesController { @Post('upload') @UseInterceptors(FileInterceptor('file', { storage: diskStorage({ destination: './uploads', filename: (req, file, callback) => { const randomName = Array(32).fill(null).map(() => Math.round(Math.random() * 16).toString(16)).join(''); callback(null, `${randomName}${extname(file.originalname)}`); }, }), })) uploadFile(@UploadedFile() file: Express.Multer.File) { return { url: `/static/${file.filename}`, }; } }
|
FileInterceptor
:文件上传拦截器(第一个参数是文件字段名(formData
中的键名)
托管静态文件,在main.ts
中配置
1 2 3 4 5 6 7 8 9 10 11 12
| import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { join } from 'path';
async function bootstrap() { const app = await NestFactory.create(AppModule); app.use('/static', express.static(join(__dirname, '..', 'uploads'))); await app.listen(3000); } bootstrap();
|
静态资源托管
除了上面的方式外,常用的处理方法还有
- 使用 nginx 作为静态文件服务器
- 使用 CDN 服务(阿里云OOS + CDN、腾讯云COS + CDN)
- nestjs 自带的
ServeStaticModule
模块
在 nginx 中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| server { listen 80; server_name yourdomain.com;
location /static/ { alias /var/www/static/; }
location / { proxy_pass http://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
|
使用ServeStaticModule
,预先安装:
1
| npm install @nestjs/serve-static
|
在app.module.ts
中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { Module } from '@nestjs/common'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path';
@Module({ imports: [ ServeStaticModule.forRoot({ rootPath: '/var/www/static', serveRoot: '/static', }), ], }) export class AppModule {}
|