NestJS-TypeScript-Tutorial
·
NestJS + TypeScript 入门完整教程
目录
什么是 NestJS
NestJS 是一个渐进式 Node.js 框架,用于构建高效、可扩展的服务端应用。它提供了开箱即用的应用程序架构,允许开发者和团队创建高度可测试、可维护和可扩展的应用程序。
NestJS vs Express vs Fastify
| 特性 | Express | Fastify | NestJS |
|---|---|---|---|
| 框架结构 | 轻量级、灵活 | 轻量级、高性能 | 完整框架,开箱即用 |
| 装饰器支持 | ❌ | ❌ | ✅ |
| TypeScript | ✅ (需手动配置) | ✅ (需手动配置) | ✅ (原生支持) |
| 依赖注入 | ❌ | ❌ | ✅ |
| 模块化设计 | ❌ | ❌ | ✅ |
| 学习曲线 | 平缓 | 平缓 | 陡峭 |
| 性能 | 中等 | 快 | 快 |
选择 NestJS 的理由:
- 企业级架构,适合大型项目
- 完整的生态系统(ORM、测试、CLI 工具)
- TypeScript 原生支持,强类型检查
- 依赖注入容器,易于测试和维护
- 活跃社区和完善文档
环境准备
系统要求
- Node.js 18.0+
- npm 9.0+ 或 yarn 4.0+
- PostgreSQL 12+ (可选,后续用到)
- Docker (可选,部署时用到)
检查环境
# 检查 Node.js 版本
node --version
# 检查 npm 版本
npm --version
# 安装 NestJS CLI (全局)
npm install -g @nestjs/cli
# 验证 NestJS CLI 安装
nest --version
项目初始化
方式一:使用 NestJS CLI (推荐)
# 创建新项目
nest new blog-api
# 进入项目目录
cd blog-api
# 选择包管理器 (npm 或 yarn),这里选择 npm
# 安装依赖
npm install
# 启动开发服务器
npm run start:dev
方式二:使用 Git 克隆官方模板
git clone https://github.com/nestjs/typescript-starter.git blog-api
cd blog-api
npm install
npm run start:dev
项目目录结构
blog-api/
├── src/
│ ├── app.controller.ts # 主控制器
│ ├── app.controller.spec.ts # 控制器单元测试
│ ├── app.module.ts # 根模块
│ ├── app.service.ts # 主服务
│ └── main.ts # 应用入口
├── test/
│ └── app.e2e-spec.ts # 端到端测试
├── dist/ # 编译后的代码 (生成)
├── node_modules/ # 依赖包
├── .env # 环境变量 (需创建)
├── nest-cli.json # NestJS CLI 配置
├── tsconfig.json # TypeScript 配置
└── package.json # 项目元数据
核心概念
1. 模块 (Module)
模块是用 @Module() 装饰器标注的类。用于组织相关功能。
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
// 导入其他模块
imports: [],
// 声明当前模块的控制器
controllers: [UsersController],
// 声明当前模块的提供者 (服务)
providers: [UsersService],
// 导出提供者给其他模块使用
exports: [UsersService],
})
export class UsersModule {}
2. 控制器 (Controller)
负责处理 HTTP 请求和返回响应。
// src/users/users.controller.ts
import {
Controller,
Get,
Post,
Put,
Delete,
Param,
Body,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
// 依赖注入:自动实例化 UsersService
constructor(private readonly usersService: UsersService) {}
// GET /users - 获取所有用户
@Get()
findAll() {
return this.usersService.findAll();
}
// GET /users/:id - 获取单个用户
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
// POST /users - 创建用户
@Post()
@HttpCode(HttpStatus.CREATED) // 响应状态码 201
create(@Body() createUserDto: any) {
return this.usersService.create(createUserDto);
}
// PUT /users/:id - 更新用户
@Put(':id')
update(
@Param('id') id: string,
@Body() updateUserDto: any
) {
return this.usersService.update(+id, updateUserDto);
}
// DELETE /users/:id - 删除用户
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT) // 响应状态码 204
remove(@Param('id') id: string) {
return this.usersService.remove(+id);
}
}
3. 服务 (Service)
包含业务逻辑,通常由控制器调用。
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
// 模拟数据库
private users: any[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
// 获取所有用户
findAll() {
return this.users;
}
// 获取单个用户
findOne(id: number) {
return this.users.find(user => user.id === id);
}
// 创建用户
create(createUserDto: any) {
const newUser = {
id: Math.max(...this.users.map(u => u.id)) + 1,
...createUserDto,
};
this.users.push(newUser);
return newUser;
}
// 更新用户
update(id: number, updateUserDto: any) {
const user = this.findOne(id);
if (!user) return null;
Object.assign(user, updateUserDto);
return user;
}
// 删除用户
remove(id: number) {
const index = this.users.findIndex(user => user.id === id);
if (index > -1) {
return this.users.splice(index, 1);
}
return null;
}
}
4. 装饰器 (Decorators)
装饰器是 TypeScript 功能,NestJS 大量使用它来标记类和方法。
// 常用装饰器对照表
@Module() // 声明模块
@Controller() // 声明控制器
@Injectable() // 声明提供者 (通常是服务)
@Get() // GET 请求
@Post() // POST 请求
@Put() // PUT 请求
@Delete() // DELETE 请求
@Param() // 路由参数
@Body() // 请求体
@Query() // 查询参数
@Headers() // 请求头
@HttpCode() // HTTP 状态码
@Inject() // 注入依赖
@Middleware() // 中间件
@Guard() // 守卫 (如认证)
@Pipe() // 管道 (验证、转换)
@UseInterceptors() // 拦截器
@UseGuards() // 使用守卫
@UsePipes() // 使用管道
5. DTO (Data Transfer Object)
定义数据的形状,用于 API 请求和响应验证。
// src/users/dto/create-user.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
}
// src/users/dto/update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
// 继承 CreateUserDto,所有字段都变为可选
export class UpdateUserDto extends PartialType(CreateUserDto) {}
实战项目:博客 API
现在让我们构建一个简单的博客 API,包含用户、文章、评论功能。
第一步:生成模块和资源
使用 NestJS CLI 快速生成模块、控制器、服务、DTO:
# 生成文章模块 (包含控制器、服务、DTO)
nest g module posts
nest g controller posts
nest g service posts
# 生成评论模块
nest g module comments
nest g controller comments
nest g service comments
# 生成用户模块
nest g module users
nest g controller users
nest g service users
第二步:创建 DTO
// src/posts/dto/create-post.dto.ts
import { IsString, IsOptional, MinLength, MaxLength } from 'class-validator';
export class CreatePostDto {
@IsString()
@MinLength(5)
title: string;
@IsString()
@MinLength(10)
content: string;
@IsString()
@IsOptional()
@MaxLength(200)
summary?: string;
@IsString()
@IsOptional()
author?: string;
}
// src/posts/dto/update-post.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreatePostDto } from './create-post.dto';
export class UpdatePostDto extends PartialType(CreatePostDto) {}
// src/comments/dto/create-comment.dto.ts
import { IsString, IsNumber, MinLength } from 'class-validator';
export class CreateCommentDto {
@IsNumber()
postId: number;
@IsString()
author: string;
@IsString()
@MinLength(3)
content: string;
}
第三步:实现服务
// src/posts/posts.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
export interface Post {
id: number;
title: string;
content: string;
summary?: string;
author: string;
createdAt: Date;
updatedAt: Date;
}
@Injectable()
export class PostsService {
// 模拟数据库存储
private posts: Post[] = [
{
id: 1,
title: 'NestJS 入门指南',
content: '这是一篇关于 NestJS 框架的介绍文章...',
summary: 'NestJS 是一个渐进式 Node.js 框架',
author: 'Admin',
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01'),
},
];
// 获取所有文章
findAll(): Post[] {
return this.posts;
}
// 根据 ID 获取单个文章
findOne(id: number): Post {
const post = this.posts.find(p => p.id === id);
if (!post) {
throw new NotFoundException(`文章 ID ${id} 不存在`);
}
return post;
}
// 创建文章
create(createPostDto: CreatePostDto): Post {
const newPost: Post = {
id: Math.max(...this.posts.map(p => p.id), 0) + 1,
...createPostDto,
author: createPostDto.author || 'Anonymous',
createdAt: new Date(),
updatedAt: new Date(),
};
this.posts.push(newPost);
return newPost;
}
// 更新文章
update(id: number, updatePostDto: UpdatePostDto): Post {
const post = this.findOne(id);
Object.assign(post, updatePostDto, { updatedAt: new Date() });
return post;
}
// 删除文章
remove(id: number): Post {
const post = this.findOne(id);
const index = this.posts.indexOf(post);
this.posts.splice(index, 1);
return post;
}
}
// src/comments/comments.service.ts
import { Injectable, BadRequestException } from '@nestjs/common';
import { CreateCommentDto } from './dto/create-comment.dto';
import { PostsService } from '../posts/posts.service';
export interface Comment {
id: number;
postId: number;
author: string;
content: string;
createdAt: Date;
}
@Injectable()
export class CommentsService {
private comments: Comment[] = [];
private commentId = 1;
// 注入 PostsService 用于验证文章是否存在
constructor(private postsService: PostsService) {}
// 获取特定文章的所有评论
findByPostId(postId: number): Comment[] {
this.postsService.findOne(postId); // 验证文章存在
return this.comments.filter(c => c.postId === postId);
}
// 创建评论
create(createCommentDto: CreateCommentDto): Comment {
// 验证文章是否存在
this.postsService.findOne(createCommentDto.postId);
const newComment: Comment = {
id: this.commentId++,
...createCommentDto,
createdAt: new Date(),
};
this.comments.push(newComment);
return newComment;
}
// 删除评论
remove(id: number): Comment {
const index = this.comments.findIndex(c => c.id === id);
if (index === -1) {
throw new BadRequestException(`评论 ID ${id} 不存在`);
}
return this.comments.splice(index, 1)[0];
}
}
第四步:实现控制器
// src/posts/posts.controller.ts
import {
Controller,
Get,
Post,
Body,
Param,
Put,
Delete,
HttpCode,
HttpStatus,
ValidationPipe,
ParseIntPipe,
} from '@nestjs/common';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
// GET /posts - 获取所有文章
@Get()
findAll() {
return this.postsService.findAll();
}
// GET /posts/:id - 获取单个文章
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
// ParseIntPipe 自动将 string 转为 number
return this.postsService.findOne(id);
}
// POST /posts - 创建文章
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body(ValidationPipe) createPostDto: CreatePostDto) {
// ValidationPipe 自动验证 DTO
return this.postsService.create(createPostDto);
}
// PUT /posts/:id - 更新文章
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body(ValidationPipe) updatePostDto: UpdatePostDto,
) {
return this.postsService.update(id, updatePostDto);
}
// DELETE /posts/:id - 删除文章
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id', ParseIntPipe) id: number) {
return this.postsService.remove(id);
}
}
// src/comments/comments.controller.ts
import {
Controller,
Get,
Post,
Body,
Param,
Delete,
HttpCode,
HttpStatus,
ValidationPipe,
ParseIntPipe,
} from '@nestjs/common';
import { CommentsService } from './comments.service';
import { CreateCommentDto } from './dto/create-comment.dto';
@Controller('comments')
export class CommentsController {
constructor(private readonly commentsService: CommentsService) {}
// GET /comments/post/:postId - 获取特定文章的评论
@Get('post/:postId')
findByPostId(@Param('postId', ParseIntPipe) postId: number) {
return this.commentsService.findByPostId(postId);
}
// POST /comments - 创建评论
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body(ValidationPipe) createCommentDto: CreateCommentDto) {
return this.commentsService.create(createCommentDto);
}
// DELETE /comments/:id - 删除评论
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id', ParseIntPipe) id: number) {
return this.commentsService.remove(id);
}
}
第五步:配置根模块
// src/app.module.ts
import { Module } from '@nestjs/common';
import { PostsModule } from './posts/posts.module';
import { CommentsModule } from './comments/comments.module';
import { UsersModule } from './users/users.module';
@Module({
imports: [PostsModule, CommentsModule, UsersModule],
controllers: [],
providers: [],
})
export class AppModule {}
确保 comments.module.ts 导入 PostsModule:
// src/comments/comments.module.ts
import { Module } from '@nestjs/common';
import { CommentsController } from './comments.controller';
import { CommentsService } from './comments.service';
import { PostsModule } from '../posts/posts.module';
@Module({
imports: [PostsModule], // 导入 PostsModule 使用 PostsService
controllers: [CommentsController],
providers: [CommentsService],
})
export class CommentsModule {}
第六步:测试 API
启动开发服务器:
npm run start:dev
使用 curl 或 Postman 测试:
# 获取所有文章
curl http://localhost:3000/posts
# 创建文章
curl -X POST http://localhost:3000/posts \
-H "Content-Type: application/json" \
-d '{
"title": "TypeScript 最佳实践",
"content": "这是一篇关于 TypeScript 的文章...",
"author": "Dev Team"
}'
# 获取评论
curl http://localhost:3000/comments/post/1
# 创建评论
curl -X POST http://localhost:3000/comments \
-H "Content-Type: application/json" \
-d '{
"postId": 1,
"author": "User123",
"content": "很有用的文章!"
}'
# 删除评论
curl -X DELETE http://localhost:3000/comments/1
数据库集成 (PostgreSQL)
现在将内存数据源替换为真实数据库。我们使用 TypeORM 和 PostgreSQL。
安装依赖
npm install @nestjs/typeorm typeorm pg
配置数据库连接
创建 .env 文件:
# .env
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=blog_db
DATABASE_USER=postgres
DATABASE_PASSWORD=password123
DATABASE_SYNCHRONIZE=true
安装 dotenv 包用于读取环境变量:
npm install dotenv
定义实体
// src/posts/entities/post.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
} from 'typeorm';
import { Comment } from '../../comments/entities/comment.entity';
@Entity('posts')
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 255 })
title: string;
@Column({ type: 'text' })
content: string;
@Column({ type: 'varchar', length: 200, nullable: true })
summary: string;
@Column({ type: 'varchar', length: 100, default: 'Anonymous' })
author: string;
// 一对多关系:一篇文章有多条评论
@OneToMany(() => Comment, comment => comment.post, { cascade: true })
comments: Comment[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// src/comments/entities/comment.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { Post } from '../../posts/entities/post.entity';
@Entity('comments')
export class Comment {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 100 })
author: string;
@Column({ type: 'text' })
content: string;
@CreateDateColumn()
createdAt: Date;
// 多对一关系:多条评论属于一篇文章
@ManyToOne(() => Post, post => post.comments, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'postId' })
post: Post;
@Column()
postId: number;
}
更新 App Module
// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostsModule } from './posts/posts.module';
import { CommentsModule } from './comments/comments.module';
import { Post } from './posts/entities/post.entity';
import { Comment } from './comments/entities/comment.entity';
@Module({
imports: [
// 配置 TypeORM
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.DATABASE_HOST || 'localhost',
port: parseInt(process.env.DATABASE_PORT || '5432'),
username: process.env.DATABASE_USER || 'postgres',
password: process.env.DATABASE_PASSWORD || 'password',
database: process.env.DATABASE_NAME || 'blog_db',
entities: [Post, Comment],
synchronize: process.env.NODE_ENV !== 'production', // 自动同步数据库schema
logging: false,
}),
PostsModule,
CommentsModule,
],
})
export class AppModule {}
更新模块以使用 TypeORM
// src/posts/posts.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { Post } from './entities/post.entity';
@Module({
imports: [TypeOrmModule.forFeature([Post])],
controllers: [PostsController],
providers: [PostsService],
exports: [PostsService],
})
export class PostsModule {}
更新服务使用数据库
// src/posts/posts.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Post } from './entities/post.entity';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
@Injectable()
export class PostsService {
constructor(
@InjectRepository(Post)
private postsRepository: Repository<Post>,
) {}
// 获取所有文章
async findAll(): Promise<Post[]> {
return await this.postsRepository.find({
relations: ['comments'], // 关联加载评论
});
}
// 获取单个文章
async findOne(id: number): Promise<Post> {
const post = await this.postsRepository.findOne({
where: { id },
relations: ['comments'],
});
if (!post) {
throw new NotFoundException(`文章 ID ${id} 不存在`);
}
return post;
}
// 创建文章
async create(createPostDto: CreatePostDto): Promise<Post> {
const post = this.postsRepository.create(createPostDto);
return await this.postsRepository.save(post);
}
// 更新文章
async update(id: number, updatePostDto: UpdatePostDto): Promise<Post> {
const post = await this.findOne(id);
Object.assign(post, updatePostDto);
return await this.postsRepository.save(post);
}
// 删除文章
async remove(id: number): Promise<void> {
const post = await this.findOne(id);
await this.postsRepository.remove(post);
}
}
// src/comments/comments.service.ts
import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Comment } from './entities/comment.entity';
import { CreateCommentDto } from './dto/create-comment.dto';
import { PostsService } from '../posts/posts.service';
@Injectable()
export class CommentsService {
constructor(
@InjectRepository(Comment)
private commentsRepository: Repository<Comment>,
private postsService: PostsService,
) {}
// 获取特定文章的评论
async findByPostId(postId: number): Promise<Comment[]> {
await this.postsService.findOne(postId); // 验证文章存在
return await this.commentsRepository.find({ where: { postId } });
}
// 创建评论
async create(createCommentDto: CreateCommentDto): Promise<Comment> {
await this.postsService.findOne(createCommentDto.postId);
const comment = this.commentsRepository.create(createCommentDto);
return await this.commentsRepository.save(comment);
}
// 删除评论
async remove(id: number): Promise<void> {
const result = await this.commentsRepository.delete(id);
if (result.affected === 0) {
throw new BadRequestException(`评论 ID ${id} 不存在`);
}
}
}
更新控制器为异步操作
// src/posts/posts.controller.ts
import {
Controller,
Get,
Post,
Body,
Param,
Put,
Delete,
HttpCode,
HttpStatus,
ValidationPipe,
ParseIntPipe,
} from '@nestjs/common';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Get()
async findAll() {
return await this.postsService.findAll();
}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return await this.postsService.findOne(id);
}
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body(ValidationPipe) createPostDto: CreatePostDto) {
return await this.postsService.create(createPostDto);
}
@Put(':id')
async update(
@Param('id', ParseIntPipe) id: number,
@Body(ValidationPipe) updatePostDto: UpdatePostDto,
) {
return await this.postsService.update(id, updatePostDto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
async remove(@Param('id', ParseIntPipe) id: number) {
await this.postsService.remove(id);
}
}
启动 PostgreSQL
使用 Docker Compose 快速启动 PostgreSQL:
# 先启动容器
docker run --name postgres-blog \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=password123 \
-e POSTGRES_DB=blog_db \
-p 5432:5432 \
-d postgres:15-alpine
# 检查数据库连接
psql -h localhost -U postgres -d blog_db
或使用 docker-compose.yml (推荐):
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password123
POSTGRES_DB: blog_db
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
启动:
docker-compose up -d
Docker 部署
创建 Dockerfile
# Dockerfile
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
# 复制 package.json 和 package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 编译 TypeScript
RUN npm run build
# 运行阶段
FROM node:18-alpine
WORKDIR /app
# 从构建阶段复制编译后的代码
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
# 暴露端口
EXPOSE 3000
# 启动应用
CMD ["node", "dist/main.js"]
创建 .dockerignore
.dockerignore
node_modules
npm-debug.log
dist
.env
.git
.gitignore
README.md
test
多容器部署(应用 + 数据库)
# docker-compose.prod.yml
version: '3.8'
services:
# PostgreSQL 数据库
postgres:
image: postgres:15-alpine
container_name: blog-postgres
environment:
POSTGRES_USER: ${DATABASE_USER}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: ${DATABASE_NAME}
ports:
- "${DATABASE_PORT}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- blog-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USER}"]
interval: 10s
timeout: 5s
retries: 5
# NestJS 应用
api:
build:
context: .
dockerfile: Dockerfile
container_name: blog-api
ports:
- "3000:3000"
environment:
NODE_ENV: production
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_USER: ${DATABASE_USER}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
DATABASE_NAME: ${DATABASE_NAME}
depends_on:
postgres:
condition: service_healthy
networks:
- blog-network
restart: unless-stopped
volumes:
postgres_data:
networks:
blog-network:
driver: bridge
创建生产环境 .env 文件
# .env.production
NODE_ENV=production
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_NAME=blog_db
DATABASE_USER=postgres
DATABASE_PASSWORD=your-secure-password
DATABASE_SYNCHRONIZE=false
构建和运行
# 构建 Docker 镜像
docker build -t blog-api:1.0 .
# 运行容器 (开发环境)
docker run -d \
--name blog-api \
-p 3000:3000 \
-e DATABASE_HOST=host.docker.internal \
-e DATABASE_PORT=5432 \
-e DATABASE_NAME=blog_db \
-e DATABASE_USER=postgres \
-e DATABASE_PASSWORD=password123 \
blog-api:1.0
# 或使用 docker-compose (推荐)
docker-compose -f docker-compose.prod.yml up -d
# 查看日志
docker-compose -f docker-compose.prod.yml logs -f api
# 停止服务
docker-compose -f docker-compose.prod.yml down
常用 Docker 命令
# 查看运行中的容器
docker ps
# 查看容器日志
docker logs container-name
docker logs -f container-name # 实时查看
# 进入容器交互式 shell
docker exec -it container-name sh
# 停止/启动容器
docker stop container-name
docker start container-name
# 删除容器
docker rm container-name
# 删除镜像
docker rmi image-name
生产环境最佳实践
1. 环境变量管理
// src/config/env.ts
import { plainToClass } from 'class-transformer';
import { IsEnum, IsNumber, IsString, validateSync } from 'class-validator';
enum Environment {
Development = 'development',
Production = 'production',
Test = 'test',
}
class EnvironmentVariables {
@IsEnum(Environment)
NODE_ENV: Environment;
@IsNumber()
PORT: number;
@IsString()
DATABASE_HOST: string;
@IsNumber()
DATABASE_PORT: number;
@IsString()
DATABASE_NAME: string;
@IsString()
DATABASE_USER: string;
@IsString()
DATABASE_PASSWORD: string;
}
export function validate(config: Record<string, unknown>) {
const validatedConfig = plainToClass(EnvironmentVariables, config, {
enableImplicitConversion: true,
});
const errors = validateSync(validatedConfig, {
skipMissingProperties: false,
});
if (errors.length > 0) {
throw new Error(errors.toString());
}
return validatedConfig;
}
2. 日志配置
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger, ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const logger = new Logger('Bootstrap');
// 全局验证管道
app.useGlobalPipes(
new ValidationPipe({
transform: true,
forbidNonWhitelisted: true,
whitelist: true,
}),
);
const port = process.env.PORT || 3000;
await app.listen(port);
logger.log(`应用启动成功,监听端口: ${port}`);
logger.log(`环境: ${process.env.NODE_ENV || 'development'}`);
}
bootstrap();
3. 异常处理
// src/common/filters/http-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(HttpExceptionFilter.name);
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const exceptionResponse = exception.getResponse();
const errorMessage =
typeof exceptionResponse === 'object'
? (exceptionResponse as any).message
: exceptionResponse;
this.logger.error(`${request.method} ${request.path}`, errorMessage);
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.path,
message: errorMessage,
});
}
}
总结
关键要点
- NestJS 提供了企业级架构,特别适合大型、复杂的应用
- TypeScript 提供强类型检查,提高代码质量
- 装饰器和依赖注入 使代码更模块化、易于测试
- TypeORM 简化数据库操作
- Docker 简化部署和环境管理
常见踩坑
- 忘记在模块中导入依赖的模块 → 导致找不到提供者
- 不使用 DTO 验证 → 数据不安全
- 数据库配置写死 → 不同环境无法切换
- 忘记设置数据库关系 → 查询性能差
- Docker 中数据库连接地址用 localhost → 跨容器通信失败
下一步学习
- 认证和授权 (JWT)
- 缓存 (Redis)
- 消息队列 (RabbitMQ)
- WebSocket 实时通信
- GraphQL
- 微服务架构
- 单元测试和集成测试
参考资源
祝你学习愉快!🚀
更多推荐
所有评论(0)