@lark-apaas/fullstack-nestjs-core
v1.1.31
Published
FullStack Nestjs Core
Downloads
4,651
Readme
@lark-apaas/fullstack-nestjs-core
FullStack NestJS Core 是一个为 NestJS 全栈应用提供核心功能的工具包,包括平台集成、CSRF 保护、用户上下文管理和开发工具等。
特性
- 平台模块: 一站式集成 Config、Logger、Database、Auth 等核心功能
- HTTP 客户端: 自动集成 @nestjs/axios,提供 HTTP 请求能力,自动打印请求日志
- CSRF 保护: 提供完整的 CSRF Token 生成和验证机制
- 用户上下文: 自动从请求头中提取并注入用户上下文信息
- 视图上下文: 将用户信息和 CSRF Token 注入到模板渲染上下文
- 旧路径兼容: 灰度迁移期间自动将旧路径 302 重定向到新路径,实现平滑过渡
- 开发工具: 自动生成 Swagger 文档、OpenAPI JSON 和 TypeScript 客户端 SDK
- 应用配置: 一键配置 Logger、Cookie Parser、全局前缀等
安装
npm install @lark-apaas/fullstack-nestjs-core或使用 yarn:
yarn add @lark-apaas/fullstack-nestjs-core环境要求
- Node.js >= 18.0.0
- NestJS >= 10.4.20
快速开始
方案 1: 使用 PlatformModule(推荐)
最简单的方式是使用 PlatformModule,它会自动集成所有核心功能:
import { Module } from '@nestjs/common';
import { PlatformModule } from '@lark-apaas/fullstack-nestjs-core';
@Module({
imports: [
PlatformModule.forRoot({
enableCsrf: true, // 是否启用 CSRF 保护,默认 true
csrfRoutes: '/api/*', // CSRF 保护的路由,默认 '/api/*'
alwaysNeedLogin: true, // 是否所有路由都需要登录,默认 true
}),
],
})
export class AppModule {}PlatformModule 自动集成:
- ✅ ConfigModule (环境变量配置)
- ✅ LoggerModule (日志系统)
- ✅ HttpModule (HTTP 客户端,自动打印请求日志)
- ✅ DataPaasModule (数据库连接)
- ✅ AuthNPaasModule (认证系统)
- ✅ UserContextMiddleware (用户上下文)
- ✅ LegacyPathRedirectMiddleware (旧路径兼容重定向)
- ✅ CsrfTokenMiddleware + CsrfMiddleware (CSRF 保护)
- ✅ ViewContextMiddleware (视图上下文)
- ✅ ValidationPipe (全局验证管道)
方案 2: 使用 configureApp 辅助函数
在 main.ts 中使用 configureApp 快速配置应用:
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { configureApp } from '@lark-apaas/fullstack-nestjs-core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 一键配置:Logger、CookieParser、GlobalPrefix、DevTools
await configureApp(app);
await app.listen(3000);
}
bootstrap();configureApp 会自动:
- ✅ 注册 AppLogger
- ✅ 注册 cookie-parser 中间件
- ✅ 根据
CLIENT_BASE_PATH环境变量设置全局前缀 - ✅ 在非生产环境自动挂载
DevToolsV2Module
方案 3: 手动配置各个模块
如果需要更细粒度的控制,可以单独使用各个模块:
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import {
CsrfTokenMiddleware,
CsrfMiddleware,
UserContextMiddleware,
ViewContextMiddleware,
} from '@lark-apaas/fullstack-nestjs-core';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// 用户上下文中间件
consumer
.apply(UserContextMiddleware)
.forRoutes('*');
// CSRF Token 生成(用于视图渲染)
consumer
.apply(CsrfTokenMiddleware)
.exclude('/api/(.*)')
.forRoutes('*');
// 视图上下文注入
consumer
.apply(ViewContextMiddleware)
.exclude('/api/(.*)')
.forRoutes('*');
// CSRF 验证(用于 API 保护)
consumer
.apply(CsrfMiddleware)
.forRoutes('/api/*');
}
}核心模块
PlatformModule
全局平台模块,集成了所有核心功能。
配置选项
| 选项 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| enableCsrf | boolean | true | 是否启用 CSRF 保护 |
| csrfRoutes | string | string[] | '/api/*' | CSRF 保护应用的路由 |
| alwaysNeedLogin | boolean | true | 是否所有路由都需要登录 |
使用示例
@Module({
imports: [
PlatformModule.forRoot({
enableCsrf: true,
csrfRoutes: ['/api/*', '/admin/*'],
alwaysNeedLogin: false,
}),
],
})
export class AppModule {}DevToolsV2Module(推荐)
使用 @hey-api/openapi-ts 生成高质量的 TypeScript 客户端 SDK。
新特性:
- ✅ 使用最新的
@hey-api/openapi-ts生成器 - ✅ 自动注入
baseURL配置到生成的客户端 - ✅ 自动给所有生成的文件添加
// @ts-nocheck注释
配置选项
| 选项 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| basePath | string | '/' | API 基础路径 |
| docsPath | string | '/api/docs' | Swagger UI 的挂载路径 |
| openapiOut | string | './client/src/api/gen/openapi.json' | OpenAPI JSON 输出路径 |
| needSetupServer | boolean | false | 是否挂载 Swagger UI 服务器 |
| needGenerateClientSdk | boolean | true | 是否生成客户端 SDK |
| clientSdkOut | string | './client/src/api/gen' | 客户端 SDK 输出目录 |
| swaggerOptions | object | - | Swagger 文档配置 |
使用示例
import { NestFactory } from '@nestjs/core';
import { DevToolsV2Module } from '@lark-apaas/fullstack-nestjs-core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 在开发环境挂载开发工具
if (process.env.NODE_ENV !== 'production') {
await DevToolsV2Module.mount(app, {
basePath: '/app',
docsPath: '/api_docs',
needSetupServer: true,
needGenerateClientSdk: true,
swaggerOptions: {
title: 'My API',
version: '1.0.0',
},
});
}
await app.listen(3000);
}
bootstrap();生成的文件
client/src/api/gen/
├── openapi.json # OpenAPI 规范文件
├── client.config.ts # 客户端配置(自动生成 baseURL)
├── types.gen.ts # TypeScript 类型定义
├── sdk.gen.ts # SDK 函数
└── client/
└── client.gen.ts # Axios 客户端所有生成的 .ts 文件都会自动添加 // @ts-nocheck 注释,避免类型检查错误。
DevToolsModule(旧版)
使用 openapi-typescript-codegen 生成客户端 SDK(不推荐,建议迁移到 DevToolsV2Module)。
import { DevToolsModule } from '@lark-apaas/fullstack-nestjs-core';
await DevToolsModule.mount(app, {
docsPath: 'api-docs',
openapiOut: './openapi.json',
needSetupServer: true,
needGenerateClientSdk: true,
clientSdkOut: './src/sdk',
});HTTP 客户端
PlatformModule 自动集成了 @nestjs/axios,提供开箱即用的 HTTP 请求能力。
特性
- ✅ 自动集成: 导入 PlatformModule 后即可直接使用,无需额外配置
- ✅ 自动日志: 所有 HTTP 请求和响应自动打印到日志系统
- ✅ 标准 API: 完全遵循 @nestjs/axios 的标准用法
- ✅ 默认配置: 自动设置 5 秒超时和最多 5 次重定向
基础使用
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
interface User {
id: string;
name: string;
email: string;
}
@Injectable()
export class UserService {
constructor(private readonly httpService: HttpService) {}
async getUserById(id: string): Promise<User> {
const { data } = await firstValueFrom(
this.httpService.get<User>(`https://api.example.com/users/${id}`)
);
return data;
}
async createUser(userData: Partial<User>): Promise<User> {
const { data } = await firstValueFrom(
this.httpService.post<User>('https://api.example.com/users', userData)
);
return data;
}
async updateUser(id: string, userData: Partial<User>): Promise<User> {
const { data } = await firstValueFrom(
this.httpService.put<User>(`https://api.example.com/users/${id}`, userData)
);
return data;
}
async deleteUser(id: string): Promise<void> {
await firstValueFrom(
this.httpService.delete(`https://api.example.com/users/${id}`)
);
}
}带配置的请求
async getUserWithHeaders(id: string) {
const { data } = await firstValueFrom(
this.httpService.get(`https://api.example.com/users/${id}`, {
headers: {
'Authorization': 'Bearer token',
'X-Custom-Header': 'value',
},
params: {
include: 'profile',
},
timeout: 10000, // 10秒超时
})
);
return data;
}自动日志输出
所有 HTTP 请求会自动打印到日志系统:
[HttpService] HTTP Request {
method: 'GET',
url: 'https://api.example.com/users/123',
headers: { ... },
params: { include: 'profile' },
data: undefined
}
[HttpService] HTTP Response {
method: 'GET',
url: 'https://api.example.com/users/123',
status: 200,
statusText: 'OK',
data: { id: '123', name: 'John', email: '[email protected]' }
}错误请求也会自动记录:
[HttpService] HTTP Response Error {
method: 'GET',
url: 'https://api.example.com/users/999',
status: 404,
statusText: 'Not Found',
data: { message: 'User not found' },
message: 'Request failed with status code 404'
}高级用法
直接访问 Axios 实例
constructor(private readonly httpService: HttpService) {
// 访问底层的 axios 实例
const axiosInstance = this.httpService.axiosRef;
// 添加自定义拦截器
axiosInstance.interceptors.request.use((config) => {
config.headers['X-Custom'] = 'my-value';
return config;
});
}使用 RxJS 操作符
import { map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
async getUsers() {
return this.httpService.get<User[]>('https://api.example.com/users').pipe(
map(response => response.data),
catchError(error => {
console.error('Error fetching users:', error);
return of([]);
})
);
}默认配置
PlatformModule 注册 HttpModule 时使用以下默认配置:
HttpModule.register({
timeout: 5000, // 5 秒超时
maxRedirects: 5, // 最多 5 次重定向
})注意事项
- 记得使用 firstValueFrom: @nestjs/axios 返回的是 Observable,需要转换为 Promise
- 记得解构 data: 响应数据在
response.data中 - 类型安全: 使用泛型指定响应数据类型
httpService.get<User>(...)
中间件
LegacyPathRedirectMiddleware
CLIENT_BASE_PATH 灰度切换到新路径格式(/app/:appId)后,将旧路径的页面请求 302 重定向到新路径,实现平滑过渡。已内置于 PlatformModule,无需手动注册。
重定向规则
| 环境 (req.userContext.env) | 旧路径前缀 | 重定向目标 |
|---|---|---|
| preview | /af/p/:appId/... | CLIENT_BASE_PATH/... |
| runtime | /spark/faas/:appId/... | CLIENT_BASE_PATH/... |
触发条件
CLIENT_BASE_PATH以/app/:appId格式开头(新路径格式)- 请求路径匹配对应环境的旧前缀
- 请求路径不以
/api开头(仅拦截页面请求)
示例
CLIENT_BASE_PATH = /app/cli_abc123
# 预览态旧链接访问
GET /af/p/cli_abc123/pages/home?tab=1
→ 302 /app/cli_abc123/pages/home?tab=1
# 运行态旧链接访问
GET /spark/faas/cli_abc123/pages/home
→ 302 /app/cli_abc123/pages/home注意事项
- 中间件依赖
UserContextMiddleware已填充req.userContext,注册顺序不可颠倒 - 旧路径格式(
CLIENT_BASE_PATH不是/app/:appId)下该中间件是空操作,没有额外开销 - 路径后缀和 query string 完整保留
CsrfTokenMiddleware
生成 CSRF Token 并设置到 Cookie 中,用于视图渲染场景。
配置选项
| 选项 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| cookieKey | string | 'suda-csrf-token' | Cookie 中存储 Token 的键名 |
| cookieMaxAge | number | 2592000000 (30天) | Cookie 过期时间(毫秒) |
| cookiePath | string | '/' | Cookie 路径 |
使用示例
CsrfTokenMiddleware.configure({
cookieKey: 'my-csrf-token',
cookieMaxAge: 86400000, // 1天
});
consumer
.apply(CsrfTokenMiddleware)
.exclude('/api/(.*)')
.forRoutes('*');CsrfMiddleware
验证请求中的 CSRF Token,用于保护 API 接口。
配置选项
| 选项 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| headerKey | string | 'x-suda-csrf-token' | 请求头中 Token 的键名 |
| cookieKey | string | 'suda-csrf-token' | Cookie 中 Token 的键名 |
使用示例
CsrfMiddleware.configure({
headerKey: 'x-my-csrf-token',
cookieKey: 'my-csrf-token',
});
consumer
.apply(CsrfMiddleware)
.forRoutes('/api/*');注意: 确保 CsrfTokenMiddleware 和 CsrfMiddleware 的 cookieKey 配置一致。
UserContextMiddleware
从请求头中提取用户上下文信息并注入到 req.userContext。
注入的上下文
req.userContext = {
userId: string | undefined; // 用户 ID(来自 x-user-id 请求头)
tenantId: string | undefined; // 租户 ID(来自 x-tenant-id 请求头)
appId: string; // 应用 ID(来自 x-app-id 请求头)
}使用示例
@Controller()
export class AppController {
@Get('profile')
getProfile(@Req() req: Request) {
const { userId, tenantId, appId } = req.userContext;
return { userId, tenantId, appId };
}
}ViewContextMiddleware
将用户上下文和 CSRF Token 注入到 res.locals,用于模板渲染。
注入的变量
res.locals = {
csrfToken: string; // CSRF Token
userId: string; // 用户 ID
tenantId: string; // 租户 ID
appId: string; // 应用 ID
}使用示例
// 在模板引擎中可以直接使用这些变量
// EJS 示例:
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<div>User ID: <%= userId %></div>TypeScript 支持
本包完全使用 TypeScript 编写,并提供完整的类型定义。
扩展 Express Request 类型
declare global {
namespace Express {
interface Request {
csrfToken?: string;
userContext: {
userId?: string;
tenantId?: string;
appId: string;
};
}
}
}环境变量
| 变量名 | 描述 | 默认值 | |--------|------|--------| | CLIENT_BASE_PATH | 应用的全局路径前缀 | - | | NODE_ENV | 运行环境 | - |
许可证
MIT
关键字
- plugin
- nestjs
- server
- typescript
- fullstack
