NestJS 实战:连接 MySQL、数据操作与接口文档配置
连接MySQL
路由已生效,作为后端项目,必须引入数据库功能,否则与开发静态页面无异。MySQL作为实际项目中的主流选择,本文将详细介绍其安装、连接和使用方法,并分享可能遇到的问题。已有经验的开发者可跳过此部分。
数据库安装
若本地未安装MySQL且无云数据库可用,请先通过官网下载MySQL Community Server对应版本。推荐使用Navicat for MySQL或SQLyog等可视化工具管理数据库,选择习惯使用的即可。
TypeORM连接数据库
前置知识
ORM(对象关系映射)技术将关系型数据库的表结构映射为JavaScript对象。相比直接使用Node.js操作MySQL底层接口,ORM框架(如TypeORM、Sequelize、Prisma)能简化操作。例如:
原始插入语句:
connection.query(`INSERT INTO posts (title, content) VALUES ('${title}', '${content}')`, (err, data) => {
if (err) console.error(err);
else console.log(data);
});
使用TypeORM后简化为:
return await this.userRepository.save(createUserDto);
安装依赖
npm install @nestjs/typeorm typeorm mysql2 -S
官方提供两种连接方式:
方法1:环境变量配置
- 创建
.env
和.env.prod
文件存储开发/生产环境变量:
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWD=root
DB_DATABASE=blog
注意:.env.prod
应加入.gitignore
- 创建
config/env.ts
读取配置:
import * as fs from 'fs';
import * as path from 'path';
const isProd = process.env.NODE_ENV === 'production';
function parseEnv() {
const localEnv = path.resolve('.env');
const prodEnv = path.resolve('.env.prod');
if (!fs.existsSync(localEnv) && !fs.existsSync(prodEnv)) {
throw new Error('缺少环境配置文件');
}
const filePath = isProd && fs.existsSync(prodEnv) ? prodEnv : localEnv;
return { path: filePath };
}
export default parseEnv();
- 在
app.module.ts
配置连接:
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'rootroot',
database: 'nestjs',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
],
})
export class AppModule {}
推荐使用@nestjs/config
管理环境变量。
方法2:ormconfig.json配置
- 创建
ormconfig.json
:
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "blog",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}
- 在
app.module.ts
中直接调用:
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forRoot()],
})
export class AppModule {}
更多连接方式请参考TypeORM官方文档。至此,数据库连接已成功建立。
数据操作
创建用户表
通过TypeORM的实体映射机制创建数据库表。在user/entities/user.entity.ts
中定义用户实体:
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity("user")
export class UserEntity {
@PrimaryGeneratedColumn()
id: number; // 自动生成的主键
@Column({ length: 20 })
username: string;
@Column({ length: 20 })
password: string;
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
create_time: Date;
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
update_time: Date;
}
保存后系统会自动创建user表。
CRUD操作
模块配置
在user.module.ts
中导入数据库相关模块:
import { Module } from "@nestjs/common";
import { UserService } from "./user.service";
import { UserController } from "./user.controller";
import { UserEntity } from "./entities/user.entity";
import { TypeOrmModule } from "@nestjs/typeorm";
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
创建用户
定义接收参数DTO:
// create-user.dto.ts
export class CreateUserDto {
username: string;
password: string;
}
服务层实现:
// user.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserEntity } from './entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserEntity)
private userRepository: Repository<UserEntity>,
) {}
async create(createUserDto: CreateUserDto) {
return await this.userRepository.save(createUserDto);
}
}
查询用户
基础查询:
async findAll() {
return await this.userRepository.find();
}
带条件查询:
async findAll() {
return await this.userRepository.find({ where: { id: 1 } });
}
更新用户
控制器接收参数:
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update(+id, updateUserDto);
}
服务层实现(使用TypeORM查询构造器):
async update(id: number, updateUserDto: UpdateUserDto) {
const qb = await this.userRepository.createQueryBuilder();
return await qb.update().set(updateUserDto).where({ id }).execute();
}
删除用户
async remove(id: number) {
const qb = await this.userRepository.createQueryBuilder();
return await qb.delete().where({ id }).execute();
}
统一接口返回格式
异常过滤器
创建HTTP异常过滤器:
// http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response.status(status).json({
code: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
在main.ts中注册:
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { HttpExceptionFilter } from "./common/filter/http-exception/http-exception.filter";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
响应拦截器
创建响应拦截器:
// transform.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next
.handle()
.pipe(map((data) => ({ code: 200, data, describe: '请求成功' })));
}
}
在main.ts中注册:
app.useGlobalInterceptors(new TransformInterceptor());
配置接口文档 Swagger
下面介绍如何使用Swagger高效地编写接口文档。选择Swagger的原因有二:一是Nest.js提供了专用模块支持,二是它能精确展示每个字段的含义(前提是注解写到位)。
安装配置
首先安装依赖:
npm install @nestjs/swagger swagger-ui-express -S
本文使用的是5.1.4版本,注意与4.x.x版本存在API差异。
在main.ts中配置Swagger文档信息:
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// Swagger配置
const config = new DocumentBuilder()
.setTitle('管理后台')
.setDescription('管理后台接口文档')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
await app.listen(3000);
}
bootstrap();
配置完成后,访问 http://localhost:3000/docs 即可查看生成的文档。
接口标签分类
可以根据Controller进行分类,只需添加@ApiTags
注解:
import { ApiTags } from '@nestjs/swagger';
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
@ApiTags("文章")
@Controller('post')
export class PostsController {...}
对所有Controller添加分类标签后,刷新文档即可看到分类效果。
接口说明
通过@ApiOperation
装饰器为每个接口添加说明,使接口功能一目了然:
import { ApiTags, ApiOperation } from '@nestjs/swagger';
export class PostsController {
@ApiOperation({ summary: '创建文章' })
@Post()
async create(@Body() post) {....}
@ApiOperation({ summary: '获取文章列表' })
@Get()
async findAll(@Query() query): Promise<PostsRo> {...}
}