Drizzle-Orm + mysql + Nest 快速构建crud 体验下一代orm魅力

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
  1. 安装drizzle-orm 相关
csharp 复制代码
pnpm add drizzle-orm mysql2 安装mysql相关
pnpm add -D drizzle-kit 安装drizzle 相关
  1. 使用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;
    }
  }
} 
  1. 创建drizzle modules
python 复制代码
import { Module } from '@nestjs/common';
import { DrizzleService } from './drizzle.service';

@Module({
  providers: [DrizzleService],
  exports: [DrizzleService],
})
export class DbModule { } 
  1. 在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 { }
  1. 创建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

  1. 现在一切准备工作都就绪了,准备去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`;
  }
}
  1. 接入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验证部分

  1. 添加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;
  }
}
  1. 创建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同步表

  1. pnpm drizzle-kit migrate
  2. pnpm drizzle-kit generate
  3. pnpm drizzle-kit push 执行完mysql迁移后会出现下图的文件 最后一个简单的drizzle-orm crud 服务就在nestjs中跑起来了。

相关代码 gitee.com/kang8413316...

clone 代码到本地,修改env为自己的数据库地址就行

ini 复制代码
# 数据库连接配置
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=数据库密码
DB_NAME=数据库名称
相关推荐
打野赵怀真10 分钟前
React Hooks 的优势和使用场景
前端·javascript
xmyLydia14 分钟前
我做了一个代码生成器:Spring Boot + Angular 全自动构建
后端
HaushoLin14 分钟前
ERR_PNPM_DLX_NO_BIN No binaries found in tailwindcss
前端·vue.js·css3·html5
Lafar15 分钟前
Widget 树和 Element 树和RenderObject树是一一 对应的吗
前端
小桥风满袖16 分钟前
炸裂,前端神级动效库合集
前端·css
匆叔17 分钟前
Tauri 桌面端开发
前端·vue.js
1_2_3_17 分钟前
react-antd-column-resize(让你的table列可以拖拽列宽)
前端
Lafar17 分钟前
Flutter和iOS混合开发
前端·面试
九龙湖兔兔18 分钟前
pnpm给插件(naiveUI)打补丁
前端·架构