跳转到主要内容

面试题库

TypeScript 与 NestJS 面试题

TS 语言基础、Node/事件循环、NestJS 对照 Spring、TypeORM/Prisma、类型体操与补全,共 70+ 题。

  • Node.js
  • Nest
  • TypeScript
  • 面试

TypeScript + Node.js + NestJS 面试题

Redis、RabbitMQ、Kafka、ElasticSearch、MySQL、网络、Docker 等题目与语言无关,直接沿用 backend/03 和 backend/04 文档,本文不重复。


一、TypeScript 语言基础

1、TypeScript 是什么?和 JavaScript 的关系

  • TypeScript = JavaScript + 静态类型系统,是 JS 的超集,最终编译成 JS 运行。
  • 两个核心理解点:
    • 结构类型(Structural Typing):TS 只看”形状”不看”名字”。两个结构相同的类型即使没有继承关系也互相兼容。
    • 类型擦除:TS 的类型只在编译期存在,编译后完全消失(装饰器元数据除外)。
    • 动态逃逸口:TS 有 any、类型断言,逃逸很自由,也容易滥用。

2、TS 有哪些基础类型

分类类型说明
原始类型number / string / boolean / null / undefined / symbol / bigint只有一种数字类型(双精度浮点)
二进制Buffer / Uint8Array
空值/无返回void
任意any / unknown见下一题
数组T[] / Array<T>
键值Map<K,V> / Record<K,V>
集合Set<T>
枚举enum / union 字面量类型更推荐 type Status = 'A' | 'B'

3、any、unknown、never 的区别(必考)

  • any:放弃类型检查,任何操作都允许,会”污染”传播——相当于关闭 TS。
  • unknown:安全的 any。可以接收任何值,但使用前必须收窄(typeof、instanceof、断言),否则编译报错。推荐优先 unknown。
  • never:不可能存在的类型。函数永远抛错/死循环返回 never;never 是所有类型的子类型,常用于穷尽检查:
type Shape = Circle | Square;
function area(s: Shape) {
  switch (s.kind) {
    case 'circle': return ...;
    case 'square': return ...;
    default:
      const _exhaustive: never = s; // 漏分支时这里编译报错
  }
}

4、什么是类型推断?什么时候需要显式标注

  • let x = 1 自动推断为 number;函数返回值通常也能推断。
  • 需要显式标注的场景:函数参数、空数组([] 会被推断为 any[])、想用父类型接收多种子类型、公共 API 的导出函数(返回值显式标注可防止实现变化破坏调用方)。

5、联合类型和交叉类型

  • 联合 A | B:取值可以是 A 或 B,使用时需要收窄(判别联合 + switch 是最常用模式)。
  • 交叉 A & B:同时具备两者所有属性,类似”接口多继承”。
  • 联合类型 + 判别联合是 TS 表达力的核心:用一个字符串字面量字段区分分支,配合 never 穷尽检查既安全又好读。

6、interface 和 type 的区别(必考)

维度interfacetype
对象/函数类型✅✅
联合、交叉、条件、映射类型❌✅
声明合并(同名自动合并)✅(扩展第三方声明时有用)❌ 重复声明直接报错
extends✅ 也可 extends type通过 & 交叉实现
原始类型起别名❌✅ type ID = string | number

结论:给对象/类定形状优先 interface,需要类型运算时用 type。

7、TS 的类有哪些特性

  • 单继承(extends)。
  • 属性参数简写:constructor(private readonly name: string) 一行完成声明+赋值。
  • 访问修饰符:public(默认)/ protected / private(只编译期检查,运行时无效)/#private(真私有字段)。
  • readonly 修饰只读属性。
  • 抽象类 abstract:抽象方法必须由子类实现。
  • 类既是”类型”也是”值”:const u: User = ... 中类名可直接当类型用;但 interface 只是类型,运行时不存在。

8、TS 如何处理异常

  • TS/JS 没有 checked 异常,所有抛出的值都可以是任何类型(throw 'error' 甚至合法)。
  • 最佳实践:永远 throw new Error('...') 或其子类;catch (e) 中 e 是 unknown,需要收窄:
try {
  await risky();
} catch (e) {
  if (e instanceof HttpException) ...
  const msg = e instanceof Error ? e.message : String(e);
}
  • 自定义异常:class BusinessException extends Error {}。注意编译到 ES5 目标时 instanceof 会失效(原型链问题),需 Object.setPrototypeOf(this, BusinessException.prototype)。

9、泛型及泛型约束

function first<T>(arr: T[]): T | undefined { return arr[0]; }

// 泛型约束:限制 T 必须有 length 属性
function getLen<T extends { length: number }>(x: T): number { return x.length; }
  • 泛型在编译期擦除。TS 可以用 keyof、条件类型、infer 在类型层面做”计算”,见补全篇的类型体操。

10、常用内置工具类型(必考)

  • Partial<T> / Required<T>:所有属性变可选/必填。
  • Pick<T, K> / Omit<T, K>:挑取/剔除部分属性。
  • Record<K, V>:键值映射对象。
  • ReturnType<T> / Parameters<T>:取函数返回值/参数类型。
  • Readonly<T>:全只读。
  • 手写 Partial 体现映射类型功底:
type MyPartial<T> = { [K in keyof T]?: T[K] };

11、枚举:enum、const enum、union 该用哪个

  • enum:编译成双向映射对象,运行时占体积。
  • const enum:编译期内联,产物干净。
  • 更主流的做法是字面量联合:type Role = 'admin' | 'user'——零运行时开销、穷尽检查友好。需要反向映射或迭代时才用 enum。

12、单线程事件循环(必考)

  • Node 主线程单线程执行 JS,IO(网络、文件、DNS)由 libuv 线程池异步完成,CPU 密集型任务会阻塞整个进程。
  • 事件循环阶段:timers → pending → poll → check(setImmediate)→ close。
  • 宏任务 vs 微任务:每执行完一个宏任务,清空全部微任务队列;Promise.then 是微任务,setTimeout 是宏任务,微任务优先。
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
// 输出 1 3 2

13、async/await 和 Promise

  • async 函数返回 Promise;await 是 then 的语法糖;async/await 以同步书写风格表达异步流程。
  • 并发执行多个异步:await Promise.all([a(), b()])(一个失败全部 reject)、allSettled(互不影响)、race、any。常见错误:顺序 await 两个独立请求,白白串行。
  • 高并发限流:用 p-limit 等库控制并发数。
  • “线程安全”问题:单线程内不存在竞态;但 await 之间会让出执行权,if (!cache[key]) { cache[key] = await load(); } 这种 check-then-act 依然可能重复加载(并发请求各自通过检查),需要用 cache[key] ??= load() 缓存 Promise 本身来解决。

14、Stream 和 Buffer

  • Buffer:固定长度的二进制缓冲区,Node 特有。
  • Stream:分 Readable / Writable / Duplex / Transform,避免大文件一次性读入内存:
import { createReadStream, createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';
await pipeline(createReadStream('in.zip'), createWriteStream('out.zip'));
  • 背压(backpressure):写入速度跟不上读取速度时 pipe/pipeline 会自动暂停读取。

15、Reflect Metadata(Nest 的根基)

  • TS 类型编译后擦除,传统上只能用 Object.keys、instanceof 等弱反射。
  • 装饰器 + reflect-metadata 弥补了这一点:Nest 依赖它实现依赖注入和路由参数解析:
import 'reflect-metadata';

function Entity(name: string): ClassDecorator {
  return (target) => {
    Reflect.defineMetadata('entityName', name, target);
  };
}

@Entity('user')
class User {}
Reflect.getMetadata('entityName', User); // 'user'
  • Nest 中 @Injectable() 把类注册为 Provider,@Body() 等参数装饰器把元数据写进方法签名,框架启动时通过 Reflect.getMetadata('design:paramtypes', ...) 读出构造参数类型完成自动注入——没有装饰器元数据就没有 Nest 的 IoC。

16、V8 与内存管理

  • V8 执行管线:Ignition 字节码解释执行 + TurboFan 热点优化编译。
  • GC:V8 分新生代(Scavenge 半空间复制)和老生代(标记清除 + 标记整理),STW 时间很短。
  • 常见内存泄漏:全局变量、遗忘的 setInterval、闭包持有大对象、未销毁的事件监听器、无上限的 Map 缓存(用 WeakMap / LRU 缓存规避)。排查:process.memoryUsage()、heap snapshot(node --inspect + Chrome DevTools)。

17、模块体系

  • ES Module 是官方标准:import / export;CommonJS(require)是历史包袱,两者互操作有坑(ESM 导入 CJS 默认值语义)。
  • package.json 中 "type": "module" 决定 .js 文件按哪种解析;TS 里由 module 编译选项控制。
  • 一个文件就是一个模块,依赖解析基于 node_modules 查找。

18、相等判断的坑

  • == 会隐式类型转换('1' == 1 为 true),永远用 === / !==。
  • NaN !== NaN,判断用 Number.isNaN。
  • 对象比较的是引用,深比较用 lodash.isEqual 或自己实现;Object.is 可区分 +0/-0 和 NaN。

二、Node.js 与 Web 基础

HTTP 协议类题目(无状态、请求/响应报文、状态码、GET/POST 区别、HTTPS)与语言无关,答案见通用文档,此处只列 Node 侧的题目。

1、Node 如何充当 HTTP 服务器

  • Node 内置 http 模块,运行时本身就是 HTTP 服务器,不需要外部容器:
import { createServer } from 'http';
createServer((req, res) => res.end('ok')).listen(3000);
  • 实际开发不裸用 http 模块,而是用 Express / Fastify / NestJS。没有连接线程池模型——单线程事件循环处理所有连接,IO 异步非阻塞。

2、请求处理端点在 Nest/Express 体系里是什么

  • Express 里是路由回调 (req, res, next),Nest 里是 @Controller 中的处理方法。
  • 经典陷阱:Controller 也是单例,但单线程串行执行,普通属性不会有竞态;真正的坑是 await 期间共享状态被其他请求修改,以及不要在 Controller 上存请求级数据(Nest 用 Request 作用域 Provider 或 AsyncLocalStorage/ClsModule 解决)。

3、中间件与生命周期钩子

  • 统一编码、鉴权、日志等横切逻辑 → Express/Nest 中间件、Nest Guard/Interceptor。
  • 应用启停监听 → Nest 的 OnModuleInit / OnApplicationBootstrap / OnModuleDestroy / enableShutdownHooks();进程级事件 process.on('SIGTERM') 用于优雅停机(K8s 滚动更新必考)。

4、转发 vs 重定向在 Node 里怎么写

  • 转发(服务器内部):“重写 req.path 后 next()“,Nest 中可结合 @Req() 修改后放行,更常见的是直接调用内部 Service 方法;模板渲染体系(SSR 框架如 Next.js)才有真正的 forward 概念。
  • 重定向:Express res.redirect(302, '/login');Nest @Res() res 或 res.status(302).redirect()。语义:转发一次请求地址栏不变,重定向两次请求地址栏改变。

5、Cookie 和 Session 在 Nest 里的实现

  • Cookie:cookie-parser 中间件 + res.cookie()。
  • Session:express-session(生产配 Redis store,多实例共享)。
  • 前后端分离下主流是 JWT:无状态、水平扩展不需要共享存储;缺点是无法主动失效(配 Redis 黑名单)。对比 Session 的优缺点是高频题。
  • “浏览器禁用 Cookie 怎么办”:URL 传 session id(不安全)或改用 token 放 Authorization 头——JWT 天然不受影响。

6、单线程的 Node 如何利用多核

  • cluster 模块 fork 多个 worker 共享端口(master 分发连接),或用 pm2/容器编排多实例 + 负载均衡。
  • CPU 密集任务:worker_threads(真正的线程,共享内存需 transfer 或 SharedArrayBuffer)或拆成独立进程/消息队列。“Node 适不适合做 CPU 密集型服务”的标准答案:不适合,扬长避短。

7、Node 的全局对象和进程相关

  • globalThis(统一全局对象)、process(argv/env/exit)、Buffer、setTimeout 家族。
  • 环境变量:process.env.NODE_ENV,配合 @nestjs/config 的 ConfigModule.forRoot({ isGlobal: true }) 使用。

三、NestJS

1、什么是 Nest 的 IoC 容器?

  • Nest 借鉴 Angular 的 IoC 设计:@Module() 划分模块并登记 providers,框架负责实例化和装配,业务代码只声明依赖:
@Injectable()
export class UsersService {
  findAll() { return []; }
}

@Module({
  controllers: [UsersController],
  providers: [UsersService], // 注册到容器
})
export class UsersModule {}

@Controller('users')
export class UsersController {
  // 构造器注入,容器自动解析 UsersService
  constructor(private readonly usersService: UsersService) {}
}
  • 好处:解耦、可测试(测试时用 Test.createTestingModule 换成 mock provider)、统一生命周期管理。

2、依赖注入有哪几种方式?

  • 构造器注入(官方推荐):constructor(private readonly svc: UsersService),依赖不可变、必填,测试时容易替换。
  • 属性注入:@Inject(UsersService) private svc,需要装饰器,依赖可能为 undefined。
  • 基于 token 的注入:接口编译后擦除,无法直接当 token,需要 InjectionToken:
export interface LoggerService { log(msg: string): void; }
export const LOGGER = Symbol('LOGGER');
providers: [{ provide: LOGGER, useClass: ConsoleLogger }]
constructor(@Inject(LOGGER) private logger: LoggerService) {}
  • 另外支持 useValue(常量/配置对象)、useFactory(工厂,可注入依赖 async 工厂)、useExisting(别名)四种提供方式。

3、Provider 的作用域

作用域说明
DEFAULT(默认)全应用单例
Scope.TRANSIENT每次注入新实例
Scope.REQUEST每个请求独立实例(注入链上所有依赖都会升级为 request 作用域)
  • REQUEST 作用域的性能代价:每请求都要实例化一条依赖链,Nest 官方建议用 AsyncLocalStorage(ClsModule)替代来传递请求上下文。

4、Provider 的生命周期

  • 实例化(构造器)→ 属性注入完成 → 钩子按序触发:
    1. onModuleInit()
    2. OnApplicationBootstrap 的 onApplicationBootstrap()(此时所有模块就绪,可安全互相调用)
    3. 服务运行
    4. onModuleDestroy() → onApplicationShutdown()(需 app.enableShutdownHooks(),用于 SIGTERM 优雅停机:关闭连接、ack 完在途消息)

5、将类声明为可注入要注意什么

  • 类上 @Injectable();Controller 上 @Controller()(同时也可被注入)。
  • 注意:光加 @Injectable() 不够,必须注册到某个 Module 的 providers 里才会被容器管理——这是最常踩的坑(没有包扫描式自动注册,必须显式登记或用 DiscoveryModule)。

6、同名 Provider / 模块冲突怎么办?

  • 每个 Module 有独立的注入上下文:默认 providers 是模块私有的;exports 后被 import 才进入全局可见范围。
  • @Global() 模块导出的 provider 全局唯一;同 token 重复注册时后注册的覆盖(overrideProvider 也基于此)。
  • 跨模块注入的规则:A 想注入 B 模块的 provider,B 必须 exports 且 A import B。

7、循环依赖怎么解决?

  • Nest 不自动解决循环依赖,必须开发者显式打破环:
// module 级
@Module({ imports: [forwardRef(() => BModule)] })
// provider 级
constructor(@Inject(forwardRef(() => BService)) private b: BService) {}
  • 更好的方案:抽公共逻辑到第三个 Service、事件解耦(EventEmitter2)、或改用接口 + token 注入。函数参数 forwardRef(() => ...) 是因为类声明时对方还未定义(TS 的 hoisting 问题),不是运行时特性。

8、单例 Provider 的并发安全问题

  • 单线程串行,纯同步代码无竞态;风险来自:
    1. await 期间的共享可变状态(见一.13);
    2. worker_threads / cluster 各进程内存独立,本身不共享。
  • 结论题答法:Controller/Service 保持无状态或只放不可变配置,请求级数据走 REQUEST 作用域或 AsyncLocalStorage。

9、Nest 的 AOP 体系——必考

Nest 用五种组件实现横切关注点,执行顺序是高频考点:

请求 → Middleware → Guard → Interceptor(前置) → Pipe → Controller
                                              ↘ Handler 抛异常 → ExceptionFilter
Controller 返回 → Interceptor(后置, 可改写响应/包装 Result) → 响应
组件典型用途
Middleware日志、原始 body 处理
Guard认证鉴权(返回 bool 或抛 ForbiddenException)
Interceptor响应包装、缓存、耗时统计、map RxJS 流
Pipe参数转换(ParseIntPipe)、DTO 校验(ValidationPipe + class-validator)
ExceptionFilter全局异常 → 统一响应体
  • 自定义 Guard 示例:
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
  canActivate(ctx: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', ctx.getHandler());
    const user = ctx.switchToHttp().getRequest().user;
    return roles.includes(user.role);
  }
}

10、Nest 常用装饰器对照表(必背)

需求装饰器
控制器@Controller(‘users’)
路由方法@Get() @Post() @Put() @Delete() @Patch()
查询参数 ?a=1@Query()
路径参数 /users/:id@Param()
请求体@Body()
请求头@Headers()
返回 JSON直接返回对象即可(自动序列化)
全局异常@Catch() + ExceptionFilter
参数校验@UsePipes(ValidationPipe) 或全局 ValidationPipe
跨域@EnableCors / app.enableCors()
响应拦截NestInterceptor

11、统一返回体和全局异常处理

// 统一响应包装:Interceptor
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, R<T>> {
  intercept(ctx: ExecutionContext, next: CallHandler): Observable<R<T>> {
    return next.handle().pipe(map(data => ({ code: 0, data, message: 'ok' })));
  }
}

// 全局异常:Filter
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const res = host.switchToHttp().getResponse<Response>();
    const status = exception instanceof HttpException ? exception.getStatus() : 500;
    res.status(status).json({ code: status, message: exception instanceof Error ? exception.message : 'error' });
  }
}
// main.ts 全局注册
app.useGlobalFilters(new AllExceptionsFilter());
app.useGlobalInterceptors(new TransformInterceptor());

12、事务怎么做?

  • 两种做法:
    1. 显式编程式(TypeORM):dataSource.transaction(async (em) => { ... }),em 上所有操作同事务。
    2. 声明式:引入 typeorm-transactional(基于 AsyncLocalStorage + CLS),然后在 Service 方法上用 @Transactional() 装饰器,事务上下文自动透传。
  • 隔离级别:数据库层面设置(SET TRANSACTION ISOLATION LEVEL / typeorm transaction(isolation)),隔离级别的概念题沿用数据库文档;嵌套事务行为靠手动控制事务边界/嵌套策略实现,没有自动传播机制。
  • 事务失效场景:自调用(同类方法内部调用不走装饰器逻辑)、异常被 try/catch 吞掉、非事务连接上的操作、async 错误未 await 传播。

13、动态模块与配置体系

  • 动态模块让库模块按使用方配置装配:
ConfigModule.forRoot({ isGlobal: true, load: [() => require('./config')] }),
TypeOrmModule.forRootAsync({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    type: 'postgres',
    url: config.get('DATABASE_URL'),
    autoLoadEntities: true,
  }),
}),
JwtModule.register({ secret: process.env.JWT_SECRET, signOptions: { expiresIn: '1h' } }),
  • forRoot vs forRootAsync:同步配置 vs 依赖注入 + 异步取配置(如从配置中心拉取)。
  • @nestjs/config + .env + ConfigService 类型安全取值;多环境用 .env.development / .env.production + NODE_ENV 切换。
  • 官方生态包:@nestjs/typeorm、@nestjs/jwt、@nestjs/swagger、@nestjs/bull、@nestjs/microservices 等。

14、Nest 目录约定

  • CLI nest g resource user 生成 module/controller/service/dto/entity 标准结构;main.ts 引导 NestFactory.create(AppModule)。
  • 模块组织建议:按业务域(feature module)拆分 + AppModule 聚合导入 + 公共能力(config、logger、guards)做成 @Global 模块。

15、Nest 微服务怎么做?

  • 通信抽象:@nestjs/microservices,同一个 Service 可以暴露 HTTP/TCP/MQTT/Kafka/gRPC 多种传输层:
const app = await NestFactory.createMicroservice(AppModule, {
  transport: Transport.KAFKA,
  options: { client: { brokers: ['localhost:9092'] } },
});
@MessagePattern('user.created')  // 消息处理器
@EventPattern('user.created')    // 事件(fire-and-forget)
  • 组件生态:
能力Node/Nest 方案
注册发现Consul(官方 @nestjs/terminus 集成)、Kubernetes Service DNS、Nacos 社区 SDK
负载均衡客户端负载均衡:@nestjs/microservices 内置 + BullMQ/Kafka 消费组;网关层 Nginx/Kong/APISIX
熔断@nestjs/circuit-breaker(基于 opossum)、自定义 Interceptor + opossum
配置中心@nestjs/config + Consul/Vault/Apollo
网关Nest 自建网关 / Kong / APISIX / Traefik
链路追踪OpenTelemetry(@opentelemetry/auto-instrumentations-node)+ Jaeger
事件总线EventEmitter2(进程内)、Kafka/RabbitMQ(跨服务)
  • 熔断 vs 降级:熔断是”连续失败后直接快速失败一段时间”(opossum 的 closed→open→half-open 三态),降级是”失败/超限时走兜底逻辑”,通常熔断触发降级。示例:
@Injectable()
export class PaymentService {
  private breaker = new CircuitBreaker(this.callBank.bind(this), {
    timeout: 3000, errorThresholdPercentage: 50, resetTimeout: 10000,
  });
  async pay(dto: PayDto) {
    try {
      return await this.breaker.fire(dto);
    } catch {
      return this.fallback(dto); // 降级:记录待补偿队列,稍后重试
    }
  }
}

16、Nest 的请求日志和耗时统计

  • 全局 Middleware 记录入站,Interceptor tap/计时不侵入业务:
intercept(ctx, next) {
  const start = Date.now();
  const req = ctx.switchToHttp().getRequest();
  return next.handle().pipe(
    tap(() => console.log(`${req.method} ${req.url} ${Date.now() - start}ms`)),
  );
}
  • 生产日志库:nest-pino(结构化 JSON 日志 + 请求 ID)。

17、Nest 里用到的设计模式

  • 单例:默认 Provider。
  • 工厂:useFactory、NestFactory。
  • 装饰器:一切 @ 装饰器。
  • 观察者:EventEmitter2、RxJS Observable(Interceptor 的 next.handle() 就是 Observable 流)。
  • 代理/适配器:@nestjs/microservices 各 transport 的 ClientProxy、HttpService 适配 axios。
  • 策略:Passport 的多个 Strategy(jwt/local/oauth2)、Guard 内按角色分派。
  • 模板方法:生命周期钩子 OnModuleInit 等。
  • 责任链:Middleware → Guard → Pipe → Interceptor 链。

四、数据访问层

1、参数绑定与 SQL 注入防范

// TypeORM:永远用参数占位,框架自动预编译
userRepo.createQueryBuilder('u')
  .where('u.name = :name', { name: input })        // ✅ 预编译参数
  // .where(`u.name = '${input}'`)                 // ❌ 字符串拼接,注入!
// 原生查询
dataSource.query('SELECT * FROM users WHERE id = $1', [id]); // PostgreSQL $1

2、TypeORM vs Prisma 选型(高频)

维度TypeORMPrisma
范式传统 ORM(实体/装饰器)Schema-first + 生成客户端
类型安全装饰器实体,深度查询类型较弱极强(schema 自动生成类型)
复杂 SQLQueryBuilder 较强复杂查询要 $queryRaw
迁移自动 migrationmigrate diff 友好
Nest 集成官方 @nestjs/typeormPrismaService 封装

答法:重业务轻 SQL 用 Prisma(类型安全 + DX),复杂报表/查询调优多则 TypeORM QueryBuilder 或保留 SQL。

3、应用层缓存策略

  • 默认每次查询都打到数据库,缓存要自己做:
    • 方案 A:Interceptor + Redis 缓存响应(cache-manager-redis-store)。
    • 方案 B:Repository 层包缓存(cache aside:先查缓存,miss 查库回填,写时删缓存——缓存一致性的延迟双删/Canal 答案沿用 Redis 文档)。

4、insert 返回自增主键

// TypeORM:save 后实体自动回填 id
const user = await repo.save(new User(...)); // user.id 已有值
// Prisma
const user = await prisma.user.create({ data: {...} }); // 返回完整记录含 id
// 原生(PostgreSQL)RETURNING
await dataSource.query('INSERT INTO users(name) VALUES ($1) RETURNING id', [name]);

5、分页查询

const [items, total] = await repo.findAndCount({
  skip: (page - 1) * size,
  take: size,
  order: { createdAt: 'DESC' },
});

深分页优化(游标分页 WHERE id < :cursor LIMIT n)沿用 ES/MySQL 文档思路。


五、补全篇(TS/Nest 特有、必考)

1、类型体操三板斧:keyof / typeof / infer

// keyof:取对象类型的所有键组成联合
type UserKeys = keyof { id: number; name: string }; // 'id' | 'name'

// typeof:从值反推类型(配置对象常用)
const config = { host: 'localhost', port: 3000 } as const;
type Config = typeof config; // { readonly host: 'localhost'; readonly port: 3000 }

// infer:在条件类型中"提取"类型变量
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type R = Unwrap<Promise<string>>; // string

// 内置 ReturnType / Parameters 的实现原理就是 infer
type MyReturnType<T> = T extends (...args: never[]) => infer R ? R : never;

2、条件类型与映射类型

// 条件类型 + 分发特性(传入联合类型会逐个计算再合并)
type NonNullable<T> = T extends null | undefined ? never : T;

// 映射类型 + as 重映射(实现 Pick)
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };

// 模板字面量类型
type EventName = `on${Capitalize<'click' | 'input'>}`; // 'onClick' | 'onInput'

3、as const、satisfies、类型断言

  • as const:把字面量锁定为最窄类型且全只读,是元组/配置对象类型推断的关键。
  • as T:强制断言(编译期,无运行时检查),滥用会掩盖 bug;unknown as T 双重断言是明确危险的信号。
  • satisfies T(TS 4.9+):校验值满足类型但保留更精确的字面量推断——比 : T 声明更好用:
const themes = {
  dark:  { bg: '#000', color: '#fff' },
} satisfies Record<string, { bg: string; color: string }>;

4、函数重载、可选/默认/剩余参数

function combine(a: string, b: string): string;           // 重载签名
function combine(a: number, b: number): number;
function combine(a: any, b: any): any { return a + b; }   // 实现签名对外不可见
  • 可选参数 b?: string(= string | undefined)、默认参数 b = 'x' 会自动收窄类型、剩余参数 ...rest: string[]。

5、声明文件与 @types

  • 用到无类型的 JS 库时:declare module 'xxx' 或写 .d.ts;社区类型在 @types/xxx(DefinitelyTyped)。
  • declare 只描述类型不产生代码;tsconfig 的 typeRoots/types 控制加载范围。

6、tsconfig 关键配置(必考)

  • strict: true 总开关(含 strictNullChecks 等,务必开)。
  • target:输出 JS 语法版本(ES2020+);module:模块体系(Node 用 commonjs 或 NodeNext/ESM);moduleResolution: bundler / node16。
  • esModuleInterop:让 CJS 的 import x from 'cjs' 正常工作。
  • skipLibCheck: true 跳过依赖包类型检查(提速);paths/baseUrl 路径别名;declaration、sourceMap、experimentalDecorators + emitDecoratorMetadata(Nest 必需,元数据注入依赖它)。

7、Nest 的参数校验:class-validator + class-transformer

// main.ts 全局开启
app.useGlobalPipes(new ValidationPipe({
  whitelist: true,           // 剥离未定义字段
  forbidNonWhitelisted: true, // 多传字段直接报错
  transform: true,           // payload 转 DTO 类实例
}));
// DTO
export class CreateUserDto {
  @IsString() @IsNotEmpty() name: string;
  @IsEmail() email: string;
  @IsInt() @Min(0) age: number;
}
  • DTO 模式 + 类型即文档是 TS 全栈的核心优势。

8、认证方案:Passport + JWT 完整链路

// 策略
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(config: ConfigService) {
    super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            secretOrKey: config.get('JWT_SECRET') });
  }
  validate(payload: { sub: number; role: string }) { return payload; } // 挂到 req.user
}
// 使用
@UseGuards(AuthGuard('jwt'), RolesGuard)
@Roles('admin')
@Delete('users/:id')
remove(@Param('id', ParseIntPipe) id: number) { ... }
  • Guard 负责认证与授权,Strategy 负责令牌校验,校验结果挂到 req.user 供后续 Guard 使用。

9、Nest 事件与 RxJS

  • Interceptor 返回的是 Observable,可用 map/tap/catchError/timeout/retry 操作符做响应变换、重试、超时——理解”Interceptor 是流”是区分初级/中级的分水岭。
  • 进程内事件:@nestjs/event-emitter,@OnEvent('user.created');跨服务事件走 MQ(沿用 MQ 章节题目)。

10、Swagger / OpenAPI 文档

// main.ts
const config = new DocumentBuilder()
  .setTitle('API').setBearerAuth().build();
SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, config));
// Controller 上
@ApiTags('users') @ApiOperation({ summary: '创建用户' })
@ApiResponse({ status: 201, type: UserVo })
  • DTO 类即 Schema,天然同步。

11、限流

  • @nestjs/throttler:
ThrottlerModule.forRoot([{ ttl: 60000, limit: 10 }]); // 全局:1 分钟 10 次
@Throttle({ default: { limit: 3, ttl: 60000 } })       // 单接口覆盖
@SkipThrottle()                                        // 跳过
  • 分布式多实例场景改用 Redis 存储(自定义 ThrottlerStorage)或网关层限流(Kong/APISIX)。

12、优雅停机与健康检查

app.enableShutdownHooks(); // SIGTERM → onModuleDestroy 顺序执行
// 健康检查:@nestjs/terminus
@Get('health')
health() { return this.health.check([() => this.db.pingCheck('database')]); }
  • K8s 滚动更新流程:readinessProbe 失败摘流量 → SIGTERM → 处理完在途请求 → 关闭 DB/Redis/Kafka 连接 → 退出。Nest 优雅停机是微服务”发布如何不停服”的标准答案。

13、测试

// Nest 内置 Jest + Supertest
const moduleRef = await Test.createTestingModule({
  controllers: [UsersController],
  providers: [{ provide: UsersService, useValue: { findAll: jest.fn().mockResolvedValue([]) } }],
}).compile();

it('GET /users', () =>
  request(app.getHttpServer()).get('/users').expect(200).expect([]));
  • e2e:起完整 AppModule + Supertest 打真实 HTTP;单测:Test.createTestingModule + overrideProvider mock 依赖。

14、构建与部署

  • 运行:nest start(开发);生产 nest build(tsc)或 SWC 编译器(nest build -b swc,快 20 倍)。dev 热重载:nest start --watch。
  • 生产镜像用多阶段构建(builder 安装依赖编译 → runner 只拷 dist + 生产依赖)。
  • 进程守护:pm2 start dist/main.js -i max(cluster 多核);或 Docker + K8s。

15、Node 性能排查

  • 慢在哪:clinic doctor / autocannon 压测。
  • CPU 热点 / 阻塞事件循环:--inspect + Chrome CPU Profile;blocked-at 检测同步长任务。
  • 内存:heap snapshot 对比找增长对象;--max-old-space-size=2048 调整堆上限。
  • 事件循环延迟:monitorEventLoopDelay() 指标接入 Prometheus。
  • 常见事故:正则回溯爆炸、JSON.parse 大 payload 同步阻塞、误用 * 同步 fs API。

16、Monorepo 与 Nx(加分项)

  • nest new -p npm monorepo 原生 monorepo 或用 Nx/turbo 管理多 app + 共享 libs(shared dto/types);CI 只构建受影响的项目(nx affected)。

17、TS 后端优劣势一句话速答

  • 优势:类型即文档、前后端同构共享 DTO、结构类型+联合类型表达力、JSON 原生亲和、开发迭代速度。
  • 劣势:运行时无类型(校验需 class-validator 兜底)、生态重前端轻后端、单线程 CPU 密集弱、装饰器元数据依赖 legacy decorators 特性。