面试题库
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 的区别(必考)
| 维度 | interface | type |
|---|---|---|
| 对象/函数类型 | ✅ | ✅ |
| 联合、交叉、条件、映射类型 | ❌ | ✅ |
| 声明合并(同名自动合并) | ✅(扩展第三方声明时有用) | ❌ 重复声明直接报错 |
| 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 的生命周期
- 实例化(构造器)→ 属性注入完成 → 钩子按序触发:
onModuleInit()OnApplicationBootstrap的onApplicationBootstrap()(此时所有模块就绪,可安全互相调用)- 服务运行
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 的并发安全问题
- 单线程串行,纯同步代码无竞态;风险来自:
await期间的共享可变状态(见一.13);- 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、事务怎么做?
- 两种做法:
- 显式编程式(TypeORM):
dataSource.transaction(async (em) => { ... }),em 上所有操作同事务。 - 声明式:引入
typeorm-transactional(基于 AsyncLocalStorage + CLS),然后在 Service 方法上用@Transactional()装饰器,事务上下文自动透传。
- 显式编程式(TypeORM):
- 隔离级别:数据库层面设置(
SET TRANSACTION ISOLATION LEVEL/ typeormtransaction(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' } }),
forRootvsforRootAsync:同步配置 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 选型(高频)
| 维度 | TypeORM | Prisma |
|---|---|---|
| 范式 | 传统 ORM(实体/装饰器) | Schema-first + 生成客户端 |
| 类型安全 | 装饰器实体,深度查询类型较弱 | 极强(schema 自动生成类型) |
| 复杂 SQL | QueryBuilder 较强 | 复杂查询要 $queryRaw |
| 迁移 | 自动 migration | migrate diff 友好 |
| Nest 集成 | 官方 @nestjs/typeorm | PrismaService 封装 |
答法:重业务轻 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 特性。