
2025年了我终于狠下心开始研究下nestjs 了,然后发现大多数项目要么就是typeorm 要么就是 prisma,drizzle-orm 寥寥无几接来下我将使用drizzle-orm 快速实现curd 官网链接orm.drizzle.team/docs/get-st...
1.首先使用nestjs cli 创建nest项目 下面是我项目的整体结构

arduino
nest new you-project-name
- 安装drizzle-orm 相关
csharp
pnpm add drizzle-orm mysql2 安装mysql相关
pnpm add -D drizzle-kit 安装drizzle 相关
- 使用drizzle 创建mysql链接池
typescript
import { Injectable, OnModuleInit } from '@nestjs/common';
import { drizzle } from 'drizzle-orm/mysql2';
import * as dotenv from 'dotenv';
import * as mysql from 'mysql2/promise';
import * as schema from '../db/schema';
import { Logger } from 'drizzle-orm/logger';
dotenv.config();
@Injectable()
export class DrizzleService implements OnModuleInit {
public db;
async onModuleInit() {
try {
// 使用 mysql2 的正确方式创建连接池
const pool = mysql.createPool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
// 添加额外配置以提高稳定性
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 测试连接
await pool.getConnection();
console.log('数据库连接成功!');
// 创建一个自定义的日志记录器
const customLogger = {
logQuery: (query: string, params: unknown[]) => {
console.log('Query:', query);
console.log('Params:', params);
}
};
// 使用 drizzle 创建数据库实例,并传递日志记录器开启logger 可以看到请求接口的原生sql
this.db = drizzle(pool, { logger: customLogger, });
} catch (error) {
console.error('数据库连接错误:', error);
throw error;
}
}
}
- 创建drizzle modules
python
import { Module } from '@nestjs/common';
import { DrizzleService } from './drizzle.service';
@Module({
providers: [DrizzleService],
exports: [DrizzleService],
})
export class DbModule { }
- 在app module 中导入drizzleorm 相关module
typescript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DbModule } from './db/db.module'; // 倒入drizzle-orm
import { PostsModule } from './common/posts/posts.module';
@Module({
imports: [DbModule, PostsModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
- 创建posts 模块完成crud
css
使用nestjs g resource posts
┌───────────────┬─────────────┬──────────────────────────────────────────────┐
│ name │ alias │ description │
│ application │ application │ Generate a new application workspace │
│ class │ cl │ Generate a new class │
│ configuration │ config │ Generate a CLI configuration file │
│ controller │ co │ Generate a controller declaration │
│ decorator │ d │ Generate a custom decorator │
│ filter │ f │ Generate a filter declaration │
│ gateway │ ga │ Generate a gateway declaration │
│ guard │ gu │ Generate a guard declaration │
│ interceptor │ itc │ Generate an interceptor declaration │
│ interface │ itf │ Generate an interface │
│ library │ lib │ Generate a new library within a monorepo │
│ middleware │ mi │ Generate a middleware declaration │
│ module │ mo │ Generate a module declaration │
│ pipe │ pi │ Generate a pipe declaration │
│ provider │ pr │ Generate a provider declaration │
│ resolver │ r │ Generate a GraphQL resolver declaration │
│ resource │ res │ Generate a new CRUD resource │
│ service │ s │ Generate a service declaration │
│ sub-app │ app │ Generate a new application within a monorepo │
└───────────────┴─────────────┴──────────────────────────────────────────────┘
我这里选的是rest api
- 现在一切准备工作都就绪了,准备去crud起飞
less
先实现posts controller 部分
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { PostsService } from './posts.service';
import { UpdatePostDto } from './dto/update-post.dto';
import { CreatePostDto, CreatePostType, ZodPostValidationPipe } from './dto/create-post.dto';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ZodValidationPipe } from 'nestjs-zod';
@ApiTags('posts')
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) { }
@Post()
@ApiOperation({ summary: '创建帖子' })
@ApiResponse({ status: 200, description: '帖子创建成功' })
@ApiResponse({ status: 400, description: '数据验证失败' })
create(@Body(new ZodPostValidationPipe()) createPostDto: CreatePostDto) {
return this.postsService.create(createPostDto);
}
@Get()
findAll() {
return this.postsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.postsService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
return this.postsService.update(+id, updatePostDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.postsService.remove(+id);
}
}
typescript
接着实现posts service服务 下面就已经具备一个简单的插入跟查询的接口服务了
import { insertPostSchema, posts, users } from './../../db/schema';
import { BadRequestException, Injectable } from '@nestjs/common';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { DrizzleService } from '../../db/drizzle.service';
import { and, eq, sql, getTableColumns } from 'drizzle-orm';
import { MySqlRawQueryResult } from 'drizzle-orm/mysql2';
import { QueryBuilder } from 'drizzle-orm/mysql-core';
import { createSelectSchema } from 'drizzle-zod';
import { ZodError } from 'zod';
@Injectable()
export class PostsService {
constructor(
private readonly drizzle: DrizzleService,
) { }
async create(postData: unknown) {
try {
// 插入数据
const result = await this.drizzle.db
.insert(posts)
.values(postData);
// 返回创建的帖子
if (result.insertId) {
return this.findOne(Number(result.insertId));
}
return { message: '创建帖子成功', id: result.insertId };
} catch (error) {
if (error instanceof ZodError) {
const formattedErrors = error.errors.map(err =>
`${err.path.join('.')}: ${err.message}`
).join(', ');
throw new BadRequestException(`数据验证失败: ${formattedErrors}`);
}
console.error('创建帖子时出错:', error);
throw new BadRequestException('创建帖子失败');
}
}
async findAll() {
// const result = await this.drizzle.db.select().from(posts).leftJoin(users, eq(
// users.userId, posts.authorId
// ))
const result = await this.drizzle.db.select().from(posts)
// 原生sql
// const statement = sql`select * from ${users} where ${users.userId} = ${1}`;
// const res: MySqlRawQueryResult = await this.drizzle.db.execute(statement);
// return res
// 使用列映射
// const { title, content, ...rest } = getTableColumns(posts)
// return await this.drizzle.db.select({ title, content, ...rest }).from(posts);
// query builder
// const qb = new QueryBuilder();
// const query = qb.select().from(users).where(eq(users.userId, 1));
// const { sql, params } = query.toSQL();
// console.log(query)
return result
}
findOne(id: number) {
return ''
}
update(id: number, updatePostDto: UpdatePostDto) {
return `This action updates a #${id} post`;
}
remove(id: number) {
return `This action removes a #${id} post`;
}
}
- 接入swagger文档,接入nestjs-zod 验证数据
javascript
pnpm i @nestjs/swagger
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; // 倒入swagger相关依赖
import { patchNestJsSwagger } from 'nestjs-zod'; // zod相关
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 必须在创建 Swagger 文档之前调用这个方法
patchNestJsSwagger();
const config = new DocumentBuilder()
.setTitle('API 文档')
.setDescription('使用 Zod 验证的 API')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-docs', app, document);
app.enableCors(); // 启用CORS
await app.listen(3000);
console.log(`应用程序运行在: http://localhost:3000`);
}
bootstrap();

ok了swagger 文档跑起来了 接下来我们去添加dto验证部分
- 添加dto验证及swagger文档添加参数注解
typescript
// posts/dto/create-post.dto.ts
import { z } from 'zod';
import { ApiProperty } from '@nestjs/swagger';
import { BadRequestException, PipeTransform } from '@nestjs/common';
// Zod schema
export const createPostSchema = z.object({
title: z.string().min(1, "标题不能为空"),
content: z.string().min(1, "内容不能为空"),
authorId: z.number().int().min(1, "作者ID必须为正整数")
});
// 类型
export type CreatePostType = z.infer<typeof createPostSchema>;
// Swagger文档用DTO类
export class CreatePostDto {
@ApiProperty({
description: '帖子标题',
example: '示例标题',
minLength: 1
})
title: string;
@ApiProperty({
description: '帖子内容',
example: '这是帖子内容',
minLength: 1
})
content: string;
@ApiProperty({
description: '作者ID',
example: 1,
type: 'integer',
minimum: 1
})
authorId: number;
}
// 验证管道
export class ZodPostValidationPipe implements PipeTransform {
transform(value: unknown) {
try {
return createPostSchema.parse(value);
} catch (error) {
throw new BadRequestException(`数据验证失败: ${this.formatError(error)}`);
}
}
private formatError(error) {
return error.errors.map(e =>
`${this.getFieldName(e.path[0])}: ${this.translateMessage(e.message)}`
).join(', ');
}
private getFieldName(field: string): string {
const map = { title: '标题', content: '内容', authorId: '作者ID' };
return map[field] || field;
}
private translateMessage(msg: string): string {
if (msg === 'Required') return '必填项';
return msg;
}
}
- 创建drizzle-orm mysql数据库映射同步数据表
css
import {
mysqlTable,
int,
varchar,
char,
timestamp,
datetime,
primaryKey,
mysqlEnum,
text,
uniqueIndex,
index
} from 'drizzle-orm/mysql-core';
import { sql } from 'drizzle-orm';
import { relations } from 'drizzle-orm';
import { z } from 'zod';
// 用户表定义
export const users = mysqlTable('users', {
userId: int('user_id').primaryKey().autoincrement().notNull(),
userName: varchar('user_name', { length: 30 }).notNull(),
phonenumber: varchar('phonenumber', { length: 11 }).notNull().default(''),
sex: char('sex', { length: 1 }).notNull().default('0'),
password: varchar('password', { length: 200 }).notNull().default(''),
createBy: varchar('create_by', { length: 64 }).notNull().default(''),
createTime: datetime('create_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
updateBy: varchar('update_by', { length: 64 }).notNull().default(''),
updateTime: datetime('update_time', { fsp: 6 }).default(sql`CURRENT_TIMESTAMP(6)`),
});
// 用户表关系
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
// 帖子表定义
export const posts = mysqlTable('posts', {
id: int('posts_id').primaryKey().autoincrement().notNull(),
title: varchar('posts_title', { length: 30 }).notNull(),
content: varchar('posts_content', { length: 225 }).notNull(),
authorId: int('author_id').notNull(),
});
// 帖子表关系
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.userId],
}),
}));
// 使用纯 Zod 定义验证 schema
export const insertPostSchema = z.object({
title: z.string({
required_error: "标题是必填项",
invalid_type_error: "标题必须是字符串"
}).min(1, "标题不能为空").max(30, "标题最多30个字符"),
content: z.string({
required_error: "内容是必填项",
invalid_type_error: "内容必须是字符串"
}).min(1, "内容不能为空").max(225, "内容最多225个字符"),
authorId: z.number({
required_error: "作者ID是必填项",
invalid_type_error: "作者ID必须是数字"
}).int("作者ID必须是整数").min(1, "作者ID必须是正整数"),
});
export const insertUserSchema = z.object({
userName: z.string().min(2, "用户名至少需要2个字符").max(30, "用户名最多30个字符"),
phonenumber: z.string().regex(/^1[3-9]\d{9}$/, "手机号格式不正确"),
sex: z.enum(['0', '1', '2'], {
errorMap: () => ({ message: "性别必须是:0-未知,1-男,2-女" })
}),
password: z.string().min(6, "密码至少6个字符").max(20, "密码最多20个字符"),
});
// 类型导出
export type InsertUser = z.infer<typeof insertUserSchema>;
export type InsertPost = z.infer<typeof insertPostSchema>
export type CreatePostDto = z.infer<typeof insertPostSchema>;
使用dirzzle-orm 迁移mysql同步表
- pnpm drizzle-kit migrate
- pnpm drizzle-kit generate
- pnpm drizzle-kit push 执行完mysql迁移后会出现下图的文件
最后一个简单的drizzle-orm crud 服务就在nestjs中跑起来了。
clone 代码到本地,修改env为自己的数据库地址就行
ini
# 数据库连接配置
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=数据库密码
DB_NAME=数据库名称