NestJS
NestJS 是一种基于 TypeScript 的后端框架,它结合了 Express 和 Angular 的优点,提供了一种现代化、模块化和可扩展的开发方式。NestJS 的主要目标是提供一个高效、可维护和可测试的服务端应用程序框架,同时提供了许多有用的功能和工具,如依赖注入、模块化体系结构、中间件、管道、拦截器、异常过滤器等。
NestJS 的主要特点包括:
基于 TypeScript:NestJS 是一种基于 TypeScript 的框架,支持静态类型检查和强类型编程,提高了代码的可维护性和可读性。
模块化体系结构:NestJS 使用模块化体系结构,将应用程序拆分为多个模块,每个模块可以独立开发、测试和部署,提高了代码的可扩展性和可维护性。
依赖注入:NestJS 支持依赖注入,通过注入依赖项来实现松耦合的架构设计,提高了代码的可测试性和可维护性。
中间件和管道:NestJS 提供了中间件和管道机制,可以在请求和响应之间添加额外的逻辑,如身份验证、日志记录、异常处理等,提高了应用程序的可靠性和安全性。
异常过滤器:NestJS 提供了异常过滤器,可以捕获应用程序中的异常并进行处理,提高了应用程序的稳定性和可靠性。
NestJS 可以用于构建各种类型的后端服务,如 RESTful API、WebSocket 服务、微服务等。NestJS 社区提供了许多有用的扩展和插件,如 Swagger UI、TypeORM、GraphQL 等,可以帮助开发人员更加高效地构建和管理后端服务。
如果您想成为一名后端开发人员,NestJS 是一个非常有用的框架,需要掌握 TypeScript 的基础知识和 NestJS 的开发方式,了解常用的模块和工具,如路由、控制器、服务、中间件、管道、拦截器等。掌握 NestJS 可以帮助您更加高效和灵活地构建和管理后端服务,为自己的职业发展和个人成长打下坚实的基础。

查看更多相关内容
NestJS 部署和 DevOps 如何实现?# NestJS 部署和 DevOps 详解
## 部署概述
部署是将应用程序从开发环境转移到生产环境的过程。NestJS 应用程序可以通过多种方式部署,包括传统服务器、容器化部署、云服务等。
## 1. Docker 容器化
### 创建 Dockerfile
```dockerfile
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]
```
### 创建 .dockerignore
```
node_modules
dist
.git
.env
*.log
```
### 构建 Docker 镜像
```bash
docker build -t nestjs-app .
```
### 运行 Docker 容器
```bash
docker run -p 3000:3000 nestjs-app
```
## 2. Docker Compose
### docker-compose.yml
```yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_HOST=db
- DATABASE_PORT=3306
- DATABASE_USER=root
- DATABASE_PASSWORD=password
- DATABASE_NAME=nestjs
depends_on:
- db
restart: unless-stopped
db:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=nestjs
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
volumes:
mysql_data:
```
### 启动服务
```bash
docker-compose up -d
```
## 3. Kubernetes 部署
### Deployment 配置
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nestjs-app
spec:
replicas: 3
selector:
matchLabels:
app: nestjs-app
template:
metadata:
labels:
app: nestjs-app
spec:
containers:
- name: nestjs-app
image: nestjs-app:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_HOST
valueFrom:
secretKeyRef:
name: db-secret
key: host
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
```
### Service 配置
```yaml
apiVersion: v1
kind: Service
metadata:
name: nestjs-app-service
spec:
selector:
app: nestjs-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
```
### Ingress 配置
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nestjs-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nestjs-app-service
port:
number: 80
```
## 4. CI/CD 管道
### GitHub Actions 配置
```yaml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test
- name: Run lint
run: npm run lint
- name: Build
run: npm run build
build-and-push:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: username/nestjs-app:latest,username/nestjs-app:${{ github.sha }}
cache-from: type=registry,ref=username/nestjs-app:latest
cache-to: type=inline
deploy:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to Kubernetes
uses: azure/k8s-deploy@v4
with:
manifests: |
k8s/deployment.yaml
k8s/service.yaml
images: |
username/nestjs-app:${{ github.sha }}
kubeconfig: ${{ secrets.KUBE_CONFIG }}
```
### GitLab CI 配置
```yaml
stages:
- test
- build
- deploy
variables:
NODE_ENV: test
test:
stage: test
image: node:18
script:
- npm ci
- npm run test
- npm run lint
cache:
paths:
- node_modules/
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/nestjs-app nestjs-app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
```
## 5. 环境变量管理
### 使用 .env 文件
```bash
# .env.production
NODE_ENV=production
PORT=3000
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_USER=root
DATABASE_PASSWORD=password
DATABASE_NAME=nestjs
JWT_SECRET=your-secret-key
```
### 使用 ConfigModule
```typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: `.env.${process.env.NODE_ENV}`,
}),
],
})
export class AppModule {}
```
### Kubernetes Secrets
```yaml
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
host: bG9jYWxob3N0
port: MzMwNg==
user: cm9vdA==
password: cGFzc3dvcmQ=
```
## 6. 健康检查
### 健康检查端点
```typescript
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'),
]);
}
}
```
### Kubernetes 健康检查
```yaml
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
```
## 7. 日志管理
### 结构化日志
```typescript
import { Logger } from '@nestjs/common';
export class AppService {
private readonly logger = new Logger(AppService.name);
async getData() {
this.logger.log('Fetching data', { userId: 123, action: 'fetch' });
// 业务逻辑
}
}
```
### 使用 Winston
```typescript
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
@Module({
imports: [
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
}),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
}),
new winston.transports.File({
filename: 'logs/combined.log',
}),
],
}),
],
})
export class AppModule {}
```
## 8. 监控和告警
### 使用 Prometheus
```typescript
import { Controller, Get } from '@nestjs/common';
import { MetricsService } from './metrics.service';
@Controller('metrics')
export class MetricsController {
constructor(private metricsService: MetricsService) {}
@Get()
getMetrics() {
return this.metricsService.getMetrics();
}
}
```
### Grafana 仪表板
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard
data:
dashboard.json: |
{
"dashboard": {
"title": "NestJS Application",
"panels": [
{
"title": "Request Rate",
"targets": [
{
"expr": "rate(http_requests_total[5m])"
}
]
}
]
}
}
```
## 9. 负载均衡
### Nginx 配置
```nginx
upstream nestjs_backend {
server nestjs-app-1:3000;
server nestjs-app-2:3000;
server nestjs-app-3:3000;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://nestjs_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
### AWS ALB 配置
```yaml
Resources:
LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: nestjs-alb
Subnets:
- !Ref SubnetA
- !Ref SubnetB
SecurityGroups:
- !Ref SecurityGroup
Type: application
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: nestjs-tg
Port: 3000
Protocol: HTTP
VpcId: !Ref VPC
Targets:
- Id: !Ref EC2Instance1
- Id: !Ref EC2Instance2
```
## 10. 灾难恢复
### 数据库备份
```bash
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups"
DATABASE="nestjs"
mysqldump -u root -p$MYSQL_ROOT_PASSWORD $DATABASE | gzip > $BACKUP_DIR/db_backup_$DATE.sql.gz
# 保留最近7天的备份
find $BACKUP_DIR -name "db_backup_*.sql.gz" -mtime +7 -delete
```
### 自动扩展
```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nestjs-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nestjs-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
```
## 部署最佳实践
1. **环境隔离**:开发、测试、生产环境分离
2. **自动化部署**:使用 CI/CD 自动化部署流程
3. **版本控制**:所有配置文件纳入版本控制
4. **监控告警**:实施全面的监控和告警机制
5. **备份策略**:定期备份关键数据
6. **安全加固**:实施安全最佳实践
7. **文档化**:维护详细的部署文档
8. **回滚计划**:准备快速回滚方案
9. **性能测试**:部署前进行性能测试
10. **渐进式发布**:使用蓝绿部署或金丝雀发布
## 总结
NestJS 部署和 DevOps 提供了:
- 灵活的容器化方案
- 强大的编排支持
- 自动化的 CI/CD 流程
- 完善的监控体系
- 可靠的灾难恢复
掌握部署和 DevOps 是将 NestJS 应用程序成功交付到生产环境的关键。通过合理使用容器化、编排、CI/CD 和监控工具,可以构建出高可用、可扩展的生产环境,确保应用程序的稳定运行和快速迭代。
服务端 · 2月21日 17:11
NestJS 性能优化有哪些方法?# NestJS 性能优化详解
## 性能优化概述
性能优化是构建高性能 NestJS 应用程序的关键。通过合理的架构设计、代码优化和资源管理,可以显著提升应用程序的响应速度和吞吐量。
## 1. 数据库优化
### 查询优化
#### 使用索引
```typescript
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Index()
@Column()
email: string;
@Index()
@Column()
username: string;
@Column()
name: string;
}
```
#### 避免 N+1 查询
```typescript
// 不好的方式 - N+1 查询
async getUsersWithOrders() {
const users = await this.userRepository.find();
for (const user of users) {
user.orders = await this.orderRepository.find({ where: { userId: user.id } });
}
return users;
}
// 好的方式 - 使用 JOIN
async getUsersWithOrders() {
return this.userRepository.find({
relations: ['orders'],
});
}
```
#### 使用分页
```typescript
async findAll(page: number = 1, limit: number = 10) {
const [data, total] = await this.userRepository.findAndCount({
skip: (page - 1) * limit,
take: limit,
});
return {
data,
total,
page,
totalPages: Math.ceil(total / limit),
};
}
```
### 连接池配置
```typescript
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
entities: [User],
extra: {
connectionLimit: 20, // 根据服务器配置调整
},
})
```
## 2. 缓存策略
### 使用 Redis 缓存
```typescript
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
@Injectable()
export class UserService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async findOne(id: number) {
const cacheKey = `user_${id}`;
const cachedUser = await this.cacheManager.get(cacheKey);
if (cachedUser) {
return cachedUser;
}
const user = await this.userRepository.findOne({ where: { id } });
await this.cacheManager.set(cacheKey, user, 3600); // 缓存 1 小时
return user;
}
}
```
### HTTP 缓存
```typescript
import { Controller, Get, Header } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
@Header('Cache-Control', 'public, max-age=300') // 缓存 5 分钟
findAll() {
return this.usersService.findAll();
}
}
```
### 拦截器缓存
```typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
private cache = new Map();
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const cacheKey = request.url;
if (this.cache.has(cacheKey)) {
return of(this.cache.get(cacheKey));
}
return next.handle().pipe(
tap(data => {
this.cache.set(cacheKey, data);
}),
);
}
}
```
## 3. 异步处理
### 使用异步/等待
```typescript
// 好的方式
async findAll() {
return this.userRepository.find();
}
// 不好的方式 - 同步阻塞
findAll() {
return this.userRepository.findSync();
}
```
### 并行处理
```typescript
// 不好的方式 - 串行执行
async getUserData(userId: number) {
const user = await this.userRepository.findOne({ where: { id: userId } });
const orders = await this.orderRepository.find({ where: { userId } });
const notifications = await this.notificationRepository.find({ where: { userId } });
return { user, orders, notifications };
}
// 好的方式 - 并行执行
async getUserData(userId: number) {
const [user, orders, notifications] = await Promise.all([
this.userRepository.findOne({ where: { id: userId } }),
this.orderRepository.find({ where: { userId } }),
this.notificationRepository.find({ where: { userId } }),
]);
return { user, orders, notifications };
}
```
### 使用队列处理耗时任务
```typescript
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';
@Injectable()
export class EmailService {
constructor(@InjectQueue('email') private emailQueue: Queue) {}
async sendEmail(to: string, subject: string, body: string) {
await this.emailQueue.add('send-email', { to, subject, body });
}
}
```
## 4. 压缩
### 启用 Gzip 压缩
```typescript
import compression from 'compression';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(compression());
await app.listen(3000);
}
```
### 配置压缩级别
```typescript
app.use(compression({
level: 6, // 压缩级别 1-9
threshold: 1024, // 只压缩大于 1KB 的响应
}));
```
## 5. 静态资源优化
### 使用 CDN
```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 配置静态资源使用 CDN
app.useStaticAssets('public', {
prefix: '/static/',
cacheControl: true,
maxAge: 31536000, // 1 年
});
await app.listen(3000);
}
```
### 图片优化
```typescript
import { Controller, Get, Param, Res } from '@nestjs/common';
import { Response } from 'express';
import sharp from 'sharp';
@Controller('images')
export class ImageController {
@Get(':filename')
async getImage(@Param('filename') filename: string, @Res() res: Response) {
const image = sharp(`./public/images/${filename}`);
// 根据请求参数优化图片
const width = parseInt(req.query.width) || 800;
const height = parseInt(req.query.height) || 600;
image
.resize(width, height)
.jpeg({ quality: 80 })
.pipe(res);
}
}
```
## 6. 代码优化
### 使用懒加载模块
```typescript
@Module({
imports: [
// 懒加载模块
UsersModule,
OrdersModule,
],
})
export class AppModule {}
```
### 避免不必要的计算
```typescript
// 不好的方式
async processUsers(users: User[]) {
return users.map(user => {
const expensiveResult = this.expensiveCalculation(user);
return { ...user, result: expensiveResult };
});
}
// 好的方式 - 使用缓存
async processUsers(users: User[]) {
const cache = new Map();
return users.map(user => {
const cacheKey = user.id;
if (!cache.has(cacheKey)) {
cache.set(cacheKey, this.expensiveCalculation(user));
}
return { ...user, result: cache.get(cacheKey) };
});
}
```
### 使用流处理大数据
```typescript
import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
@Controller('files')
export class FileController {
@Get('download/:filename')
downloadFile(@Param('filename') filename: string): StreamableFile {
const file = createReadStream(`./files/${filename}`);
return new StreamableFile(file);
}
}
```
## 7. 监控和分析
### 性能监控
```typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger(LoggingInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
const request = context.switchToHttp().getRequest();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - now;
this.logger.log(
`${request.method} ${request.url} - ${duration}ms`,
);
}),
);
}
}
```
### 使用 APM 工具
```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Agent } from '@elastic/apm-node';
const apm = new Agent({
serviceName: 'nestjs-app',
serverUrl: 'http://localhost:8200',
});
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
```
## 8. 负载均衡
### 使用 PM2 集群模式
```bash
npm install pm2 -g
pm2 start dist/main.js -i max --name nestjs-app
```
### 配置 PM2
```javascript
// ecosystem.config.js
module.exports = {
apps: [{
name: 'nestjs-app',
script: './dist/main.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};
```
## 9. 内存优化
### 避免内存泄漏
```typescript
// 不好的方式 - 可能导致内存泄漏
@Injectable()
export class CacheService {
private cache = new Map();
set(key: string, value: any) {
this.cache.set(key, value);
}
}
// 好的方式 - 使用 TTL
@Injectable()
export class CacheService {
private cache = new Map();
private ttl = 3600000; // 1 小时
set(key: string, value: any) {
this.cache.set(key, {
value,
expires: Date.now() + this.ttl,
});
// 定期清理过期缓存
this.cleanup();
}
private cleanup() {
const now = Date.now();
for (const [key, data] of this.cache.entries()) {
if (data.expires < now) {
this.cache.delete(key);
}
}
}
}
```
### 使用对象池
```typescript
@Injectable()
export class ObjectPool<T> {
private pool: T[] = [];
private factory: () => T;
constructor(factory: () => T, size: number = 10) {
this.factory = factory;
for (let i = 0; i < size; i++) {
this.pool.push(factory());
}
}
acquire(): T {
return this.pool.pop() || this.factory();
}
release(obj: T): void {
this.pool.push(obj);
}
}
```
## 10. 网络优化
### 使用 HTTP/2
```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
await app.listen(3000);
}
bootstrap();
```
### 配置 Keep-Alive
```typescript
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const server = app.getHttpServer();
server.keepAliveTimeout = 65000;
server.headersTimeout = 66000;
await app.listen(3000);
}
```
## 性能优化最佳实践
1. **监控性能**:持续监控应用程序性能指标
2. **基准测试**:建立性能基准并定期测试
3. **渐进优化**:逐步优化,每次只优化一个方面
4. **代码审查**:定期进行代码审查以发现性能问题
5. **使用缓存**:合理使用缓存减少数据库查询
6. **异步处理**:使用异步和并行处理提高效率
7. **资源压缩**:启用压缩减少传输数据量
8. **负载均衡**:使用负载均衡分散请求压力
9. **定期清理**:定期清理缓存和临时数据
10. **文档记录**:记录优化过程和结果
## 总结
NestJS 性能优化提供了:
- 数据库查询优化
- 多种缓存策略
- 异步处理能力
- 资源压缩技术
- 监控和分析工具
掌握性能优化是构建高性能 NestJS 应用程序的关键。通过合理应用各种优化技术和最佳实践,可以显著提升应用程序的性能、响应速度和用户体验。性能优化是一个持续的过程,需要根据实际应用场景不断调整和改进。
服务端 · 2月21日 17:11
NestJS GraphQL 如何集成?# NestJS GraphQL 集成详解
## GraphQL 概述
GraphQL 是一种用于 API 的查询语言和运行时环境。NestJS 通过 `@nestjs/graphql` 包提供了完整的 GraphQL 支持,使开发者能够构建灵活、高效的 GraphQL API。
## 安装依赖
```bash
npm install @nestjs/graphql graphql apollo-server-express
npm install -D @types/graphql
```
## 基本配置
### 配置 GraphQL 模块
```typescript
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
sortSchema: true,
playground: true,
introspection: true,
context: ({ req }) => ({ req }),
}),
],
})
export class AppModule {}
```
## 定义 Schema
### 使用装饰器定义 Schema
```typescript
import { Field, Int, ObjectType, Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { User } from './entities/user.entity';
@ObjectType()
export class User {
@Field(() => ID)
id: number;
@Field()
name: string;
@Field()
email: string;
@Field(() => Int)
age: number;
@Field()
isActive: boolean;
@Field()
createdAt: Date;
@Field()
updatedAt: Date;
}
```
### 定义输入类型
```typescript
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class CreateUserInput {
@Field()
name: string;
@Field()
email: string;
@Field()
password: string;
@Field(() => Int, { nullable: true })
age?: number;
}
@InputType()
export class UpdateUserInput {
@Field(() => ID)
id: number;
@Field({ nullable: true })
name?: string;
@Field({ nullable: true })
email?: string;
@Field(() => Int, { nullable: true })
age?: number;
}
```
## 创建 Resolver
### 基本 Resolver
```typescript
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
import { CreateUserInput, UpdateUserInput } from './dto/user.input';
@Resolver(() => User)
export class UsersResolver {
constructor(private usersService: UsersService) {}
@Query(() => [User])
async users(): Promise<User[]> {
return this.usersService.findAll();
}
@Query(() => User, { nullable: true })
async user(@Args('id', { type: () => ID }) id: number): Promise<User> {
return this.usersService.findOne(id);
}
@Mutation(() => User)
async createUser(@Args('input') input: CreateUserInput): Promise<User> {
return this.usersService.create(input);
}
@Mutation(() => User, { nullable: true })
async updateUser(@Args('input') input: UpdateUserInput): Promise<User> {
return this.usersService.update(input.id, input);
}
@Mutation(() => Boolean)
async deleteUser(@Args('id', { type: () => ID }) id: number): Promise<boolean> {
return this.usersService.remove(id);
}
}
```
### 使用自定义装饰器
```typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
// 在 Resolver 中使用
@Mutation(() => User)
async createUser(
@Args('input') input: CreateUserInput,
@CurrentUser() user: any,
): Promise<User> {
return this.usersService.create(input, user.id);
}
```
## 关系和加载
### 一对一关系
```typescript
@ObjectType()
export class Profile {
@Field(() => ID)
id: number;
@Field()
bio: string;
@Field(() => User)
user: User;
}
@ObjectType()
export class User {
@Field(() => ID)
id: number;
@Field()
name: string;
@Field(() => Profile, { nullable: true })
profile?: Profile;
}
```
### 一对多关系
```typescript
@ObjectType()
export class Post {
@Field(() => ID)
id: number;
@Field()
title: string;
@Field()
content: string;
@Field(() => User)
author: User;
}
@ObjectType()
export class User {
@Field(() => ID)
id: number;
@Field()
name: string;
@Field(() => [Post])
posts: Post[];
}
```
### 多对多关系
```typescript
@ObjectType()
export class Tag {
@Field(() => ID)
id: number;
@Field()
name: string;
@Field(() => [Post])
posts: Post[];
}
@ObjectType()
export class Post {
@Field(() => ID)
id: number;
@Field()
title: string;
@Field(() => [Tag])
tags: Tag[];
}
```
## 分页
### 基于游标的分页
```typescript
import { ArgsType, Field, Int } from '@nestjs/graphql';
@ArgsType()
export class PaginationArgs {
@Field(() => Int, { nullable: true })
first?: number;
@Field(() => String, { nullable: true })
after?: string;
}
@ObjectType()
export class PaginatedUsers {
@Field(() => [User])
data: User[];
@Field(() => Boolean)
hasNextPage: boolean;
@Field(() => String, { nullable: true })
endCursor?: string;
}
@Resolver(() => User)
export class UsersResolver {
@Query(() => PaginatedUsers)
async users(@Args() pagination: PaginationArgs): Promise<PaginatedUsers> {
const { data, hasNextPage, endCursor } = await this.usersService.paginate(pagination);
return { data, hasNextPage, endCursor };
}
}
```
### 基于偏移量的分页
```typescript
@ArgsType()
export class OffsetPaginationArgs {
@Field(() => Int, { nullable: true, defaultValue: 0 })
skip?: number;
@Field(() => Int, { nullable: true, defaultValue: 10 })
take?: number;
}
@ObjectType()
export class OffsetPaginatedUsers {
@Field(() => [User])
data: User[];
@Field(() => Int)
total: number;
@Field(() => Int)
page: number;
@Field(() => Int)
totalPages: number;
}
@Resolver(() => User)
export class UsersResolver {
@Query(() => OffsetPaginatedUsers)
async users(@Args() pagination: OffsetPaginationArgs): Promise<OffsetPaginatedUsers> {
const { data, total, page, totalPages } = await this.usersService.paginate(pagination);
return { data, total, page, totalPages };
}
}
```
## 订阅(Subscriptions)
### 配置订阅
```typescript
import { PubSub } from 'graphql-subscriptions';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
installSubscriptionHandlers: true,
context: ({ req, connection }) => {
if (connection) {
return { req: connection.context };
}
return { req };
},
}),
],
})
export class AppModule {}
```
### 创建订阅 Resolver
```typescript
import { Resolver, Subscription, Root } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
@ObjectType()
export class Message {
@Field(() => ID)
id: number;
@Field()
content: string;
@Field()
createdAt: Date;
}
@Resolver(() => Message)
export class MessagesResolver {
private pubSub: PubSub;
constructor() {
this.pubSub = new PubSub();
}
@Subscription(() => Message, {
filter: (payload, variables) => {
return payload.chatId === variables.chatId;
},
})
messageAdded(@Root() message: Message, @Args('chatId') chatId: number): Message {
return message;
}
async publishMessage(message: Message) {
await this.pubSub.publish('messageAdded', { messageAdded: message });
}
}
```
## 数据加载器(DataLoader)
### 创建 DataLoader
```typescript
import { DataLoader } from 'dataloader';
import { UsersService } from './users.service';
export class UserDataLoader {
private loader: DataLoader<number, User>;
constructor(private usersService: UsersService) {
this.loader = new DataLoader(async (ids) => {
const users = await this.usersService.findByIds(ids);
return ids.map(id => users.find(user => user.id === id));
});
}
async load(id: number): Promise<User> {
return this.loader.load(id);
}
async loadMany(ids: number[]): Promise<User[]> {
return this.loader.loadMany(ids);
}
}
```
### 在 Resolver 中使用 DataLoader
```typescript
import { Resolver, Query, Args, ID, Parent } from '@nestjs/graphql';
import { UserDataLoader } from './user-dataloader';
@Resolver(() => Post)
export class PostsResolver {
constructor(private userDataLoader: UserDataLoader) {}
@Query(() => [Post])
async posts(): Promise<Post[]> {
return this.postsService.findAll();
}
@FieldResolver(() => User)
async author(@Parent() post: Post): Promise<User> {
return this.userDataLoader.load(post.authorId);
}
}
```
## 验证和转换
### 使用 class-validator
```typescript
import { IsEmail, IsString, MinLength } from 'class-validator';
@InputType()
export class CreateUserInput {
@Field()
@IsEmail()
email: string;
@Field()
@IsString()
@MinLength(6)
password: string;
@Field()
@IsString()
name: string;
}
```
### 自定义验证器
```typescript
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';
export function IsStrongPassword(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'isStrongPassword',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any) {
const hasUpperCase = /[A-Z]/.test(value);
const hasLowerCase = /[a-z]/.test(value);
const hasNumber = /[0-9]/.test(value);
return hasUpperCase && hasLowerCase && hasNumber;
},
defaultMessage(args: ValidationArguments) {
return 'Password must contain uppercase, lowercase, and numbers';
},
},
});
};
}
@InputType()
export class CreateUserInput {
@Field()
@IsStrongPassword()
password: string;
}
```
## 错误处理
### 自定义错误类
```typescript
import { ApolloError } from 'apollo-server-express';
export class UserNotFoundError extends ApolloError {
constructor(id: number) {
super(`User with ID ${id} not found`, 'USER_NOT_FOUND', {
id,
});
}
}
export class ValidationError extends ApolloError {
constructor(message: string, fields: string[]) {
super(message, 'VALIDATION_ERROR', { fields });
}
}
```
### 在 Resolver 中使用错误
```typescript
@Resolver(() => User)
export class UsersResolver {
constructor(private usersService: UsersService) {}
@Query(() => User, { nullable: true })
async user(@Args('id', { type: () => ID }) id: number): Promise<User> {
const user = await this.usersService.findOne(id);
if (!user) {
throw new UserNotFoundError(id);
}
return user;
}
@Mutation(() => User)
async createUser(@Args('input') input: CreateUserInput): Promise<User> {
try {
return await this.usersService.create(input);
} catch (error) {
throw new ValidationError('Invalid input', error.fields);
}
}
}
```
## 权限控制
### 创建权限守卫
```typescript
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
@Injectable()
export class GqlAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
if (!req.user) {
throw new UnauthorizedException();
}
return true;
}
}
```
### 使用权限守卫
```typescript
import { Resolver, Mutation, UseGuards } from '@nestjs/graphql';
import { GqlAuthGuard } from './guards/gql-auth.guard';
@Resolver(() => User)
export class UsersResolver {
@Mutation(() => User)
@UseGuards(GqlAuthGuard)
async createUser(@Args('input') input: CreateUserInput): Promise<User> {
return this.usersService.create(input);
}
}
```
## 性能优化
### 查询复杂度分析
```typescript
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
validationRules: [
queryComplexity({
maximumComplexity: 1000,
variables: {},
onComplete: (complexity) => {
console.log(`Query complexity: ${complexity}`);
},
}),
],
})
```
### 查询深度限制
```typescript
import { depthLimit } from 'graphql-depth-limit';
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
validationRules: [
depthLimit(5),
],
})
```
## 最佳实践
1. **Schema 优先**:优先使用 Schema 优先的方法
2. **类型安全**:充分利用 TypeScript 的类型系统
3. **分页**:始终实现分页以避免大量数据传输
4. **数据加载器**:使用 DataLoader 解决 N+1 查询问题
5. **错误处理**:提供清晰的错误信息
6. **权限控制**:实现适当的权限控制机制
7. **性能监控**:监控查询性能和复杂度
8. **文档化**:使用 GraphQL Playground 和文档工具
## 总结
NestJS GraphQL 集成提供了:
- 完整的 GraphQL 支持
- 灵活的 Schema 定义
- 强大的类型安全
- 丰富的查询功能
- 易于集成的生态系统
掌握 NestJS GraphQL 集成是构建现代、灵活 API 的关键。通过合理使用 GraphQL 的特性,可以构建出高效、类型安全、易于维护的 API,满足前端对数据的精确需求。GraphQL 的查询语言特性使客户端能够精确获取所需数据,减少网络传输和提升性能。
服务端 · 2月21日 17:11
NestJS 安全和认证如何实现?安全性是任何应用程序的关键组成部分。NestJS 提供了多种安全机制,包括身份验证、授权、数据加密、防护措施等,帮助开发者构建安全可靠的应用程序。
## 身份验证(Authentication)
### JWT 认证
#### 安装依赖
```bash
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install -D @types/passport-jwt
```
#### 创建 JWT 策略
```typescript
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
```
#### 创建认证服务
```typescript
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.usersService.findByUsername(username);
if (user && await bcrypt.compare(password, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { username: user.username, sub: user.userId };
return {
access_token: this.jwtService.sign(payload),
};
}
async register(username: string, password: string) {
const hashedPassword = await bcrypt.hash(password, 10);
return this.usersService.create({ username, password: hashedPassword });
}
}
```
#### 创建认证控制器
```typescript
import { Controller, Post, Body, UseGuards, Request } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalAuthGuard } from './local-auth.guard';
import { JwtAuthGuard } from './jwt-auth.guard';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@UseGuards(LocalAuthGuard)
@Post('login')
async login(@Request() req) {
return this.authService.login(req.user);
}
@Post('register')
async register(@Body() registerDto: { username: string; password: string }) {
return this.authService.register(registerDto.username, registerDto.password);
}
@UseGuards(JwtAuthGuard)
@Post('profile')
getProfile(@Request() req) {
return req.user;
}
}
```
### 本地认证策略
```typescript
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
usernameField: 'username',
passwordField: 'password',
});
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
```
### OAuth2 认证
#### 安装依赖
```bash
npm install @nestjs/passport passport passport-google-oauth20
npm install -D @types/passport-google-oauth20
```
#### 创建 Google OAuth 策略
```typescript
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(private configService: ConfigService) {
super({
clientID: configService.get('GOOGLE_CLIENT_ID'),
clientSecret: configService.get('GOOGLE_CLIENT_SECRET'),
callbackURL: configService.get('GOOGLE_CALLBACK_URL'),
scope: ['email', 'profile'],
});
}
async validate(accessToken: string, refreshToken: string, profile: any, done: VerifyCallback): Promise<any> {
const { name, emails, photos } = profile;
const user = {
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
picture: photos[0].value,
accessToken,
};
done(null, user);
}
}
```
## 授权(Authorization)
### 基于角色的访问控制(RBAC)
```typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user?.roles?.includes(role));
}
}
```
### 角色装饰器
```typescript
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
```
### 使用角色守卫
```typescript
@Controller('users')
export class UsersController {
@Get('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
getAdminData() {
return 'Admin data';
}
}
```
## 密码加密
### 使用 bcrypt
```typescript
import * as bcrypt from 'bcrypt';
async hashPassword(password: string): Promise<string> {
const salt = await bcrypt.genSalt(10);
return bcrypt.hash(password, salt);
}
async comparePassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
```
## 安全中间件
### Helmet
```typescript
import helmet from 'helmet';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(helmet());
await app.listen(3000);
}
```
### CORS 配置
```typescript
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: 'https://example.com',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
});
await app.listen(3000);
}
```
### 速率限制
```typescript
import rateLimit from 'express-rate-limit';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(
rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
}),
);
await app.listen(3000);
}
```
## 数据验证
### 使用 class-validator
```typescript
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(6)
password: string;
}
```
### 全局验证管道
```typescript
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
await app.listen(3000);
}
```
## SQL 注入防护
### 使用参数化查询
```typescript
// 不安全
const query = `SELECT * FROM users WHERE name = '${name}'`;
// 安全
const query = `SELECT * FROM users WHERE name = ?`;
const result = await this.userRepository.query(query, [name]);
```
### 使用 ORM
```typescript
// TypeORM 自动防止 SQL 注入
const user = await this.userRepository.findOne({
where: { name: username },
});
```
## XSS 防护
### 输入清理
```typescript
import * as xss from 'xss';
function sanitizeInput(input: string): string {
return xss(input);
}
```
### 内容安全策略(CSP)
```typescript
import helmet from 'helmet';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
},
}),
);
await app.listen(3000);
}
```
## CSRF 防护
### 使用 csurf
```typescript
import * as csurf from 'csurf';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(csurf({ cookie: true }));
await app.listen(3000);
}
```
## 安全最佳实践
1. **使用 HTTPS**:始终在生产环境中使用 HTTPS
2. **环境变量**:敏感信息存储在环境变量中
3. **密码加密**:使用 bcrypt 等安全算法加密密码
4. **输入验证**:验证所有用户输入
5. **最小权限原则**:用户只拥有必要的权限
6. **定期更新依赖**:保持依赖项更新以修复安全漏洞
7. **日志记录**:记录安全相关事件
8. **错误处理**:不向客户端暴露敏感错误信息
9. **会话管理**:使用安全的会话管理
10. **安全头**:设置适当的安全头
## 总结
NestJS 安全和认证提供了:
* 多种身份验证策略
* 灵活的授权机制
* 强大的数据保护
* 全面的安全防护
* 易于集成的安全工具
掌握安全性和认证是构建安全、可靠的 NestJS 应用程序的关键。通过合理使用认证策略、授权机制和安全最佳实践,可以保护应用程序免受各种安全威胁,确保用户数据和系统的安全。
服务端 · 2月17日 22:41
NestJS 如何集成数据库?## 数据库集成概述
NestJS 提供了对多种数据库的完整支持,包括关系型数据库(如 MySQL、PostgreSQL)和非关系型数据库(如 MongoDB)。通过使用 TypeORM、Prisma、Mongoose 等 ORM/ODM 库,开发者可以轻松实现数据库操作。
## TypeORM 集成
### 安装依赖
```bash
npm install @nestjs/typeorm typeorm
npm install mysql2 # 或 pg、sqlite3、better-sqlite3 等
```
### 配置 TypeORM
```typescript
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
entities: [User],
synchronize: true, // 生产环境应设为 false
logging: true,
}),
TypeOrmModule.forFeature([User]),
],
providers: [UserService],
controllers: [UserController],
})
export class UserModule {}
```
### 创建实体
```typescript
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
password: string;
@Column()
name: string;
@Column({ default: true })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
```
### 创建 Repository
```typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const user = this.userRepository.create(createUserDto);
return this.userRepository.save(user);
}
async findAll(): Promise<User[]> {
return this.userRepository.find();
}
async findOne(id: number): Promise<User> {
return this.userRepository.findOne({ where: { id } });
}
async update(id: number, updateUserDto: any): Promise<User> {
await this.userRepository.update(id, updateUserDto);
return this.findOne(id);
}
async remove(id: number): Promise<void> {
await this.userRepository.delete(id);
}
async findByEmail(email: string): Promise<User> {
return this.userRepository.findOne({ where: { email } });
}
}
```
## Prisma 集成
### 安装依赖
```bash
npm install @prisma/client
npm install prisma --save-dev
npx prisma init
```
### 配置 Prisma
```prisma
// prisma/schema.prisma
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
name String
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
```
### 创建 Prisma 服务
```typescript
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
```
### 使用 Prisma
```typescript
import { Injectable } from '@nestjs/common';
import { PrismaService } from './prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async create(createUserDto: CreateUserDto) {
return this.prisma.user.create({
data: createUserDto,
});
}
async findAll() {
return this.prisma.user.findMany();
}
async findOne(id: number) {
return this.prisma.user.findUnique({
where: { id },
});
}
async update(id: number, updateUserDto: any) {
return this.prisma.user.update({
where: { id },
data: updateUserDto,
});
}
async remove(id: number) {
return this.prisma.user.delete({
where: { id },
});
}
}
```
## Mongoose 集成
### 安装依赖
```bash
npm install @nestjs/mongoose mongoose
```
### 配置 Mongoose
```typescript
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './schemas/user.schema';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost:27017/nest'),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
],
providers: [UserService],
controllers: [UserController],
})
export class UserModule {}
```
### 创建 Schema
```typescript
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema()
export class User extends Document {
@Prop({ required: true, unique: true })
email: string;
@Prop({ required: true })
password: string;
@Prop({ required: true })
name: string;
@Prop({ default: true })
isActive: boolean;
@Prop({ default: Date.now })
createdAt: Date;
@Prop({ default: Date.now })
updatedAt: Date;
}
export const UserSchema = SchemaFactory.createForClass(User);
```
### 创建 Mongoose 服务
```typescript
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './schemas/user.schema';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UserService {
constructor(
@InjectModel(User.name) private userModel: Model<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const createdUser = new this.userModel(createUserDto);
return createdUser.save();
}
async findAll(): Promise<User[]> {
return this.userModel.find().exec();
}
async findOne(id: string): Promise<User> {
return this.userModel.findOne({ _id: id }).exec();
}
async update(id: string, updateUserDto: any): Promise<User> {
return this.userModel.findByIdAndUpdate(id, updateUserDto, { new: true }).exec();
}
async remove(id: string): Promise<User> {
return this.userModel.findByIdAndDelete(id).exec();
}
}
```
## 数据库迁移
### TypeORM 迁移
```bash
# 生成迁移
npm run typeorm migration:generate -- -n CreateUserMigration
# 运行迁移
npm run typeorm migration:run
# 回滚迁移
npm run typeorm migration:revert
```
### Prisma 迁移
```bash
# 创建迁移
npx prisma migrate dev --name init
# 应用迁移
npx prisma migrate deploy
# 重置数据库
npx prisma migrate reset
```
## 事务处理
### TypeORM 事务
```typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { User } from './entities/user.entity';
import { Order } from './entities/order.entity';
@Injectable()
export class OrderService {
constructor(
private dataSource: DataSource,
@InjectRepository(User)
private userRepository: Repository<User>,
@InjectRepository(Order)
private orderRepository: Repository<Order>,
) {}
async createOrderWithUser(userId: number, orderData: any) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const user = await queryRunner.manager.findOne(User, { where: { id: userId } });
const order = queryRunner.manager.create(Order, {
...orderData,
user,
});
await queryRunner.manager.save(order);
await queryRunner.commitTransaction();
return order;
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
}
```
### Prisma 事务
```typescript
async createOrderWithUser(userId: number, orderData: any) {
return this.prisma.$transaction(async (prisma) => {
const user = await prisma.user.findUnique({
where: { id: userId },
});
const order = await prisma.order.create({
data: {
...orderData,
user: {
connect: { id: userId },
},
},
});
return order;
});
}
```
## 关系映射
### TypeORM 关系
```typescript
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Order, order => order.user)
orders: Order[];
}
@Entity('orders')
export class Order {
@PrimaryGeneratedColumn()
id: number;
@Column()
userId: number;
@ManyToOne(() => User, user => user.orders)
user: User;
@Column()
product: string;
}
```
### Prisma 关系
```prisma
model User {
id Int @id @default(autoincrement())
name String
orders Order[]
}
model Order {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id])
product String
}
```
## 数据库连接池配置
### TypeORM 连接池
```typescript
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: false,
extra: {
connectionLimit: 10,
},
})
```
### Prisma 连接池
```prisma
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
// 连接池配置
connection_limit = 10
}
```
## 最佳实践
1. **使用 DTO**:使用数据传输对象来验证和转换数据
2. **环境变量**:使用环境变量管理数据库配置
3. **迁移管理**:使用迁移来管理数据库结构变更
4. **事务处理**:在需要原子性操作时使用事务
5. **索引优化**:为常用查询字段添加索引
6. **连接池**:合理配置连接池大小
7. **软删除**:实现软删除而不是物理删除
8. **查询优化**:避免 N+1 查询问题
## 总结
NestJS 数据库集成提供了:
* 对多种数据库的支持
* 灵活的 ORM/ODM 选择
* 完整的类型安全
* 强大的关系映射
* 便捷的事务处理
掌握数据库集成是构建数据驱动的 NestJS 应用程序的基础。通过合理选择和使用 ORM/ODM,遵循最佳实践,可以构建出高性能、可维护的数据访问层。
服务端 · 2月17日 22:40
什么是 NestJS 框架,它的核心特点和应用场景是什么?NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,使用 TypeScript 构建,并结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数响应式编程)的元素。
## 核心特点
1. **基于 TypeScript**:NestJS 完全使用 TypeScript 编写,提供强类型支持
2. **模块化架构**:应用程序被组织成模块,每个模块封装一组相关功能
3. **依赖注入**:内置强大的依赖注入系统,便于测试和维护
4. **Express/Fastify 兼容**:底层可以使用 Express 或 Fastify 作为 HTTP 服务器
5. **装饰器驱动**:大量使用装饰器来简化代码编写
6. **可测试性**:提供完整的测试工具和最佳实践
## 与其他框架的对比
* **与 Express 对比**:NestJS 提供了更结构化的架构和内置的依赖注入,而 Express 更轻量级但需要手动组织代码
* **与 Koa 对比**:NestJS 提供了更完整的开箱即用功能,而 Koa 更注重中间件的灵活性
* **与 Meteor 对比**:NestJS 是后端框架,而 Meteor 是全栈框架
## 适用场景
* 企业级后端 API 开发
* 微服务架构
* 实时应用程序(使用 WebSocket)
* GraphQL API 开发
* RESTful API 开发
## 为什么选择 NestJS
1. **学习曲线平缓**:对于有 Angular 经验的开发者来说,NestJS 的概念非常熟悉
2. **可维护性高**:模块化和依赖注入使代码更易于维护
3. **生态系统丰富**:支持多种数据库、ORM、验证库等
4. **社区活跃**:拥有活跃的社区和丰富的文档
5. **TypeScript 支持**:提供完整的类型安全和智能提示
## 基本架构
NestJS 应用程序由以下核心概念组成:
* **Modules(模块)**:组织应用程序的基本单元
* **Controllers(控制器)**:处理传入请求并返回响应
* **Providers(提供者)**:处理业务逻辑
* **Services(服务)**:Provider 的一种,用于封装可重用的业务逻辑
* **Pipes(管道)**:数据转换和验证
* **Guards(守卫)**:权限控制和路由保护
* **Interceptors(拦截器)**:在函数执行前后添加额外逻辑
* **Exception Filters(异常过滤器)**:处理异常和错误响应
## 总结
NestJS 是一个现代化的 Node.js 后端框架,它通过提供结构化的架构、强大的依赖注入系统和丰富的功能集,使开发者能够构建可维护、可测试和可扩展的服务器端应用程序。它特别适合需要长期维护的大型项目和企业级应用。
服务端 · 2月17日 22:40
NestJS 测试和最佳实践有哪些?## NestJS 测试概述
NestJS 提供了完整的测试支持,包括单元测试、集成测试和端到端测试。测试框架基于 Jest,提供了丰富的测试工具和实用程序。
## 测试类型
### 1. 单元测试(Unit Testing)
单元测试用于测试单个函数、类或模块的行为,通常不涉及外部依赖。
#### 服务单元测试示例
```typescript
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';
describe('UsersService', () => {
let service: UsersService;
let repository: Repository<User>;
const mockUserRepository = {
create: jest.fn(),
save: jest.fn(),
find: jest.fn(),
findOne: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: mockUserRepository,
},
],
}).compile();
service = module.get<UsersService>(UsersService);
repository = module.get<Repository<User>>(getRepositoryToken(User));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('create', () => {
it('should create a new user', async () => {
const createUserDto = { name: 'John', email: 'john@example.com' };
const user = { id: 1, ...createUserDto };
mockUserRepository.create.mockReturnValue(user);
mockUserRepository.save.mockResolvedValue(user);
const result = await service.create(createUserDto);
expect(repository.create).toHaveBeenCalledWith(createUserDto);
expect(repository.save).toHaveBeenCalledWith(user);
expect(result).toEqual(user);
});
});
describe('findAll', () => {
it('should return an array of users', async () => {
const users = [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' },
];
mockUserRepository.find.mockResolvedValue(users);
const result = await service.findAll();
expect(repository.find).toHaveBeenCalled();
expect(result).toEqual(users);
});
});
});
```
### 2. 集成测试(Integration Testing)
集成测试用于测试多个组件之间的交互,通常涉及数据库、外部服务等。
#### 控制器集成测试示例
```typescript
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/users (GET)', () => {
return request(app.getHttpServer())
.get('/users')
.expect(200)
.expect([]);
});
it('/users (POST)', () => {
return request(app.getHttpServer())
.post('/users')
.send({ name: 'John', email: 'john@example.com' })
.expect(201)
.expect(res => {
expect(res.body).toHaveProperty('id');
expect(res.body.name).toBe('John');
});
});
afterEach(async () => {
await app.close();
});
});
```
### 3. 端到端测试(E2E Testing)
端到端测试模拟真实用户场景,测试整个应用程序的流程。
```typescript
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('UsersController (e2e)', () => {
let app: INestApplication;
let userId: number;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
await app.init();
});
it('should create a user', async () => {
const response = await request(app.getHttpServer())
.post('/users')
.send({ name: 'John Doe', email: 'john@example.com' })
.expect(201);
userId = response.body.id;
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe('John Doe');
});
it('should get all users', async () => {
const response = await request(app.getHttpServer())
.get('/users')
.expect(200);
expect(Array.isArray(response.body)).toBe(true);
});
it('should get a user by id', async () => {
const response = await request(app.getHttpServer())
.get(`/users/${userId}`)
.expect(200);
expect(response.body.id).toBe(userId);
});
it('should update a user', async () => {
const response = await request(app.getHttpServer())
.patch(`/users/${userId}`)
.send({ name: 'Jane Doe' })
.expect(200);
expect(response.body.name).toBe('Jane Doe');
});
it('should delete a user', async () => {
await request(app.getHttpServer())
.delete(`/users/${userId}`)
.expect(200);
});
afterAll(async () => {
await app.close();
});
});
```
## 测试工具和实用程序
### 1. Test.createTestingModule()
创建测试模块,用于配置测试环境。
```typescript
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
providers: [UsersService],
}).compile();
```
### 2. Mock 提供者
使用 mock 对象替换真实的提供者。
```typescript
{
provide: UsersService,
useValue: {
findAll: jest.fn().mockResolvedValue([]),
findOne: jest.fn().mockResolvedValue({ id: 1 }),
},
}
```
### 3. Jest 函数
使用 Jest 的 mock 功能。
```typescript
jest.fn() // 创建 mock 函数
jest.mock() // 模块 mock
jest.spyOn() // 监听对象方法
mockResolvedValue() // 设置返回值
mockRejectedValue() // 设置拒绝值
mockReturnValue() // 设置同步返回值
mockReturnValueOnce() // 设置一次性返回值
```
## 最佳实践
### 1. 测试组织
```
src/
├── users/
│ ├── users.controller.spec.ts
│ ├── users.service.spec.ts
│ └── users.module.spec.ts
test/
├── app.e2e-spec.ts
└── users.e2e-spec.ts
```
### 2. 测试命名约定
* 使用 `describe` 分组相关测试
* 使用 `it` 或 `test` 描述单个测试用例
* 测试名称应该清晰描述测试内容
```typescript
describe('UsersService', () => {
describe('create', () => {
it('should create a new user', async () => {
// 测试逻辑
});
it('should throw an error if email already exists', async () => {
// 测试逻辑
});
});
});
```
### 3. 测试隔离
每个测试应该独立运行,不依赖其他测试的状态。
```typescript
beforeEach(() => {
// 在每个测试前重置状态
});
afterEach(() => {
// 在每个测试后清理
});
```
### 4. 测试覆盖率
使用 Jest 的覆盖率功能确保代码质量。
```bash
npm run test:cov
```
在 `package.json` 中配置:
```json
{
"jest": {
"collectCoverageFrom": [
"src/**/*.(t|j)s",
"!src/main.ts",
"!src/**/*.module.ts",
"!src/**/*.dto.ts"
],
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
```
### 5. Mock 外部依赖
对于数据库、API 等外部依赖,使用 mock 避免实际调用。
```typescript
const mockRepository = {
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
};
```
### 6. 测试异步代码
正确处理异步代码的测试。
```typescript
// 使用 async/await
it('should return user', async () => {
const result = await service.findOne(1);
expect(result).toBeDefined();
});
// 使用 Promise
it('should return user', () => {
return service.findOne(1).then(result => {
expect(result).toBeDefined();
});
});
// 使用 done 回调
it('should return user', (done) => {
service.findOne(1).then(result => {
expect(result).toBeDefined();
done();
});
});
```
### 7. 测试边界情况
确保测试覆盖各种边界情况和错误场景。
```typescript
describe('findOne', () => {
it('should return user when found', async () => {
// 正常情况
});
it('should throw NotFoundException when user not found', async () => {
// 用户不存在的情况
});
it('should throw BadRequestException when id is invalid', async () => {
// 无效 ID 的情况
});
});
```
### 8. 使用测试数据库
在集成测试中使用测试数据库,避免影响生产数据。
```typescript
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
entities: [User],
synchronize: true,
}),
],
}).compile();
});
```
## 性能测试
### 1. 负载测试
使用工具如 Artillery 或 k6 进行负载测试。
```javascript
// artillery.config.js
module.exports = {
config: {
target: 'http://localhost:3000',
phases: [
{ duration: 60, arrivalRate: 10 },
],
},
scenarios: [
{
flow: [
{ get: { url: '/users' } },
],
},
],
};
```
### 2. 响应时间测试
确保 API 响应时间在可接受范围内。
```typescript
it('should respond within 200ms', async () => {
const start = Date.now();
await request(app.getHttpServer()).get('/users');
const duration = Date.now() - start;
expect(duration).toBeLessThan(200);
});
```
## 代码质量最佳实践
### 1. 代码风格
使用 ESLint 和 Prettier 保持代码风格一致。
```bash
npm run lint
npm run format
```
### 2. 类型安全
充分利用 TypeScript 的类型系统。
```typescript
interface CreateUserDto {
name: string;
email: string;
}
```
### 3. 错误处理
统一错误处理机制。
```typescript
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
// 统一错误处理逻辑
}
}
```
### 4. 日志记录
使用 NestJS Logger 进行结构化日志记录。
```typescript
import { Logger } from '@nestjs/common';
export class UsersService {
private readonly logger = new Logger(UsersService.name);
async findAll() {
this.logger.log('Finding all users');
// 业务逻辑
}
}
```
### 5. 环境配置
使用 @nestjs/config 管理环境变量。
```typescript
import { ConfigService } from '@nestjs/config';
export class UsersService {
constructor(private configService: ConfigService) {
const apiKey = this.configService.get('API_KEY');
}
}
```
### 6. 文档化
使用 Swagger 生成 API 文档。
```typescript
import { ApiTags, ApiOperation } from '@nestjs/swagger';
@ApiTags('users')
@Controller('users')
export class UsersController {
@Get()
@ApiOperation({ summary: 'Get all users' })
findAll() {
return this.usersService.findAll();
}
}
```
## 总结
NestJS 测试和最佳实践提供了:
* 完整的测试支持框架
* 灵活的测试工具和实用程序
* 清晰的测试组织结构
* 高质量的代码标准
* 良好的开发体验
掌握测试和最佳实践是构建高质量 NestJS 应用程序的关键。通过全面的测试覆盖和遵循最佳实践,可以确保应用程序的可靠性、可维护性和可扩展性。测试不仅仅是质量保证,更是开发过程中的重要组成部分。
服务端 · 2月17日 22:36
NestJS WebSocket 和实时功能如何实现?## WebSocket 概述
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。NestJS 通过 `@nestjs/websockets` 和 `@nestjs/platform-socket.io` 包提供了对 WebSocket 的完整支持,使开发者能够轻松构建实时应用程序。
## NestJS WebSocket 基础
### 安装依赖
```bash
npm install @nestjs/websockets @nestjs/platform-socket.io
```
### 创建 WebSocket 网关
网关(Gateway)是 NestJS 中处理 WebSocket 连接的类,类似于控制器处理 HTTP 请求。
```typescript
import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({
cors: {
origin: '*',
},
})
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
afterInit(server: Server) {
console.log('WebSocket server initialized');
}
handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
@SubscribeMessage('message')
handleMessage(client: Socket, payload: any): void {
this.server.emit('message', payload);
}
}
```
### 注册网关
```typescript
import { Module } from '@nestjs/common';
import { ChatGateway } from './chat.gateway';
@Module({
providers: [ChatGateway],
})
export class ChatModule {}
```
## WebSocket 网关装饰器
### @WebSocketGateway()
配置 WebSocket 网关。
```typescript
@WebSocketGateway({
namespace: '/chat', // 命名空间
cors: { // CORS 配置
origin: '*',
},
path: '/ws', // 路径
transports: ['websocket'], // 传输方式
})
export class ChatGateway {}
```
### @WebSocketServer()
注入 WebSocket 服务器实例。
```typescript
@WebSocketServer()
server: Server;
```
### @SubscribeMessage()
订阅 WebSocket 消息。
```typescript
@SubscribeMessage('message')
handleMessage(client: Socket, payload: any): void {
// 处理消息
}
```
### @MessageBody()
提取消息体。
```typescript
@SubscribeMessage('message')
handleMessage(@MessageBody() data: any): void {
console.log(data);
}
```
### @ConnectedSocket()
获取连接的 Socket 实例。
```typescript
@SubscribeMessage('message')
handleMessage(@ConnectedSocket() client: Socket): void {
console.log(client.id);
}
```
## 网关生命周期钩子
### OnGatewayInit
```typescript
afterInit(server: Server) {
console.log('Gateway initialized');
}
```
### OnGatewayConnection
```typescript
handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}
```
### OnGatewayDisconnect
```typescript
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
```
## 实时聊天应用示例
### 聊天网关
```typescript
import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
interface Message {
user: string;
text: string;
timestamp: Date;
}
@WebSocketGateway({
cors: {
origin: '*',
},
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
private connectedClients: Map<string, Socket> = new Map();
handleConnection(client: Socket) {
this.connectedClients.set(client.id, client);
this.server.emit('user-joined', {
userId: client.id,
userCount: this.connectedClients.size,
});
}
handleDisconnect(client: Socket) {
this.connectedClients.delete(client.id);
this.server.emit('user-left', {
userId: client.id,
userCount: this.connectedClients.size,
});
}
@SubscribeMessage('join-room')
handleJoinRoom(client: Socket, room: string): void {
client.join(room);
client.emit('joined-room', room);
}
@SubscribeMessage('leave-room')
handleLeaveRoom(client: Socket, room: string): void {
client.leave(room);
client.emit('left-room', room);
}
@SubscribeMessage('send-message')
handleMessage(client: Socket, payload: Message): void {
const message: Message = {
...payload,
timestamp: new Date(),
};
this.server.emit('message', message);
}
@SubscribeMessage('private-message')
handlePrivateMessage(client: Socket, payload: { to: string; message: Message }): void {
const recipient = this.connectedClients.get(payload.to);
if (recipient) {
recipient.emit('private-message', payload.message);
}
}
}
```
### 前端客户端
```typescript
import { io, Socket } from 'socket.io-client';
class ChatClient {
private socket: Socket;
constructor(url: string) {
this.socket = io(url);
}
connect() {
this.socket.on('connect', () => {
console.log('Connected to server');
});
this.socket.on('message', (message: Message) => {
console.log('Received message:', message);
});
this.socket.on('user-joined', (data) => {
console.log('User joined:', data);
});
this.socket.on('user-left', (data) => {
console.log('User left:', data);
});
}
sendMessage(message: Message) {
this.socket.emit('send-message', message);
}
sendPrivateMessage(to: string, message: Message) {
this.socket.emit('private-message', { to, message });
}
joinRoom(room: string) {
this.socket.emit('join-room', room);
}
leaveRoom(room: string) {
this.socket.emit('leave-room', room);
}
disconnect() {
this.socket.disconnect();
}
}
```
## 使用 WebSocket 守卫
```typescript
import { WebSocketGateway, SubscribeMessage, OnGatewayConnection } from '@nestjs/websockets';
import { UseGuards } from '@nestjs/common';
import { WsGuard } from './ws.guard';
@WebSocketGateway()
@UseGuards(WsGuard)
export class ChatGateway implements OnGatewayConnection {
@SubscribeMessage('message')
handleMessage(client: Socket, payload: any): void {
// 只有通过验证的客户端才能处理消息
}
}
```
### WebSocket 守卫示例
```typescript
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { Socket } from 'socket.io';
@Injectable()
export class WsGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const client: Socket = context.switchToWs().getClient();
const token = client.handshake.auth.token;
if (!token) {
throw new UnauthorizedException();
}
// 验证 token
return this.validateToken(token);
}
private validateToken(token: string): boolean {
// 实现 token 验证逻辑
return true;
}
}
```
## 使用 WebSocket 拦截器
```typescript
import { WebSocketGateway, SubscribeMessage } from '@nestjs/websockets';
import { UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';
@WebSocketGateway()
@UseInterceptors(LoggingInterceptor)
export class ChatGateway {
@SubscribeMessage('message')
handleMessage(client: Socket, payload: any): void {
// 消息会被拦截器处理
}
}
```
## 使用 WebSocket 管道
```typescript
import { WebSocketGateway, SubscribeMessage, MessageBody } from '@nestjs/websockets';
import { UsePipes } from '@nestjs/common';
import { ValidationPipe } from './validation.pipe';
@WebSocketGateway()
@UsePipes(new ValidationPipe())
export class ChatGateway {
@SubscribeMessage('message')
handleMessage(@MessageBody() message: Message): void {
// 消息会被管道验证
}
}
```
## 实时通知系统
### 通知网关
```typescript
import { WebSocketGateway, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
interface Notification {
type: 'info' | 'warning' | 'error' | 'success';
title: string;
message: string;
timestamp: Date;
}
@WebSocketGateway({
namespace: '/notifications',
cors: { origin: '*' },
})
export class NotificationGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
private userSockets: Map<string, Set<string>> = new Map();
handleConnection(client: Socket) {
const userId = client.handshake.query.userId as string;
if (!this.userSockets.has(userId)) {
this.userSockets.set(userId, new Set());
}
this.userSockets.get(userId).add(client.id);
}
handleDisconnect(client: Socket) {
const userId = client.handshake.query.userId as string;
const sockets = this.userSockets.get(userId);
if (sockets) {
sockets.delete(client.id);
if (sockets.size === 0) {
this.userSockets.delete(userId);
}
}
}
sendNotificationToUser(userId: string, notification: Notification) {
const sockets = this.userSockets.get(userId);
if (sockets) {
sockets.forEach(socketId => {
this.server.to(socketId).emit('notification', notification);
});
}
}
sendBroadcastNotification(notification: Notification) {
this.server.emit('notification', notification);
}
}
```
### 在服务中使用通知网关
```typescript
import { Injectable } from '@nestjs/common';
import { NotificationGateway } from './notification.gateway';
@Injectable()
export class NotificationService {
constructor(private notificationGateway: NotificationGateway) {}
sendOrderNotification(userId: string, orderId: string) {
this.notificationGateway.sendNotificationToUser(userId, {
type: 'info',
title: '订单更新',
message: `您的订单 ${orderId} 已更新`,
timestamp: new Date(),
});
}
sendSystemAlert(message: string) {
this.notificationGateway.sendBroadcastNotification({
type: 'warning',
title: '系统通知',
message,
timestamp: new Date(),
});
}
}
```
## 最佳实践
1. **命名空间**:使用命名空间组织不同类型的 WebSocket 连接
2. **房间**:使用房间功能管理用户组
3. **认证**:在连接时进行身份验证
4. **错误处理**:妥善处理连接错误和消息错误
5. **资源清理**:在断开连接时清理资源
6. **消息验证**:使用管道验证传入的消息
7. **日志记录**:记录连接和断开事件
8. **性能监控**:监控连接数和消息吞吐量
## 总结
NestJS WebSocket 和实时功能提供了:
* 完整的 WebSocket 支持
* 灵活的网关系统
* 丰富的装饰器和钩子
* 与 NestJS 生态系统的无缝集成
* 易于构建实时应用程序
掌握 NestJS WebSocket 功能是构建实时应用程序的关键。通过合理使用网关、命名空间、房间和认证机制,可以构建出高性能、可扩展的实时应用,如聊天应用、实时通知系统、协作工具等。
服务端 · 2月17日 22:35
NestJS 中间件和守卫的区别是什么?## 中间件(Middleware)的概念
中间件是在路由处理程序之前调用的函数,可以访问请求和响应对象,以及应用程序的请求-响应周期中的 `next()` 中间件函数。中间件主要用于:
* 执行任何代码
* 更改请求和响应对象
* 结束请求-响应周期
* 调用堆栈中的下一个中间件函数
### 中间件的基本结构
```typescript
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`Request... ${req.method} ${req.url}`);
next();
}
}
```
### 应用中间件
#### 在模块中应用中间件
```typescript
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
@Module({
imports: [],
controllers: [],
providers: [],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('cats');
}
}
```
#### 限制中间件应用到特定路由
```typescript
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.exclude('cats', { path: 'cats', method: RequestMethod.GET })
.forRoutes(CatsController);
}
```
#### 应用多个中间件
```typescript
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware, AuthMiddleware)
.forRoutes(CatsController);
}
```
### 函数式中间件
```typescript
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
}
// 应用函数式中间件
configure(consumer: MiddlewareConsumer) {
consumer
.apply(logger)
.forRoutes(CatsController);
}
```
### 全局中间件
```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { logger } from './common/middleware/logger.middleware';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
}
bootstrap();
```
## 守卫(Guards)的概念
守卫是一个使用 `@Injectable()` 装饰器的类,实现了 `CanActivate` 接口。守卫负责确定请求是否应该由路由处理程序处理。主要用于:
* 身份验证
* 授权
* 权限检查
### 守卫的基本结构
```typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return this.validateRequest(request);
}
private validateRequest(request: any): boolean {
// 验证逻辑
return true;
}
}
```
### 使用守卫
#### 在控制器上使用守卫
```typescript
@Controller('cats')
@UseGuards(AuthGuard)
export class CatsController {
@Get()
findAll() {
return 'This action returns all cats';
}
}
```
#### 在特定路由上使用守卫
```typescript
@Controller('cats')
export class CatsController {
@Get()
@UseGuards(AuthGuard)
findAll() {
return 'This action returns all cats';
}
}
```
#### 全局守卫
```typescript
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}
```
### 角色守卫示例
```typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
// 创建角色装饰器
import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
// 使用角色守卫
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {
@Get()
@Roles('admin')
findAll() {
return 'This action returns all cats';
}
}
```
## 中间件 vs 守卫
### 中间件的特点
* 在 Express 中间件链中执行
* 可以访问请求和响应对象
* 在路由处理程序之前执行
* 适用于全局逻辑(如日志记录、CORS)
* 不了解路由处理程序
### 守卫的特点
* 在 NestJS 依赖注入系统中执行
* 可以访问 ExecutionContext
* 在中间件之后、拦截器之前执行
* 适用于权限和授权逻辑
* 了解路由处理程序和类
### 选择指南
* 使用中间件:日志记录、请求解析、CORS、压缩等
* 使用守卫:身份验证、授权、权限检查等
## 自定义装饰器
### 从请求中提取用户
```typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
// 使用自定义装饰器
@Get()
findOne(@User('id') userId: string) {
return this.catsService.findOne(userId);
}
```
## JWT 认证守卫示例
```typescript
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token);
request['user'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
```
## 最佳实践
1. **职责分离**:中间件用于全局逻辑,守卫用于权限控制
2. **使用装饰器**:创建自定义装饰器简化守卫使用
3. **错误处理**:在守卫中抛出适当的异常
4. **性能考虑**:避免在守卫中执行耗时操作
5. **测试覆盖**:为中间件和守卫编写测试
6. **文档化**:为中间件和守卫添加清晰的文档
7. **避免过度使用**:只在需要时使用中间件和守卫
## 总结
NestJS 中间件和守卫系统提供了:
* 灵活的请求处理机制
* 强大的权限控制能力
* 清晰的关注点分离
* 易于测试和维护的代码结构
掌握中间件和守卫是构建安全、可维护的 NestJS 应用程序的关键。中间件处理全局逻辑,守卫处理权限控制,它们共同构成了应用程序的安全和功能基础。
服务端 · 2月17日 22:34
NestJS 微服务和架构如何设计?## 微服务架构概述
微服务架构是一种将应用程序构建为一组小型、独立服务的方法,每个服务运行在自己的进程中,通过轻量级机制(通常是 HTTP API 或消息队列)进行通信。NestJS 提供了完整的微服务支持,使开发者能够轻松构建可扩展的分布式系统。
## NestJS 微服务基础
### 安装依赖
```bash
npm install @nestjs/microservices
```
### 创建微服务
```typescript
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 8877,
},
},
);
await app.listen();
}
bootstrap();
```
### 混合应用(HTTP + 微服务)
```typescript
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const microservice = app.connectMicroservice<MicroserviceOptions>({
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 8877,
},
});
await app.startAllMicroservices();
await app.listen(3000);
}
bootstrap();
```
## 消息模式
### 1. 消息模式(Message Pattern)
```typescript
import { Controller } from '@nestjs/common';
import { EventPattern, Payload, Ctx, RmqContext } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
accumulate(@Payload() data: number[]): number {
return (data || []).reduce((a, b) => a + b, 0);
}
}
```
### 2. 事件模式(Event Pattern)
```typescript
import { Controller } from '@nestjs/common';
import { EventPattern, Payload } from '@nestjs/microservices';
@Controller()
export class NotificationController {
@EventPattern('user_created')
async handleUserCreated(@Payload() data: Record<string, unknown>) {
// 处理用户创建事件
}
}
```
## 传输层
### 1. TCP 传输
```typescript
{
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 8877,
},
}
```
### 2. Redis 传输
```typescript
{
transport: Transport.REDIS,
options: {
host: 'localhost',
port: 6379,
},
}
```
### 3. NATS 传输
```typescript
{
transport: Transport.NATS,
options: {
url: 'nats://localhost:4222',
},
}
```
### 4. MQTT 传输
```typescript
{
transport: Transport.MQTT,
options: {
url: 'mqtt://localhost:1883',
},
}
```
### 5. RabbitMQ 传输
```typescript
{
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'cats_queue',
queueOptions: {
durable: false,
},
},
}
```
### 6. Kafka 传输
```typescript
{
transport: Transport.KAFKA,
options: {
client: {
brokers: ['localhost:9092'],
},
consumer: {
groupId: 'my-consumer',
},
},
}
```
## 客户端(Client)
### 创建客户端代理
```typescript
import { Controller, Get } from '@nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';
@Controller()
export class AppController {
private client: ClientProxy;
constructor() {
this.client = ClientProxyFactory.create({
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 8877,
},
});
}
@Get()
async getSum() {
return this.client.send({ cmd: 'sum' }, [1, 2, 3, 4, 5]);
}
}
```
### 使用模块配置客户端
```typescript
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'MATH_SERVICE',
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 8877,
},
},
]),
],
controllers: [AppController],
})
export class AppModule {}
```
### 注入客户端
```typescript
import { Controller, Get, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
@Controller()
export class AppController {
constructor(@Inject('MATH_SERVICE') private client: ClientProxy) {}
@Get()
async getSum() {
return this.client.send({ cmd: 'sum' }, [1, 2, 3, 4, 5]);
}
}
```
## 消息确认
### 手动确认
```typescript
import { Controller } from '@nestjs/common';
import { EventPattern, Payload, Ctx, RmqContext } from '@nestjs/microservices';
@Controller()
export class NotificationController {
@EventPattern('notification_created')
async handleNotificationCreated(
@Payload() data: any,
@Ctx() context: RmqContext,
) {
const channel = context.getChannelRef();
const originalMsg = context.getMessage();
// 处理消息
await this.processNotification(data);
// 手动确认消息
channel.ack(originalMsg);
}
}
```
### 配置预取计数
```typescript
{
transport: Transport.RMQ,
options: {
urls: ['amqp://localhost:5672'],
queue: 'notifications_queue',
prefetchCount: 10,
queueOptions: {
durable: false,
},
},
}
```
## 微服务架构模式
### 1. API 网关模式
```typescript
import { Controller, Get, Param, Post, Body } from '@nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';
@Controller('api')
export class ApiController {
private userService: ClientProxy;
private orderService: ClientProxy;
constructor() {
this.userService = ClientProxyFactory.create({
transport: Transport.TCP,
options: { host: '127.0.0.1', port: 8877 },
});
this.orderService = ClientProxyFactory.create({
transport: Transport.TCP,
options: { host: '127.0.0.1', port: 8878 },
});
}
@Get('users/:id')
getUser(@Param('id') id: string) {
return this.userService.send({ cmd: 'get_user' }, { id });
}
@Post('orders')
createOrder(@Body() orderData: any) {
return this.orderService.send({ cmd: 'create_order' }, orderData);
}
}
```
### 2. 事件驱动架构
```typescript
// 订单服务
@Controller()
export class OrderController {
@EventPattern('order_created')
async handleOrderCreated(@Payload() order: any) {
// 处理订单创建事件
await this.sendNotification(order);
await this.updateInventory(order);
}
}
// 通知服务
@Controller()
export class NotificationController {
@EventPattern('order_created')
async handleOrderCreated(@Payload() order: any) {
// 发送通知
await this.sendEmail(order.userEmail, 'Order created');
}
}
// 库存服务
@Controller()
export class InventoryController {
@EventPattern('order_created')
async handleOrderCreated(@Payload() order: any) {
// 更新库存
await this.reduceStock(order.items);
}
}
```
### 3. CQRS 模式
```typescript
// Command Handler
@Controller()
export class OrderCommandController {
@MessagePattern({ cmd: 'create_order' })
async createOrder(@Payload() command: CreateOrderCommand) {
return this.commandBus.execute(command);
}
}
// Query Handler
@Controller()
export class OrderQueryController {
@MessagePattern({ cmd: 'get_order' })
async getOrder(@Payload() query: GetOrderQuery) {
return this.queryBus.execute(query);
}
}
```
## 服务发现
### 使用 Consul
```typescript
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
@Module({
imports: [
ClientsModule.register([
{
name: 'USER_SERVICE',
transport: Transport.TCP,
options: {
host: 'user-service',
port: 3000,
},
},
]),
],
})
export class AppModule {}
```
### 使用 Kubernetes Service Discovery
```typescript
{
transport: Transport.TCP,
options: {
host: process.env.USER_SERVICE_HOST || 'user-service',
port: parseInt(process.env.USER_SERVICE_PORT) || 3000,
},
}
```
## 分布式追踪
### 集成 OpenTelemetry
```typescript
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
import { trace } from '@opentelemetry/api';
@Controller()
export class OrderController {
@MessagePattern({ cmd: 'create_order' })
async createOrder(@Payload() data: any) {
const tracer = trace.getTracer('order-service');
const span = tracer.startSpan('create_order');
try {
const result = await this.orderService.create(data);
span.end();
return result;
} catch (error) {
span.recordException(error);
span.end();
throw error;
}
}
}
```
## 最佳实践
1. **服务边界**:明确定义服务边界和职责
2. **异步通信**:使用异步消息进行服务间通信
3. **幂等性**:确保操作是幂等的,可以安全重试
4. **错误处理**:实现适当的错误处理和重试机制
5. **监控**:实施全面的监控和日志记录
6. **配置管理**:使用配置中心管理服务配置
7. **版本控制**:实现 API 版本控制策略
8. **测试**:编写集成测试和契约测试
## 总结
NestJS 微服务和架构提供了:
* 完整的微服务支持
* 多种传输层选择
* 灵活的消息模式
* 强大的客户端代理
* 易于构建分布式系统
掌握 NestJS 微服务架构是构建可扩展、可维护的企业级应用的关键。通过合理使用微服务模式、事件驱动架构和最佳实践,可以构建出高性能、可靠的分布式系统。微服务架构使团队能够独立开发、部署和扩展各个服务,提高开发效率和系统弹性。
服务端 · 2月17日 22:33