本篇展示用 Drizzle 替代使用 Prisma中的 Prisma 配置。如果 Drizzle 是你选用的 ORM,可以参考这篇。
只有 CoreAdapter 和 CredentialsAdapter 会改动,brkpt-auth/ 中的其他部分不受影响。
- 已完成开始使用的项目
- 一个 PostgreSQL 数据库
安装 Drizzle
Section titled “安装 Drizzle”pnpm add drizzle-orm pgpnpm add -D drizzle-kit @types/pgnpm install drizzle-orm pgnpm install -D drizzle-kit @types/pgyarn add drizzle-orm pgyarn add -D drizzle-kit @types/pg配置数据库连接
Section titled “配置数据库连接”把 DATABASE_URL 添加到已有的 .env 文件中:
DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/DB_NAME?schema=public"定义 schema
Section titled “定义 schema”文件夹src/
文件夹db/
- schema.ts
import { pgTable, serial, text } from 'drizzle-orm/pg-core';
export const users = pgTable('users', { id: serial('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull().unique(), password: text('password').notNull(),});
export type User = typeof users.$inferSelect;brkpt-auth 并不要求这个具体的表结构。你的适配器决定哪些字段用于 JWT 载荷、密码校验和安全的用户响应。
创建 Drizzle client
Section titled “创建 Drizzle client”文件夹src/
文件夹db/
- db.module.ts
import { Module } from '@nestjs/common';import { ConfigModule, ConfigService } from '@nestjs/config';import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres';import { Pool } from 'pg';
import * as schema from './schema';
export const DRIZZLE = Symbol('DRIZZLE');export type DrizzleDb = NodePgDatabase<typeof schema>;
@Module({ imports: [ConfigModule], providers: [ { provide: DRIZZLE, inject: [ConfigService], useFactory: (config: ConfigService): DrizzleDb => { const pool = new Pool({ connectionString: config.getOrThrow('DATABASE_URL'), }); return drizzle(pool, { schema }); }, }, ], exports: [DRIZZLE],})export class DbModule {}配置 drizzle-kit
Section titled “配置 drizzle-kit”import { defineConfig } from 'drizzle-kit';
export default defineConfig({ schema: './src/db/schema.ts', out: './drizzle', dialect: 'postgresql', dbCredentials: { url: process.env.DATABASE_URL!, },});把 schema 推送到数据库:
pnpm drizzle-kit pushnpx drizzle-kit pushyarn drizzle-kit push重写 CoreAdapter
Section titled “重写 CoreAdapter”import { Inject, Injectable } from '@nestjs/common';import { eq } from 'drizzle-orm';
import { DRIZZLE, type DrizzleDb } from '../../db/db.module';import { type User, users } from '../../db/schema';import { CorePort } from '../features/core/core.port';import { AuthJwtPayload } from './types';
@Injectable()export class CoreAdapter implements CorePort<User> { constructor(@Inject(DRIZZLE) private readonly db: DrizzleDb) {}
mapUserToJwtPayload(user: User): AuthJwtPayload { return { sub: user.id, email: user.email, }; }
shrinkJwtPayload(payload: AuthJwtPayload): Record<string, unknown> { return { sub: payload.sub, }; }
async findUserByJwtPayload(payload: AuthJwtPayload): Promise<User | null> { const [user] = await this.db .select() .from(users) .where(eq(users.id, payload.sub)); return user ?? null; }
toSafeUser(user: User): Record<string, unknown> { const { password: _password, ...safe } = user; return safe; }
extractUserIdFromJwtPayload(payload: AuthJwtPayload): number { return payload.sub; }}重写 CredentialsAdapter
Section titled “重写 CredentialsAdapter”import { Inject, Injectable } from '@nestjs/common';import * as bcrypt from 'bcrypt';import { eq } from 'drizzle-orm';
import { DRIZZLE, type DrizzleDb } from '../../db/db.module';import { type User, users } from '../../db/schema';import { CredentialsPort } from '../features/credentials/credentials.port';import { SignInDto } from '../features/credentials/dto/sign-in.dto';import { SignUpDto } from '../features/credentials/dto/sign-up.dto';
@Injectable()export class CredentialsAdapter implements CredentialsPort<User> { constructor(@Inject(DRIZZLE) private readonly db: DrizzleDb) {}
async findUserByDto(dto: SignInDto | SignUpDto): Promise<User | null> { const [user] = await this.db .select() .from(users) .where(eq(users.email, dto.email)); return user ?? null; }
validatePassword(user: User, dto: SignInDto): Promise<boolean> { return bcrypt.compare(dto.password, user.password); }
async createUser(dto: SignUpDto): Promise<User> { const password = await bcrypt.hash(dto.password, 10); const [user] = await this.db .insert(users) .values({ name: dto.name, email: dto.email, password }) .returning(); return user; }
extractUserIdFromUser(user: User): number { return user.id; }}接入 DbModule
Section titled “接入 DbModule”在 brkpt-auth.module.ts 中,把内存实现的 UserModule 替换成 DbModule:
import { UserModule } from '../user/user.module';import { DbModule } from '../db/db.module';
@Module({ imports: [UserModule], imports: [DbModule], controllers: [...features.flatMap((f) => f.controllers)], providers: [...features.flatMap((f) => f.providers)],})export class BrkptAuthModule { // ...}现在可以移除开始使用中创建的 src/user/ 文件夹了,它的唯一作用就是提供内存 repository。
启动应用,按开始使用中的方式通过 /auth/sign-up 注册。创建的用户现在通过 Drizzle 存储在 PostgreSQL 中,重启服务后依然存在。