文章目录
一、NestJS 的一些关键特性和概念
NestJS 是一个用于构建高效、可扩展的服务器端应用程序的渐进式 Node.js 框架。它使用现代 JavaScript(ES6/ES7 特性)编写,并结合了面向对象编程(OOP)、函数式编程和泛型等概念。NestJS 提供了一套结构化的、模块化的解决方案,让开发者可以使用熟悉的语言特性来创建应用程序。
以下是 NestJS 的一些关键特性和概念:
-
模块化架构:
- 应用程序被组织成模块,每个模块负责一组相关的功能。
- 模块是独立的,但可以通过导入其他模块来共享功能或服务。
- 这种设计有助于保持代码的整洁和易于维护。
-
依赖注入:
- NestJS 内置了强大的依赖注入机制,这使得组件之间的解耦变得容易。
- 依赖注入帮助管理对象的创建和生命周期,使代码更易测试。
-
控制器 (Controllers):
- 控制器处理传入的请求并返回响应给客户端。
- 它们通常定义路由及其行为。
-
提供者 (Providers):
- 提供者包括服务(Services),它们封装业务逻辑。
- 提供者可以在多个模块之间共享,并且可以通过依赖注入轻松地访问。
-
中间件 (Middleware):
- 中间件函数在请求到达路由处理器之前执行。
- 它们可以用来执行诸如身份验证、日志记录等任务。
-
守卫 (Guards):
- 守卫用于保护路由,只有满足特定条件时才允许访问。
- 它们可以用于实现权限控制等功能。
-
异常过滤器 (Exception Filters):
- 异常过滤器用于捕获和处理抛出的异常。
- 它们可以全局应用,也可以针对特定的控制器或路由。
-
管道 (Pipes):
- 管道用于数据转换和验证。
- 它们确保传入的数据符合预期格式。
-
拦截器 (Interceptors):
- 拦截器可以修改进入或离开控制器处理程序的请求或响应。
- 它们对于横切关注点如日志、性能监控非常有用。
-
异步支持:
- NestJS 原生支持异步操作,例如通过 Promises 或 Observables 处理异步结果。
-
微服务支持:
- 支持通过多种协议(如 gRPC, Redis, MQTT, 等)与微服务进行通信。
-
WebSockets:
- 内建对 WebSocket 的支持,便于实时通信。
-
CLI 工具:
- NestJS CLI 提供命令行工具来加速开发过程,如生成新模块、控制器、服务等。
-
类型安全:
- 使用 TypeScript 编写,提供了静态类型检查和其他高级特性。
-
测试:
- 提供了内置的支持以方便单元测试、集成测试和端到端测试。
这些特性共同作用,使得 NestJS 成为一个强大而灵活的框架,适用于各种规模的应用程序。
二、创建一个简单的 NestJS 应用程序
下面让我们来创建一个简单的 NestJS 应用程序,包括设置项目、创建模块、控制器和服务,并运行应用程序。
-
安装 Nest CLI:
首先,你需要全局安装 Nest CLI 工具,这将帮助你快速搭建和管理 NestJS 项目。npm i -g @nestjs/cli
-
创建新项目:
使用 Nest CLI 创建新的 NestJS 项目。CLI 将引导你完成项目的配置。nest new project-name
这条命令会创建一个名为
project-name
的目录,并在其中初始化一个新的 NestJS 项目。 -
项目结构:
新建的项目会有一个标准的文件夹结构,它通常包含以下文件夹:src/
:源代码存放的地方。test/
:测试代码存放的地方。- 其他配置文件如
package.json
,.gitignore
,tsconfig.json
等。
-
启动应用:
安装完所有依赖后,可以通过以下命令启动开发服务器:npm run start
或者使用监视模式,在代码更改时自动重启服务器:
npm run start:dev
-
创建模块、控制器和服务:
在src/
目录下,你可以使用 CLI 来生成模块、控制器和服务。nest generate module my-module nest generate controller my-controller nest generate service my-service
-
定义路由和处理逻辑:
在生成的控制器中定义 HTTP 路由及其处理函数。例如,你可以在my-controller.controller.ts
中添加如下代码:import { Controller, Get } from '@nestjs/common'; @Controller('my') export class MyController { @Get('resource') getHello(): string { return 'Hello World!'; } }
-
注册模块:
不要忘记在主模块(通常是AppModule
)中导入新创建的模块,以使其可用。import { Module } from '@nestjs/common'; import { MyModule } from './my/my.module'; @Module({ imports: [MyModule], controllers: [], providers: [], }) export class AppModule {}
-
访问 API:
启动应用程序后,你可以通过浏览器或者像 Postman 这样的工具来访问 http://localhost:3000/my/resource,你应该会看到返回的 “Hello World!” 消息。
这只是 NestJS 功能的一个简短介绍。NestJS 提供了丰富的特性和工具来支持复杂的业务逻辑和大规模应用的开发。
三、NestJS 高级应用
接下来我们可以深入探讨一些更高级的主题,比如依赖注入、服务之间的通信、使用数据库、以及如何进行单元测试等。
依赖注入和服务之间的通信
依赖注入(Dependency Injection)
依赖注入是 NestJS 的核心特性之一,它允许你以一种松耦合的方式定义和管理应用程序中的依赖关系。在 NestJS 中,你可以通过构造函数、属性或方法参数来注入依赖项。NestJS 使用反射机制来解析这些依赖,并自动提供所需的实例。
定义一个服务
首先,让我们定义一个简单的服务 AppService
,它将包含一些业务逻辑。
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
@Injectable()
装饰器用于标记类可以作为依赖被注入到其他类中。
在控制器中使用服务
然后,在控制器中通过构造函数注入该服务:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('app')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
在这个例子中,AppService
被注入到了 AppController
中,并且可以在控制器的方法中使用。
服务之间的通信
当你有一个以上的服务时,它们之间可能会需要相互通信。例如,你可能有一个负责处理用户数据的 UserService
和另一个负责发送电子邮件通知的 EmailService
。
创建多个服务
假设我们有两个服务:UserService
和 EmailService
。
// user.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
getUsers() {
// 模拟获取用户列表
return ['Alice', 'Bob'];
}
}
// email.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class EmailService {
sendEmail(to: string, content: string) {
console.log(`Sending email to ${to}: ${content}`);
}
}
让服务相互依赖
现在,如果你想在 UserService
中调用 EmailService
来给新用户发送欢迎邮件,你可以这样做:
import { Injectable } from '@nestjs/common';
import { EmailService } from './email.service';
@Injectable()
export class UserService {
constructor(private readonly emailService: EmailService) {}
createUser(name: string) {
// 模拟创建用户
console.log(`User created: ${name}`);
// 发送欢迎邮件
this.emailService.sendEmail(name, 'Welcome to our platform!');
}
getUsers() {
// 模拟获取用户列表
return ['Alice', 'Bob'];
}
}
这里,UserService
构造函数接收了 EmailService
实例,从而可以在 createUser
方法中调用它的 sendEmail
方法。
注册服务
最后,不要忘记在模块中注册这些服务,以便它们能够被正确地注入。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserService } from './user.service';
import { EmailService } from './email.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService, UserService, EmailService],
})
export class AppModule {}
通过这种方式,你可以轻松地实现服务间的解耦,并且每个服务都可以专注于自己的职责。这不仅提高了代码的可维护性和可读性,还使得单元测试变得更加简单,因为你可以轻松地模拟依赖的服务。
接下来我们可以深入到如何在 NestJS 中集成数据库、进行单元测试、使用中间件、以及一些最佳实践。
数据库集成
NestJS 本身不绑定任何特定的数据库技术,但它提供了非常灵活的方式与多种数据库交互。通常情况下,我们会使用 ORM(对象关系映射)工具如 TypeORM 或 Sequelize 来简化数据库操作。这里我们将以 TypeORM 为例来说明如何集成数据库。
安装 TypeORM 和数据库驱动
首先,你需要安装 TypeORM 和你选择的数据库驱动程序(例如 PostgreSQL、MySQL 等)。在这个例子中,我们将使用 MySQL 数据库:
npm install @nestjs/typeorm typeorm mysql2 --save
@nestjs/typeorm
是 NestJS 的 TypeORM 模块,而 mysql2
是 MySQL 的 Node.js 驱动。
配置数据库连接
接下来,在你的主模块文件中配置数据库连接。你可以通过 TypeOrmModule.forRoot()
方法提供数据库连接信息。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
autoLoadEntities: true,
synchronize: true, // 注意:仅用于开发环境!生产环境中应禁用此选项。
}),
],
})
export class AppModule {}
autoLoadEntities: true
告诉 TypeORM 自动加载实体,而 synchronize: true
则允许 TypeORM 同步数据库模式(即根据实体创建或更新表结构)。注意:在生产环境中不应启用 synchronize
,因为它可能会无意中修改或删除数据。
创建实体
实体是表示数据库表的对象模型。假设我们要创建一个 User
实体:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
}
然后我们需要将这个实体注册到模块中:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
@Module({
imports: [
TypeOrmModule.forFeature([User]),
],
})
export class UsersModule {}
TypeOrmModule.forFeature([User])
将 User
实体添加到当前模块的上下文中,使得它可以被该模块中的其他组件访问。
使用 Repository 操作数据库
TypeORM 提供了一个称为 Repository 的接口,它封装了许多常用的数据库操作方法。你可以在服务中注入 Repository 来执行 CRUD 操作。
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User> {
return this.usersRepository.findOneBy({ id });
}
async remove(id: number): Promise<void> {
await this.usersRepository.delete(id);
}
}
在这个例子中,UserService
使用了 usersRepository
来查询所有用户、查找单个用户和删除用户。
路由和控制器
最后,你需要定义路由来处理 HTTP 请求,并将这些请求映射到适当的服务方法上。例如:
import { Controller, Get, Param, Delete } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
findAll() {
return this.userService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(+id);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove(+id);
}
}
这将设置几个 RESTful API 端点,可以用来获取用户列表、获取单个用户信息和删除用户。
以上就是如何在 NestJS 中集成数据库的基本步骤。
四、 应用案例
下面是一个更综合详实的应用案例,涵盖从项目创建、数据库集成到实现基本的 CRUD 操作,以及如何进行单元测试。我们将构建一个简单的任务管理应用(Task Management Application),它允许用户创建、读取、更新和删除任务。
项目初始化
-
安装 Nest CLI 并创建新项目:
npm i -g @nestjs/cli nest new task-management-app
-
进入项目目录并启动开发服务器:
cd task-management-app npm run start:dev
数据库集成
我们将使用 MySQL 数据库,并通过 TypeORM 进行 ORM 操作。
安装依赖
npm install @nestjs/typeorm typeorm mysql2 --save
配置数据库连接
在 app.module.ts
中配置数据库连接:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TaskModule } from './task/task.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'task_management',
autoLoadEntities: true,
synchronize: true, // 注意:仅用于开发环境!生产环境中应禁用此选项。
}),
TaskModule,
],
})
export class AppModule {}
创建任务实体
在 src
目录下创建 task
文件夹,并在其中添加 task.entity.ts
文件:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Task {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 255 })
title: string;
@Column({ type: 'text' })
description: string;
@Column({ default: false })
completed: boolean;
}
创建任务模块和服务
使用 CLI 创建模块和服务:
nest generate module task
nest generate service task
在 task.service.ts
中定义服务逻辑:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Task } from './task.entity';
@Injectable()
export class TaskService {
constructor(
@InjectRepository(Task)
private tasksRepository: Repository<Task>,
) {}
findAll(): Promise<Task[]> {
return this.tasksRepository.find();
}
findOne(id: number): Promise<Task> {
return this.tasksRepository.findOneBy({ id });
}
async remove(id: number): Promise<void> {
await this.tasksRepository.delete(id);
}
async create(title: string, description: string): Promise<Task> {
const task = this.tasksRepository.create({ title, description });
return this.tasksRepository.save(task);
}
async update(id: number, attrs: Partial<Task>): Promise<Task> {
const task = await this.findOne(id);
if (!task) {
throw new NotFoundException('Task not found');
}
Object.assign(task, attrs);
return this.tasksRepository.save(task);
}
}
创建控制器
使用 CLI 创建控制器:
nest generate controller task
在 task.controller.ts
中定义路由处理逻辑:
import { Controller, Get, Post, Body, Param, Patch, Delete } from '@nestjs/common';
import { TaskService } from './task.service';
import { Task } from './task.entity';
@Controller('tasks')
export class TaskController {
constructor(private readonly taskService: TaskService) {}
@Get()
findAll(): Promise<Task[]> {
return this.taskService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string): Promise<Task> {
return this.taskService.findOne(+id);
}
@Post()
create(@Body() body: { title: string; description: string }): Promise<Task> {
return this.taskService.create(body.title, body.description);
}
@Patch(':id')
update(
@Param('id') id: string,
@Body() body: Partial<Task>
): Promise<Task> {
return this.taskService.update(+id, body);
}
@Delete(':id')
async remove(@Param('id') id: string): Promise<void> {
await this.taskService.remove(+id);
}
}
单元测试
NestJS 支持开箱即用的单元测试。我们可以为 TaskService
编写测试用例。首先,创建测试文件 task.service.spec.ts
:
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { TaskService } from './task.service';
import { Task } from './task.entity';
import { Repository } from 'typeorm';
describe('TaskService', () => {
let service: TaskService;
let repository: Repository<Task>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
TaskService,
{
provide: getRepositoryToken(Task),
useClass: Repository,
},
],
}).compile();
service = module.get<TaskService>(TaskService);
repository = module.get<Repository<Task>>(getRepositoryToken(Task));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
// Add more tests here...
});
你可以继续在这个测试文件中添加更多针对 TaskService
的测试用例,例如模拟数据插入、查询、更新和删除操作。
注册模块
确保在 app.module.ts
中导入了 TaskModule
:
import { TaskModule } from './task/task.module';
@Module({
imports: [TypeOrmModule.forRoot(), TaskModule],
})
export class AppModule {}
这个案例展示了如何使用 NestJS 构建一个简单的任务管理应用程序,包括设置数据库连接、创建实体、编写服务和控制器来处理 CRUD 操作,以及如何编写单元测试。当然,实际应用中你还需要考虑更多的细节,比如错误处理、验证、安全性等。
————————————————
最后我们放松一下眼睛