@jsfsi-core/ts-nestjs

中文文档
2.0.10发布于 2026/11/01ISCTypeScriptESM + CJS构建来源已验证

用于构建遵循六边形体系结构和领域驱动设计原则的健壮后端应用程序的特定于nestjs的实用程序。

中文使用文档
文档目录40 节
  1. 📦安装
  2. 🏗️架构
  3. 应用程序结构
  4. 📋特性
  5. 应用程序启动
  6. 配置服务
  7. 异常过滤器
  8. 请求验证
  9. 自定义日志记录器
  10. 请求中间件
  11. 内存中速率限制保护
  12. 📝命名约定
  13. 控制器
  14. 服务
  15. 模块
  16. 🧪测试原理
  17. 测试控制器
  18. 测试服务
  19. 使用结果类型进行测试
  20. ⚠️错误处理原则
  21. 边缘异常过滤器
  22. 域内结果类型
  23. 验证错误
  24. 🎯领域驱动设计
  25. 域层结构
  26. 域名服务
  27. 🔄结果类集成
  28. 使用结果类型
  29. 误差映射
  30. 📚最佳实践
  31. 1. 依赖注入
  32. 2. 模块组织
  33. 3. 配置
  34. 4. 请求验证
  35. 5. 错误处理
  36. 🔗其他资源
  37. NestJS
  38. 体系结构
  39. 验证
  40. 📄许可证

@jsfsi-core / ts-nestjs

用于构建遵循六边形体系结构和领域驱动设计原则的健壮后端应用程序的特定于nestjs的实用程序。

📦安装

npm install @jsfsi-core/ts-nestjs

同行依赖性:

  • @nestjs/core
  • @nestjs/common
  • express
  • body-parser

🏗️架构

这个包提供了特定于nestjs的六边形架构模式实现:

  • 应用程序启动:已配置的NestJS应用工厂
  • 配置:带有Zod验证的类型安全配置服务
  • 异常过滤器:在应用程序边缘集中错误处理
  • 验证器类型安全的请求验证装饰器
  • 中间件):请求日志记录和公共中间件

应用程序结构

src/
├── app/
│   ├── app.ts              # Application factory
│   └── bootstrap.ts        # Bootstrap helper
├── configuration/
│   └── AppConfigurationService.ts  # Configuration setup
├── filters/
│   └── AllExceptionsFilter.ts      # Exception handler (edge)
├── guards/
│   └── InMemoryRateLimitGuard.ts   # In-memory rate limit guard
├── middlewares/
│   ├── RequestMiddleware.ts              # Request logging
│   └── RequestMiddlewareLogCustomizer.ts  # Optional custom log payload
└── validators/
    └── ZodValidator.ts             # Request validators

📋特性

应用程序启动

使用预配置的设置创建类型安全的应用程序:

main.ts:

import 'reflect-metadata';

import * as path from 'path';
import { bootstrap } from '@jsfsi-core/ts-nestjs';
import { GCPLogger } from '@jsfsi-core/ts-nodejs';

import { AppModule } from './app/AppModule';

bootstrap({
  appModule: AppModule,
  configPath: path.resolve(__dirname, '../configuration'),
  logger: new GCPLogger('my-app'),
});

这个 bootstrap 函数:

  • 加载指定的环境配置 configPath
  • 创建并配置NestJS应用程序
  • 使用以下命令检索配置 ConfigService 与 APP_CONFIG_TOKEN
  • 在配置中指定的端口上自动启动应用程序
  • 处理CORS、异常过滤器和日志设置
  • 解析JSON请求正文到 JSON_BODY_LIMIT (一个 bytes 大小如 32mb;默认的 100kb) - - - createTestingApp 应用相同的限制。饰有 @JsonBodyLimit('4mb') 相反,在其路径下的每个路由上(安装在控制器路径上,因此它假定没有全局前缀;它的解析器不保留),以自己的限制获取主体 rawBody)

重要的:你的 AppModule 必须进口 appConfigModuleSetup() 将配置注册到 APP_CONFIG_TOKEN. 如果没有找到配置,bootstrap函数将抛出错误。

配置服务

使用Zod模式的类型安全配置:

import { z } from 'zod';
import { AppConfigSchema, appConfigModuleSetup, APP_CONFIG_TOKEN } from '@jsfsi-core/ts-nestjs';
import { ConfigService } from '@nestjs/config';

// Define configuration schema
export const AppConfigSchema = z.object({
  APP_PORT: z
    .string()
    .transform((val) => parseInt(val, 10))
    .refine((val) => !isNaN(val), { message: 'APP_PORT must be a valid number' }),
  DATABASE_URL: z.string().url(),
  CORS_ORIGIN: z.string().default('*'),
});

export type AppConfig = z.infer<typeof AppConfigSchema>;

// In your app module (AppModule.ts)
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { appConfigModuleSetup, RequestMiddleware } from '@jsfsi-core/ts-nestjs';

import { BrowserAdapter } from '../adapters/BrowserAdapter';
import { HealthController } from '../communication/controllers/health/HealthController';
import { RenderController } from '../communication/controllers/render/RenderController';
import { RenderService } from '../domain/RenderService';

const controllers = [HealthController, RenderController];
const services = [RenderService];
const adapters = [BrowserAdapter];

@Module({
  imports: [appConfigModuleSetup()],
  controllers: [...controllers],
  providers: [...services, ...adapters],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer.apply(RequestMiddleware).forRoutes('*');
  }
}

// Use in service
@Injectable()
export class MyService {
  constructor(private readonly configService: ConfigService) {}

  someMethod() {
    const config = this.configService.get<AppConfig>(APP_CONFIG_TOKEN);
    // config is fully typed
  }
}

异常过滤器

在应用程序边缘集中异常处理:

这个 createApp() 函数自动寄存器 AllExceptionsFilter 的:

  • 捕获所有未处理的异常
  • 将HTTP异常映射到适当的状态码
  • 记录用于监视的错误
  • 返回一致的错误响应

注意:这是捕获异常的地方(六边形架构的边缘)。过滤器是自动注册的,不需要手动设置。

请求验证

使用Zod进行类型安全请求验证:

import { Controller, Post } from '@nestjs/common';
import { SafeBody, SafeQuery, SafeParams } from '@jsfsi-core/ts-nestjs';
import { z } from 'zod';

const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1),
  age: z.number().int().positive(),
});

@Controller('users')
export class UserController {
  @Post()
  async createUser(@SafeBody(CreateUserSchema) user: z.infer<typeof CreateUserSchema>) {
    // user is fully typed based on schema
    // Validation happens automatically
    // Returns 400 Bad Request if validation fails
  }

  @Get(':id')
  async getUser(@SafeParams(z.object({ id: z.string().uuid() })) params: { id: string }) {
    // params.id is validated as UUID
  }

  @Get()
  async listUsers(
    @SafeQuery(z.object({ page: z.string().transform(Number).optional() }))
    query: {
      page?: number;
    },
  ) {
    // query.page is validated and transformed
  }
}

自定义日志记录器

使用 CustomLogger 而不是内置的NestJS Logger 为了在整个应用程序中保持一致的日志记录:

import { CustomLogger } from '@jsfsi-core/ts-nestjs';
import { Injectable } from '@nestjs/common';

@Injectable()
export class HealthService {
  private readonly logger = new CustomLogger(HealthService.name);

  async check(): Promise<{ status: string }> {
    this.logger.log('Checking health');
    return { status: 'OK' };
  }
}

这个 CustomLogger NestJS延伸的 Logger 并为整个服务的日志记录提供一致的接口。通过将类名传递给构造函数,日志将以服务名作为前缀,从而更容易在生产中跟踪日志。

可用的方法:

  • log(message: string) -一般资料
  • error(message: string, trace?: string) -带有可选堆栈跟踪的错误消息
  • warn(message: string) -警告信息
  • debug(message: string) -调试信息(仅在开发中显示)
  • verbose(message: string) -详细日志记录

示例输出:

[Nest] 12345  - 2025/01/01, 12:00:00     LOG [HealthService] Checking health

请求中间件

带有可选自定义有效负载的自动请求日志记录。

基本用法:

import { RequestMiddleware } from '@jsfsi-core/ts-nestjs';

// In your app module
@Module({
  // ...
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(RequestMiddleware).forRoutes('*');
  }
}

基本日志有效载荷 (包括总):

  • method - HTTP方法
  • url -请求URL
  • statusCode -响应状态码
  • timeSpentMs —响应时间(毫秒)
  • domain -客户端IP(来自请求)
  • requestHeaders -请求报头
  • responseHeaders -响应头

日志严重性从状态码中选择(例如:2XX→verbose, 4XX→warn, 5XX→error)。

自定义负载 -在每个请求日志中添加额外的字段(例如: userId, tenantId),提供定制器:

  1. 实现 RequestMiddlewareLogCustomizer 与 buildLogPayload(req, res): Record<string, unknown>.
  2. 将其注册在 REQUEST_MIDDLEWARE_LOG_CUSTOMIZER 模块中的令牌 providers.

中间件将自定义有效负载与基本有效负载合并;如果名称冲突,自定义键将覆盖基本键。

import {
  REQUEST_MIDDLEWARE_LOG_CUSTOMIZER,
  RequestMiddleware,
  RequestMiddlewareLogCustomizer,
} from '@jsfsi-core/ts-nestjs';
import { Request, Response } from 'express';
import { Module, NestModule, MiddlewareConsumer, Provider } from '@nestjs/common';

// 1. Implement the customizer
export class RequestUserLogCustomizer implements RequestMiddlewareLogCustomizer {
  buildLogPayload(req: Request & { user?: { id: string } }, _res: Response): Record<string, unknown> {
    return {
      userId: req.user?.id,
    };
  }
}

const middlewares: Provider[] = [
  {
    provide: REQUEST_MIDDLEWARE_LOG_CUSTOMIZER,
    useClass: RequestUserLogCustomizer,
  },
];

@Module({
  imports: [appConfigModuleSetup()],
  controllers: [...controllers],
  providers: [...services, ...adapters, ...middlewares],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer.apply(RequestMiddleware).forRoutes('*');
  }
}

使用上面的定制器,每个请求日志将包括基本字段plus userId 当请求具有经过身份验证的用户时。

内存中速率限制保护

InMemoryRateLimitGuard 在滑动时间窗口内限制每个客户端(按IP)的请求数量。它仅在内存中(没有分布式状态),适合单实例api或单节点保护。

选项:

  • windowMs:以毫秒为单位的时间窗口。 60000 1分钟)。
  • maxRequests:窗口内每个客户端允许的最大请求数。

行为:

  • 客户端标识派生自 x-forwarded-for 头(第一个IP)或 request.ip,否则 'unknown'.
  • 滑动窗口:仅在最近的请求 windowMs 数到极限。
  • 当超过限制时,警卫就会投掷 HttpStatus.TOO_MANY_REQUESTS (429)并设置 Retry-After 头(窗口中最老的请求过期前的秒数)。
  • 构造函数抛出if windowMs 或者 maxRequests 不是正的。

用法:

import { InMemoryRateLimitGuard, InMemoryRateLimitGuardOptions } from '@jsfsi-core/ts-nestjs';
import { Controller, Get, UseGuards } from '@nestjs/common';

@Controller('api')
export class ApiController {
  @Get('limited')
  @UseGuards(new InMemoryRateLimitGuard({ windowMs: 60_000, maxRequests: 100 }))
  limited(): { ok: boolean } {
    return { ok: true };
  }
}

对于应用范围或配置驱动的限制,请扩展 InMemoryRateLimitGuard 并从配置中传递选项(参见template-rest-api README)。

出口: InMemoryRateLimitGuard, InMemoryRateLimitGuardOptions.

📝命名约定

控制器

  • 控制器: PascalCase后缀with Controller (例如, UserController, AuthController)
  • 端点:使用RESTful命名(例如: getUser, createUser, updateUser)

服务

  • 服务: PascalCase后缀with Service (例如, UserService, AuthService)
  • 域名服务:驻留在域层,而不是驻留在NestJS服务中

模块

  • 模块: PascalCase后缀with Module (例如, UserModule, AppModule)

🧪测试原理

测试控制器

import { createTestingApp } from '@jsfsi-core/ts-nestjs';
import { Controller, Get, Module } from '@nestjs/common';
import request from 'supertest';

@Controller('test')
class TestController {
  @Get()
  getHello(): { message: string } {
    return { message: 'Hello' };
  }
}

@Module({
  controllers: [TestController],
})
class TestModule {}

describe('TestController', () => {
  it('returns hello message', async () => {
    const app = await createTestingApp(TestModule);

    const response = await request(app.getHttpServer()).get('/test');

    expect(response.status).toBe(200);
    expect(response.body).toEqual({ message: 'Hello' });
  });
});

测试服务

import { Test } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';

describe('UserService', () => {
  let service: UserService;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: ConfigService,
          useValue: {
            get: jest.fn(),
          },
        },
      ],
    }).compile();

    service = module.get<UserService>(UserService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });
});

使用结果类型进行测试

当服务返回Result类型时,进行相应的测试:

import { isFailure } from '@jsfsi-core/ts-crossplatform';

describe('AuthService', () => {
  it('returns user on successful sign in', async () => {
    const [user, failure] = await authService.signIn(email, password);

    expect(user).toBeDefined();
    expect(failure).toBeUndefined();
  });

  it('returns SignInFailure on authentication error', async () => {
    const [user, failure] = await authService.signIn(email, password);

    expect(user).toBeUndefined();
    expect(isFailure(SignInFailure)(failure)).toBe(true);
  });
});

⚠️错误处理原则

边缘异常过滤器

异常应该只在边缘抛出 (在控制器/异常过滤器中),而不是在域逻辑中:

// ✅ Good - In controller (edge)
@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthenticationService) {}

  @Post('signin')
  async signIn(@SafeBody(SignInSchema) body: SignInDto) {
    const [user, failure] = await this.authService.signIn(body.email, body.password);

    if (isFailure(SignInFailure)(failure)) {
      throw new UnauthorizedException('Invalid credentials');
    }

    return user;
  }
}

// ✅ Good - Domain service returns Result
export class AuthenticationService {
  async signIn(email: string, password: string): Promise<Result<User, SignInFailure>> {
    // No exceptions thrown here
    return this.authAdapter.signIn(email, password);
  }
}

// ✅ Good - Exception filter catches all exceptions
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(error: unknown, host: ArgumentsHost) {
    // All exceptions caught here (edge)
  }
}

// ❌ Bad - Throwing in domain service
export class AuthenticationService {
  async signIn(email: string, password: string): Promise<User> {
    // Don't throw exceptions in domain layer
    if (!isValid(email)) {
      throw new Error('Invalid email');
    }
  }
}

域内结果类型

域服务应该返回 Result 类型:

// ✅ Good
@Injectable()
export class UserService {
  async getUser(id: string): Promise<Result<User, UserNotFoundFailure>> {
    const [user, failure] = await this.userRepository.findById(id);

    if (isFailure(UserNotFoundFailure)(failure)) {
      return Fail(failure);
    }

    return Ok(user);
  }
}

// ✅ Good - Mapping Result to HTTP in controller
@Controller('users')
export class UserController {
  @Get(':id')
  async getUser(@SafeParams(IdSchema) params: { id: string }) {
    const [user, failure] = await this.userService.getUser(params.id);

    if (isFailure(UserNotFoundFailure)(failure)) {
      throw new NotFoundException('User not found');
    }

    return user;
  }
}

验证错误

使用 SafeBody, SafeQuery, SafeParams 对于自动验证:

// ✅ Good - Automatic validation
@Post('users')
async createUser(@SafeBody(CreateUserSchema) user: CreateUserDto) {
  // user is already validated
  return this.userService.create(user);
}

// ❌ Bad - Manual validation
@Post('users')
async createUser(@Body() user: any) {
  // Manual validation needed
  if (!user.email) {
    throw new BadRequestException('Email required');
  }
}

🎯领域驱动设计

域层结构

领域逻辑应该是框架不可知的:

src/
├── domain/
│   ├── models/
│   │   ├── User.ts
│   │   └── SignInFailure.ts
│   └── services/
│       └── AuthenticationService.ts    # Domain service (no NestJS dependencies)
├── adapters/
│   └── DatabaseAdapter.ts              # Implements domain interfaces
└── controllers/                        # NestJS-specific (edge)
    └── AuthController.ts

域名服务

域服务包含业务逻辑:

// ✅ Good - Domain service (no NestJS decorators)
export class AuthenticationService {
  constructor(private readonly authAdapter: AuthenticationAdapter) {}

  async signIn(email: string, password: string): Promise<Result<User, SignInFailure>> {
    // Business logic here
    return this.authAdapter.signIn(email, password);
  }
}

// ✅ Good - Inject domain service in NestJS service
@Injectable()
export class AuthService {
  constructor(private readonly authenticationService: AuthenticationService) {}

  async signIn(email: string, password: string) {
    return this.authenticationService.signIn(email, password);
  }
}

🔄结果类集成

使用结果类型

域服务返回结果类型,控制器映射到HTTP:

import { Result, isFailure } from '@jsfsi-core/ts-crossplatform';

@Controller('orders')
export class OrderController {
  constructor(private readonly orderService: OrderService) {}

  @Post()
  async createOrder(@SafeBody(CreateOrderSchema) order: CreateOrderDto) {
    const [orderId, failure] = await this.orderService.create(order);

    if (isFailure(ValidationFailure)(failure)) {
      throw new BadRequestException(failure.message);
    }

    if (isFailure(PaymentFailure)(failure)) {
      throw new PaymentRequiredException('Payment failed');
    }

    return { id: orderId };
  }
}

误差映射

将域故障映射为HTTP异常:

function mapFailureToHttpException(failure: Failure): HttpException {
  if (isFailure(ValidationFailure)(failure)) {
    return new BadRequestException(failure.message);
  }

  if (isFailure(NotFoundFailure)(failure)) {
    return new NotFoundException(failure.message);
  }

  if (isFailure(UnauthorizedFailure)(failure)) {
    return new UnauthorizedException(failure.message);
  }

  return new InternalServerErrorException('An error occurred');
}

📚最佳实践

1. 依赖注入

使用构造函数注入:

@Injectable()
export class UserService {
  constructor(
    private readonly userRepository: UserRepository,
    private readonly configService: ConfigService,
  ) {}
}

2. 模块组织

将模块中的相关功能分组:

@Module({
  imports: [TypeOrmModule.forFeature([UserEntity])],
  controllers: [UserController],
  providers: [UserService, UserRepository],
  exports: [UserService],
})
export class UserModule {}

3. 配置

始终使用类型化配置:

// ✅ Good
const config = this.configService.get<AppConfig>(APP_CONFIG_TOKEN);

// ❌ Bad
const port = process.env.PORT; // Not type-safe

4. 请求验证

总是用Zod模式验证请求:

// ✅ Good
@Post()
async create(@SafeBody(CreateSchema) data: CreateDto) {
  // data is validated and typed
}

// ❌ Bad
@Post()
async create(@Body() data: any) {
  // No validation, no type safety
}

5. 错误处理

在域中使用结果类型,只在边缘使用例外:

// Domain: Result types
async getUser(id: string): Promise<Result<User, UserNotFoundFailure>> {
  // ...
}

// Controller: Map to HTTP
async getUser(@Param('id') id: string) {
  const [user, failure] = await this.service.getUser(id);

  if (isFailure(UserNotFoundFailure)(failure)) {
    throw new NotFoundException();
  }

  return user;
}

🔗其他资源

NestJS

体系结构

验证

📄许可证

国际安全委员会

版本与发布记录

@jsfsi-core/ts-nestjs 共发布 57 个版本,首次发布于 2025/10/29;过去 12 个月发布了 46 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.0.10latest2026/11/011 天前
2.0.92026/11/012 天前
2.0.82026/10/304 天前
2.0.72026/10/304 天前
2.0.62026/08/282 个月前
2.0.52026/08/272 个月前

关于 @jsfsi-core/ts-nestjs 的常见问题

@jsfsi-core/ts-nestjs 是什么?

用于构建遵循六边形体系结构和领域驱动设计原则的健壮后端应用程序的特定于nestjs的实用程序。

如何安装 @jsfsi-core/ts-nestjs?

在项目目录运行 npm install @jsfsi-core/ts-nestjs,或使用 pnpm add / yarn add / bun add。

@jsfsi-core/ts-nestjs 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@jsfsi-core/ts-nestjs 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@jsfsi-core/ts-nestjs 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js 26.7.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@jsfsi-core/ts-nestjs 可以商用吗?

@jsfsi-core/ts-nestjs 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@jsfsi-core/ts-nestjs 还在维护吗?

最新版本 2.0.10 发布于 2026/11/01(1 天前);至今共发布 57 个版本,其中过去 12 个月发布了 46 个;首次发布于 2025/10/29。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。