本文详细介绍了如何从环境搭建到实战案例,一步步进行Nest项目实战,包括基础概念、数据库连接、用户管理功能的实现,以及如何进行测试和部署。通过这些步骤,开发者可以轻松构建和部署一个完整的用户管理系统。
1. Nest.js简介与环境搭建
1.1 什么是Nest.js
Nest.js 是一个使用 TypeScript 构建的高效、可扩展、结构化的Web应用程序框架。它基于 Angular 的设计模式,结合了面向对象编程、函数式编程及装饰器模式,旨在提供高度模块化的结构,使得开发者可以高效地开发复杂的Web应用。Nest.js 支持多种HTTP协议(如REST、GraphQL)和数据库(如MySQL、MongoDB),并且提供了丰富的中间件和工具库,方便开发者快速构建、测试和部署应用。
1.2 安装Node.js和NPM
要开始使用 Nest.js,首先需要安装Node.js和npm(Node.js的包管理器)。以下是安装步骤:
- 访问Node.js官网 https://nodejs.org/
- 点击“LTS”版本(长期支持版本),下载并安装。
- 安装完成后,打开命令行工具(如Windows的CMD或PowerShell,Mac和Linux的终端),检查Node.js和npm是否安装成功:
node -v npm -v
此命令会输出Node.js和npm的版本号,表示安装成功。
1.3 创建Nest.js项目
创建一个新的 Nest.js 项目可以通过 Nest CLI(命令行工具)来完成。以下是具体步骤:
- 安装Nest CLI:
npm i -g @nestjs/cli
-
使用 Nest CLI 创建一个新的 Nest.js 项目:
nest new my-nest-app
这里
my-nest-app
是你项目的名称,你也可以用其他你喜欢的名字替换。 - 进入项目目录并安装依赖:
cd my-nest-app npm install
- 运行项目:
npm run start
这将启动项目并在浏览器中打开http://localhost:3000/,你可以看到默认的“Hello World”页面。
1.4 安装与配置开发环境
为了进一步配置开发环境,可以安装一些常用的工具和库,例如:
- TypeScript:Nest.js 主要使用 TypeScript 编写,因此确保你的项目中安装了 TypeScript。
npm install --save-dev typescript
- Prettier:代码格式化工具,有助于保持代码风格的一致性。
npm install --save-dev prettier
- ESLint:代码检查工具,帮助你发现和修复代码中的问题。
npm install --save-dev eslint
- Jest:单元测试框架,后续章节将详细介绍其使用方法。
安装完成后,参考 Nest.js 的官方文档进行相应的配置。
2. 第一个Nest.js应用
2.1 创建第一个控制器
在 Nest.js 中,控制器是用来处理 HTTP 请求的组件,每个控制器包含一组路由方法。这里我们将创建一个简单的控制器来响应HTTP GET 请求。
-
在命令行中生成一个新的控制器:
nest generate controller welcome
这将生成一个名为
welcome.controller.ts
的文件,位于项目根目录下的src
文件夹中。 -
打开生成的控制器文件,并添加一个处理 GET 请求的方法:
import { Controller, Get } from '@nestjs/common'; @Controller('welcome') export class WelcomeController { @Get() getWelcomeMessage() { return 'Hello, Welcome to Nest.js!'; } }
-
确保在
src/app.module.ts
文件中已经导入并注册了新创建的控制器:import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { WelcomeController } from './welcome/welcome.controller'; @Module({ imports: [], controllers: [AppController, WelcomeController], providers: [AppService], }) export class AppModule {}
2.2 定义路由与HTTP请求
在 Nest.js 中,路由是通过装饰器来定义的。上一节中,我们已经使用了@Get()
装饰器定义了一个 GET 请求的路由。接下来,我们将添加一个 POST 请求的路由。
-
在
welcome.controller.ts
文件中添加一个处理 POST 请求的方法:@Post() postWelcomeMessage() { return 'This is a POST request to welcome endpoint.'; }
- 确保在
src/app.module.ts
文件中,AppModule
中已经注册了新的路由。@Module({ imports: [], controllers: [AppController, WelcomeController], providers: [AppService], }) export class AppModule {}
2.3 运行与测试应用
现在,我们已经创建并定义了路由,可以运行应用并测试新添加的路由。
-
在命令行中运行项目:
npm run start
- 使用 Postman 或者浏览器测试新路由。例如,访问
http://localhost:3000/welcome
查看 GET 请求的响应,访问http://localhost:3000/welcome?body=POST
查看 POST 请求的响应。
通过这种方式,你可以创建并测试更多的路由,以满足你的应用需求。
3. Nest.js核心概念
3.1 模块(Module)
模块是 Nest.js 中最重要的概念之一。每个模块都是一个独立的可重用组件,可以包含一组服务、控制器、提供者和其他模块。模块通过导入其他模块来实现依赖注入,从而建立一个松耦合、高效且可扩展的结构。
创建模块的过程如下:
-
在命令行中生成一个新的模块:
nest generate module users
这将生成一个名为
users.module.ts
的文件,位于项目根目录下的src
文件夹中。 -
打开生成的模块文件并定义其内容:
import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ imports: [], controllers: [UsersController], providers: [UsersService], }) export class UsersModule {}
- 在
src/app.module.ts
文件中导入并注册新创建的模块:@Module({ imports: [UsersModule], controllers: [AppController], providers: [AppService], }) export class AppModule {}
模块的定义包括imports
、controllers
和providers
,分别用于导入其他模块、定义控制器和定义提供者。
3.2 服务(Service)
服务是 Nest.js 中的业务逻辑处理组件。服务的职责通常包括:数据处理、业务规则实现、与外部系统交互等。服务通常不直接与 HTTP 请求直接交互,而是由控制器来调用服务来处理请求。
创建服务的过程如下:
-
在命令行中生成一个新的服务:
nest generate service users
这将生成一个名为
users.service.ts
的文件,位于项目根目录下的src/users
文件夹中。 -
打开生成的服务文件并定义其内容:
import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { getUsers() { return [ { id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Doe' }, ]; } }
-
在
users.module.ts
文件中导入并注册新创建的服务:import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ imports: [], controllers: [UsersController], providers: [UsersService], }) export class UsersModule {}
服务通常通过@Injectable
装饰器声明,并在模块中通过providers
数组进行注册。
3.3 注解(Decorator)
注解是 Nest.js 中用于修饰类、方法或属性的重要工具,用于改变类、方法或属性的行为。Nest.js 中常用的注解包括:
@Controller
:定义控制器@Injectable
:声明服务@Get
、@Post
:定义 HTTP 路由@Module
:定义模块
注解可以改变类或方法的行为,或增强它们的功能。例如,前面介绍的@Controller
、@Injectable
和@Get
等注解用于定义控制器、服务和路由。
3.4 依赖注入(Dependency Injection)
依赖注入是一种设计模式,用于解耦组件之间的依赖关系。在 Nest.js 中,依赖注入通过容器来管理服务的创建和提供,从而简化了服务的使用和维护。
在 Nest.js 中,依赖注入通过装饰器和提供者来实现。在服务中,可以通过@Injectable
装饰器声明一个提供者,然后在模块中通过providers
数组进行注册。在控制器或服务中,可以通过@Inject
装饰器使用已注册的提供者。
依赖注入的实现过程如下:
-
在服务中使用
@Injectable
装饰器声明一个提供者:import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { getUsers() { return [ { id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Doe' }, ]; } }
-
在模块中通过
providers
数组注册提供者:import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ imports: [], controllers: [UsersController], providers: [UsersService], }) export class UsersModule {}
-
在控制器中使用
@Inject
装饰器注入依赖的服务:import { Controller } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} getUsers() { return this.usersService.getUsers(); } }
通过这种方式,可以实现松耦合的设计,使得代码更加灵活和易于维护。
4. 实战案例:用户管理系统
4.1 数据库连接与配置
在这个实战案例中,我们将构建一个简单的用户管理系统,实现用户数据的增删改查功能。首先,我们要连接数据库并进行配置。
Nest.js 支持多种数据库,这里我们以 MySQL 为例,用 TypeORM 作为 ORM(对象关系映射)工具。
-
安装数据库连接依赖:
npm install --save @nestjs/typeorm mysql
-
配置数据库连接:
在项目的根目录下创建一个ormconfig.json
文件,配置数据库连接信息:{ "type": "mysql", "host": "localhost", "port": 3306, "username": "root", "password": "password", "database": "nestjs", "synchronize": true, "logging": true, "entities": ["src/**/*.entity{.ts,.js}"] }
-
在主模块中引入 TypeORM,并配置数据库连接:
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UsersModule } from './users/users.module'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'nestjs', synchronize: true, logging: true, entities: ['src/**/*.entity{.ts,.js}'], }), UsersModule, ], controllers: [], providers: [], }) export class AppModule {}
4.2 创建用户模型与服务
接下来,我们将创建用户模型和对应的业务逻辑服务。
-
生成用户实体文件:
nest generate entity user
-
编辑生成的用户实体文件,定义用户模型:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; }
-
生成用户服务文件:
nest generate service user
-
编辑生成的用户服务文件,实现用户业务逻辑:
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from '../users/user.entity'; @Injectable() export class UserService { constructor( @InjectRepository(User) private userRepository: Repository<User>, ) {} async createUser(name: string, email: string) { const user = this.userRepository.create({ name, email }); return this.userRepository.save(user); } async getUsers() { return this.userRepository.find(); } async getUserById(id: number) { return this.userRepository.findOne({ where: { id } }); } async updateUser(id: number, name: string, email: string) { const user = await this.userRepository.findOne({ where: { id } }); if (user) { user.name = name; user.email = email; return this.userRepository.save(user); } return null; } async deleteUser(id: number) { const user = await this.userRepository.findOne({ where: { id } }); if (user) { return this.userRepository.remove(user); } return null; } }
4.3 实现用户增删改查功能
接下来,我们将创建控制器来处理用户管理的 HTTP 请求,调用用户服务来实现具体的业务逻辑。
-
生成用户控制器文件:
nest generate controller user
-
编辑生成的用户控制器文件,定义路由和处理函数:
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common'; import { UserService } from './user.service'; import { User } from './user.entity'; @Controller('users') export class UserController { constructor(private readonly userService: UserService) {} @Get() async getUsers() { return this.userService.getUsers(); } @Get(':id') async getUserById(@Param('id') id: number) { return this.userService.getUserById(id); } @Post() async createUser(@Body() user: User) { return this.userService.createUser(user.name, user.email); } @Put(':id') async updateUser(@Param('id') id: number, @Body() user: User) { return this.userService.updateUser(id, user.name, user.email); } @Delete(':id') async deleteUser(@Param('id') id: number) { return this.userService.deleteUser(id); } }
-
在
users.module.ts
文件中,导入并注册新创建的控制器和服务:import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserController } from './user.controller'; import { UserService } from './user.service'; import { User } from './user.entity'; @Module({ imports: [TypeOrmModule.forFeature([User])], controllers: [UserController], providers: [UserService], }) export class UsersModule {}
通过上述步骤,我们已经成功构建了一个简单的用户管理系统,可以实现用户数据的增删改查功能。
5. 测试Nest.js应用
5.1 单元测试与集成测试
单元测试和集成测试是软件测试的两种常见类型。单元测试用于测试单个函数或组件,而集成测试则用于测试组件之间的交互。
5.2 使用Jest进行测试
Jest 是一个流行的 JavaScript 测试框架,可以与 Nest.js 无缝集成,用于执行单元测试和集成测试。
-
安装 Jest 和相关依赖:
npm install --save-dev jest ts-jest @types/jest
-
配置 Jest:
在项目的根目录下创建一个jest.config.js
文件,配置 Jest:module.exports = { preset: 'ts-jest', testRegex: 'src/(.*)\\.(test|spec)\\.ts$', moduleFileExtensions: ['ts', 'js'], transform: { '^.+\\.ts$': 'ts-jest', }, };
-
在控制器或服务中编写测试用例:
例如,在user.controller.spec.ts
文件中,我们可以编写如下测试用例:import { Test, TestingModule } from '@nestjs/testing'; import { UserController } from './user.controller'; import { UserService } from '../user.service'; import { User } from '../user.entity'; import { NotFoundException, BadRequestException } from '@nestjs/common'; const mockUserService = () => ({ getUsers: jest.fn(() => Promise.resolve([])), getUserById: jest.fn().mockResolvedValue({}), createUser: jest.fn().mockResolvedValue({}), updateUser: jest.fn().mockResolvedValue({}), deleteUser: jest.fn().mockResolvedValue({}), }); describe('UserController', () => { let userController: UserController; let userService: UserService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UserController], providers: [{ provide: UserService, useFactory: mockUserService }], }).compile(); userController = module.get<UserController>(UserController); userService = module.get<UserService>(UserService); }); it('should return all users', async () => { const users = await userController.getUsers(); expect(users).toEqual([]); }); it('should return a user by id', async () => { const user = await userController.getUserById(1); expect(user).toEqual({}); }); it('should create a user', async () => { const user = await userController.createUser({ name: 'John Doe', email: 'john@example.com' }); expect(user).toEqual({}); }); it('should update a user', async () => { const user = await userController.updateUser(1, { name: 'Jane Doe', email: 'jane@example.com' }); expect(user).toEqual({}); }); it('should delete a user', async () => { const user = await userController.deleteUser(1); expect(user).toEqual({}); }); it('should return an error if user not found', async () => { try { await userController.getUserById(999); } catch (error) { expect(error).toBeInstanceOf(NotFoundException); } }); it('should return an error if user update fails', async () => { try { await userController.updateUser(999, { name: 'Test', email: 'test@example.com' }); } catch (error) { expect(error).toBeInstanceOf(BadRequestException); } }); });
在服务文件中编写测试用例,例如在 user.service.spec.ts
文件中:
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { User } from './user.entity';
import { NotFoundException, BadRequestException } from '@nestjs/common';
const mockUserRepository = () => ({
create: jest.fn(),
save: jest.fn().mockResolvedValue({}),
find: jest.fn(() => Promise.resolve([])),
findOne: jest.fn().mockResolvedValue({}),
remove: jest.fn().mockResolvedValue({}),
});
describe('UserService', () => {
let userService: UserService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: UserService,
useFactory: (userRepository) => new UserService(userRepository),
inject: [UserRepository],
},
{
provide: UserRepository,
useFactory: mockUserRepository,
},
],
}).compile();
userService = module.get<UserService>(UserService);
});
it('should return all users', async () => {
const users = await userService.getUsers();
expect(users).toEqual([]);
});
it('should return a user by id', async () => {
const user = await userService.getUserById(1);
expect(user).toEqual({});
});
it('should create a user', async () => {
const user = await userService.createUser('John Doe', 'john@example.com');
expect(user).toEqual({});
});
it('should update a user', async () => {
const user = await userService.updateUser(1, 'Jane Doe', 'jane@example.com');
expect(user).toEqual({});
});
it('should delete a user', async () => {
const user = await userService.deleteUser(1);
expect(user).toEqual({});
});
it('should throw an error if user not found', async () => {
try {
await userService.getUserById(999);
} catch (error) {
expect(error).toBeInstanceOf(NotFoundException);
}
});
it('should throw an error if user update fails', async () => {
try {
await userService.updateUser(999, 'Test', 'test@example.com');
} catch (error) {
expect(error).toBeInstanceOf(BadRequestException);
}
});
});
通过这种方式,我们可以更好地控制测试环境,验证控制器方法的行为。
6. 部署与上线
6.1 构建项目
在项目开发完成后,需要构建项目以便部署到生产环境。
-
构建项目:
npm run build
这将生成一个
dist
文件夹,其中包含编译后的代码。 - 检查编译后的代码:
ls dist
6.2 部署到服务器
部署到服务器的步骤取决于你的服务器环境。这里以主流的Linux服务器为例,提供一个简单的部署流程。
-
上传编译后的代码到服务器。可以使用SCP、FTP等工具上传到服务器指定的目录中。
scp -r dist user@server:/path/to/deploy
-
安装运行时依赖。确保安装了 Node.js 和 npm。
cd /path/to/deploy npm install --production
- 启动应用:
npm run start:prod
或者使用 PM2 或其他进程管理工具来管理应用进程。
6.3 配置服务器环境
为了确保应用在生产环境中稳定运行,需要对服务器环境进行配置。
-
设置环境变量:
创建.env
文件,定义环境变量。NODE_ENV=production PORT=3000
-
配置Nginx:
创建一个 Nginx 配置文件,将所有 HTTP 请求代理到 Node.js 应用。server { listen 80; server_name example.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }
- 配置PM2:
使用 PM2 管理应用进程。安装 PM2:npm install pm2 -g
设置应用:
pm2 start dist/main.js --name "nestjs-app" pm2 startup pm2 save pm2 startup
通过以上步骤,我们可以顺利地将 Nest.js 项目部署到生产环境,并确保其稳定运行。