我正在开发 DocFlow,它是一个完整的 AI 全栈协同文档平台。该项目融合了多个技术栈,包括基于 Tiptap 的富文本编辑器、NestJs 后端服务、AI 集成功能和实时协作。在开发过程中,我积累了丰富的实战经验,涵盖了 Tiptap 的深度定制、性能优化和协作功能的实现等核心难点。
如果你对 AI 全栈开发、Tiptap 富文本编辑器定制或 DocFlow 项目的完整技术方案感兴趣,欢迎加我微信 yunmz777 进行私聊咨询,获取详细的技术分享和最佳实践。
对很多想从前端转向全栈的同学来说,NestJS 是一个非常友好的选择:语法风格接近前端熟悉的 TypeScript,又借鉴了后端常见的模块化与依赖注入模式,可以在保留前端开发舒适度的同时,比较轻松地搭起一套“像样的后端服务”。
如果目标不仅是写几个简单接口,而是要扛起鉴权、实时协同、AI 能力,还要自己搭建和维护数据库、缓存、搜索、对象存储、监控这些基础设施,那么就需要一套偏“自托管、自运维”的 NestJS 技术栈组合。
这里推荐的技术栈选择标准主要有三点:
- 和
NestJS 生态高度契合,有成熟的官方或社区集成;
- 能够支撑中大型文档、知识类应用的性能和复杂度,比如协同编辑、全文检索、RAG、任务队列等;
- 个人或小团队也能在合理成本内自行部署和维护,比如使用
Prisma + MySql、Redis、Elasticsearch、MinIO 这类开源组件。
接下来就按照从框架、运行时到数据层、搜索、队列、AI 的顺序,分享一套适合前端转全栈使用的 NestJS 核心技术栈,代码也尽量贴近实战,方便直接改造复用。
NestJS 和 TypeScript
NestJS 是整个后端的“框架壳子”,负责模块划分、依赖注入、装饰器等基础能力;TypeScript 则是地基,把很多原本要靠经验避免的错误提前到编译期发现,例如 Controller 入参、Service 返回值、配置对象等。
实际项目中,一般会开启严格模式,再结合全局的 ValidationPipe 和 class-transformer,把请求里的原始 JSON 自动转换成带类型的 DTO 实例,从而减少 any 的使用、避免脏数据流入业务层。
基本使用案例:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
}),
);
await app.listen(3000);
}
bootstrap();
Fastify 和 @nestjs/platform-fastify
Fastify 是一个高性能 HTTP 引擎,相比 Express 更轻量、更适合高并发场景;@nestjs/platform-fastify 负责把 Fastify 接进 Nest,让你在业务层依然只写标准的 Controller、Guard、Interceptor 等。
搭配 @fastify/helmet、@fastify/rate-limit、@fastify/cookie、@fastify/secure-session、@fastify/multipart、@fastify/static,可以在统一框架里完成安全头、限流、会话、上传和静态资源托管。
基本使用案例:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
await app.listen(3000, '0.0.0.0');
}
bootstrap();
@nestjs/config
@nestjs/config 是 Nest 官方的配置模块,用来统一管理多环境配置。思路很简单:所有数据库地址、第三方秘钥、开关配置等,都通过 ConfigService 读取,而不是在代码里到处散落 process.env。
推荐的做法是为不同领域写独立的配置工厂,然后在对应模块中注入使用。
基本使用案例:
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
],
})
export class AppModule {}
// some.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class SomeService {
constructor(private readonly configService: ConfigService) {}
getDbUrl() {
return this.configService.get<string>('DATABASE_URL');
}
}
Passport 和 JWT
@nestjs/passport 和 @nestjs/jwt 提供了一整套认证基础设施:Passport 统一管理各种认证策略(本地、JWT、OAuth 等),JWT 提供无状态令牌,让前后端分离、多端访问更容易管理。
常见流程是:通过本地策略验证用户名密码,登录成功后签发 JWT,后续请求通过 AuthGuard('jwt') 验证;接入第三方登录(如 GitHub)时,仅需增加新的 Passport 策略。
基本使用案例:
// auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: 'secret', // 实际项目请从 ConfigService 读取
}),
],
providers: [JwtStrategy],
exports: [PassportModule, JwtModule],
})
export class AuthModule {}
// some.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller('profile')
export class ProfileController {
@UseGuards(AuthGuard('jwt'))
@Get()
getProfile() {
return { ok: true };
}
}
WebSocket 和 Socket.IO
当系统需要即时通知、在线状态、协同编辑时,可以使用 @nestjs/websockets 搭配 @nestjs/platform-socket.io 和 socket.io。Gateway 充当“长连接入口”,负责管理连接、房间和事件。
更高级的协同场景中,还可以引入 hocuspocus、yjs、y-prosemirror 来负责文档协同算法,Nest 只需要负责连接管理和权限校验。
基本使用案例:
// chat.gateway.ts
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
MessageBody,
} from '@nestjs/websockets';
import { Server } from 'socket.io';
@WebSocketGateway()
export class ChatGateway {
@WebSocketServer()
server: Server;
@SubscribeMessage('message')
handleMessage(@MessageBody() data: string) {
this.server.emit('message', data);
}
}
Prisma
Prisma 通过独立的 schema.prisma 文件定义模型,并生成强类型的 PrismaClient,非常适合在 Nest 的 Service 层中使用。它把数据库迁移、数据建模、类型安全绑在一起,能明显降低 SQL 错误和字段拼写错误的概率。
在 Service 中,直接注入封装好的 PrismaService,就可以用类型安全的方式进行增删改查。
基本使用案例:
// user.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/common/prisma/prisma.service';
@Injectable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
findAll() {
return this.prisma.user.findMany();
}
}
Redis 和 ioredis
Redis 是常见的缓存与中间层,而 ioredis 是稳定好用的 Node 客户端组合。它通常用于三个方向:缓存(加速读取)、分布式协调(锁、限流、防重复)、短期数据存储(会话、任务状态等)。
在 Nest 中一般会封装一个 RedisService,对外暴露 get、set、incr 等方法,避免直接在业务里使用底层客户端。
基本使用案例:
// redis.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { Redis } from 'ioredis';
@Injectable()
export class RedisService implements OnModuleInit {
private client: Redis;
onModuleInit() {
this.client = new Redis('redis://localhost:6379');
}
async get(key: string) {
return this.client.get(key);
}
async set(key: string, value: string, ttlSeconds?: number) {
if (ttlSeconds) {
await this.client.set(key, value, 'EX', ttlSeconds);
} else {
await this.client.set(key, value);
}
}
}
BullMQ
BullMQ 是基于 Redis 的任务队列,@nestjs/bullmq 让你可以用装饰器方式定义队列和消费者。它适合承载各种耗时任务,例如大文件解析、批量导入导出、调用外部 AI 接口等。
这样可以把重任务从 HTTP 请求中剥离,避免接口超时,用户只需拿到一个“任务已受理”的 ID。
基本使用案例:
消费者(处理任务):
// task.processor.ts
import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Job } from 'bullmq';
@Processor('tasks')
export class TaskProcessor extends WorkerHost {
async process(job: Job) {
// 这里处理耗时任务
console.log('processing job', job.id, job.data);
}
}
生产者(投递任务):
// task.service.ts
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
@Injectable()
export class TaskService {
constructor(@InjectQueue('tasks') private readonly queue: Queue) {}
async createTask(payload: any) {
const job = await this.queue.add('process', payload);
return { jobId: job.id };
}
}
Elasticsearch
Elasticsearch 在文档和知识类系统中通常用作结构化搜索与日志索引引擎。通过 @elastic/elasticsearch 客户端可以在 Nest 的 Service 里封装搜索接口,对外统一暴露“搜索文档”“搜索日志”等能力。
相对数据库原生查询,Elasticsearch 更擅长复杂查询、聚合统计、模糊搜索与搜索结果排序,是提升搜索体验的关键组件。
基本使用案例:
封装搜索 Service:
// search.service.ts
import { Injectable } from '@nestjs/common';
import { Client } from '@elastic/elasticsearch';
@Injectable()
export class SearchService {
private client = new Client({ node: 'http://localhost:9200' });
async searchDocs(keyword: string) {
const res = await this.client.search({
index: 'documents',
query: {
multi_match: {
query: keyword,
fields: ['title', 'content'],
},
},
});
return res.hits.hits;
}
}
对象存储(MinIO)
当系统有大量文件(如 PDF、Word、图片、音频)时,本地磁盘很快就会吃不消,这时可以用自建的 MinIO 集群来做对象存储。它负责长期保存大文件,后端只需要关心对象名和访问地址,不必再直接管理磁盘。
在 Nest 中通常会封装一个存储 Service,对上层暴露“上传文件”“生成下载地址”等方法;同时配合 imagekit、sharp、exiftool-vendored、pdf-parse、mammoth 等,对文件做压缩、预览、元信息与文本提取等处理。
基本使用案例:
// storage.service.ts
import { Injectable } from '@nestjs/common';
import { Client } from 'minio';
@Injectable()
export class StorageService {
private client = new Client({
endPoint: 'localhost',
port: 9000,
useSSL: false,
accessKey: 'minio',
secretKey: 'minio123',
});
async upload(bucket: string, objectName: string, buffer: Buffer) {
await this.client.putObject(bucket, objectName, buffer);
return { bucket, objectName };
}
}
@nestjs/swagger
@nestjs/swagger 负责从 Controller 和 DTO 的装饰器中生成 OpenAPI 文档,让接口定义和代码实现保持同步,不再需要单独维护一份容易过期的接口文档。
在前后端分离项目中,Swagger 文档可以同时服务前端、测试与产品:前端对齐请求和响应结构,测试做接口验证,产品了解后端已有能力。
基本使用案例:
// main.ts 片段
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('API 文档')
.setDescription('服务接口说明')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
await app.listen(3000);
}
class-validator 和 class-transformer
class-validator 通过在 DTO 类属性上添加装饰器(如 IsString、IsInt、IsOptional 等)定义字段的合法规则,class-transformer 负责把原始请求 JSON 转换成 DTO 实例。配合全局 ValidationPipe,可以保证进入 Controller 的数据已经过校验和转换。
这一体系大大减少了手写 if 校验的重复劳动,同时确保错误请求在统一入口被拦截并抛出合适的 HTTP 异常。
基本使用案例:
// create-user.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(6)
password: string;
}
// user.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UserController {
@Post()
create(@Body() dto: CreateUserDto) {
return dto;
}
}
Prometheus 和 Terminus
@willsoto/nestjs-prometheus 搭配 prom-client 可以方便地暴露各种指标端点,例如 HTTP 延迟、错误率、队列堆积情况等;@nestjs/terminus 则专注于健康检查,通过多种 HealthIndicator 检查数据库、Redis、Elasticsearch、Qdrant 等依赖服务是否可用。
在生产环境下,这两者为“可观测性”打基础,使运维和开发可以快速感知和定位问题。
基本使用案例:
// health.controller.ts
import { Controller, Get } from '@nestjs/common';
import {
HealthCheck,
HealthCheckService,
TypeOrmHealthIndicator,
} from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('database'),
]);
}
}
OpenAI 和 LangChain
openai 提供与大模型交互的基础接口,而 langchain、@langchain/core、@langchain/community、@langchain/openai、@langchain/textsplitters 则把模型调用、提示模板、工具调用、长文档切片等复杂逻辑抽象成可组合的模块。对于文档工作流类项目,这一层就是从“普通文档系统”升级为“智能文档系统”的关键。
在 Nest 中,通常会拆出一个 AI 模块,把“向量检索 + RAG + 模型调用”封装在 Service 里,再通过少量 HTTP 接口暴露出问答、总结、润色等能力。
基本使用案例:
// ai.service.ts
import { Injectable } from '@nestjs/common';
import OpenAI from 'openai';
@Injectable()
export class AiService {
private client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async chat(prompt: string) {
const res = await this.client.chat.completions.create({
model: 'gpt-4.1-mini',
messages: [{ role: 'user', content: prompt }],
});
return res.choices[0]?.message?.content ?? '';
}
}
总结
如果只是想把接口“跑起来”,也许只要 NestJS 加上一两个库就够用;但一旦要扛起鉴权、实时协同、AI、文件与搜索这些完整能力,就很容易演变成这篇文章前面列出来的这一整套技术栈:NestJS + TypeScript 打基础,Fastify 提供高性能 HTTP 入口,Prisma + MySql 管数据,Redis + BullMQ 管缓存和队列,Elasticsearch 管搜索,MinIO 管文件,再加上 @nestjs/swagger、class-validator、Prometheus、Terminus 和 OpenAI、LangChain 这些周边,让项目从“能跑”变成“好用、可维护、可扩展”。
对前端转全栈来说,这套组合有两个现实的好处:一是语法和思路都围绕 TypeScript 展开,上手成本可控;二是每一个环节都有成熟的 Nest 集成(模块、装饰器、示例代码),可以按需逐步引入,而不必一口气吃下全部。你可以先用 NestJS + Prisma + Redis 起一个简单项目,再慢慢把队列、搜索、对象存储和 AI 能力补上,最终搭出一套适合自己长期维护的后端脚手架。