简介
如果您曾经使用过Node.js应用程序,您可能已经注意到,随着时间的推移,维护它变得越来越困难。您向应用程序添加的新功能越多,代码库就变得越大。
Nest.js是一个服务器端Node.js框架,用于构建高效、可靠和可扩展的应用程序。它为后端应用程序提供了一个模块化结构,用于将代码组织到单独的模块中。它是为了消除杂乱无章的代码库而建造的。
深受ANGLE,)的启发,Nest.js是用https://www.typescriptlang.org/))构建的,并在引擎盖下使用Express.js,这使得它与大多数Express中间件兼容。
在这篇文章中,您将创建一个REST风格的小型API,使用户能够在书店中获取、创建和删除图书。
前提条件
要完成本教程,您需要:
- Node.js的本地开发环境。遵循如何安装Node.js并创建本地开发Environment
理解Nest.js的构建块
以下是构建Nest.js应用程序时使用的构造块:
- 控制器
- 提供商
- 模块
我们将从查看控制器开始。像大多数Web框架一样,Nest.js中的控制器负责处理任何传入的请求并将响应返回到应用程序的客户端。例如,如果您对特定的端点进行API调用,比如/home
,控制器将收到该请求,并根据可用资源返回相应的响应。
Nest.js的结构使得路由机制能够控制哪个控制器负责处理特定请求。
要在Nest.js中定义控制器,需要创建一个类型脚本文件,并包含一个修饰符@Controller()
,如以下代码片段所示:
1[label users.controller.ts]
2
3import { Controller, Get } from '@nestjs/common';
4
5@Controller('users')
6export class UsersController {
7 @Get()
8 findAll() {
9 return 'This will return all the users';
10 }
11}
控制器修饰符中的users
前缀将提示UsersController
处理应用程序内的任何/users
GET请求,并返回指定的相应响应。控制器处理的其他HTTP请求包括POST
、PUT
、DELETE
,我们将在后面的教程中看到。
一旦创建了控制器,就需要将其添加到模块定义中,Nest.js才能轻松识别它。这可以是根ApplicationModule
或在应用程序中创建的任何其他模块。在这篇文章的模块部分中有更多关于这方面的信息。
现在我们来看看提供商 。
如前所述,Nest.js的灵感主要来自角度,类似于角度应用程序,您可以创建一个提供者并将其注入到控制器或其他提供者中。这些提供者也被称为服务 ,它们旨在抽象任何形式的复杂性和逻辑。
Nest.js中的服务提供者是一个顶部带有特殊@Injectable()
修饰符的JavaScript类。例如,您可以创建一个服务来获取用户:
1[label users.service.ts]
2
3import { Injectable } from '@nestjs/common';
4import { User } from './interfaces/user.interface';
5
6@Injectable()
7export class UsersService {
8 private readonly users: User[] = [];
9
10 create(user: User) {
11 this.users.push(user); }
12
13 findAll(): User[] {
14 return this.users;
15 }
16}
上面创建的provider是一个类,有两个方法create()
和findAll()
,可以分别用来创建和返回所有用户。为了方便地帮助进行类型检查,使用了一个接口来指定方法应该接收的元素的类型。
最后,让我们来看一下模块。模块允许您对相关文件进行分组。它们是用@module
装饰符装饰的打字文件。这个附加的修饰符提供Nest用来组织应用程序结构的元数据。
每个Nest.js应用程序必须至少有一个模块,通常称为 根模块 。这个根模块是顶级模块,通常对于小型应用程序来说已经足够了。 建议将大型应用程序分解为多个模块,因为这有助于维护应用程序的结构。
如果您有一个管理大量用户数据或功能的应用程序,您可以将控制器、服务和其他相关文件分组到一个模块中,如UsersModule
:
1import { Module } from '@nestjs/common';
2import { UsersController } from './users.controller.ts';
3import { UsersService } from './users.service.ts';
4
5@Module({
6 controllers: [UsersController],
7 providers: [UsersService]
8})
9
10export class UsersModule {}
在本例中,我们导出了一个同时包含UsersController
和UsersService
的UsersModule
。准备就绪后,我们可以继续在应用程序的根模块中导入和使用UsersModule
,如以下代码片段所示:
1...
2import { UsersModule } from './users/users.module';
3
4@Module({
5 ...
6})
7
8export class AppModule { }
在Nest.js中还有其他一些重要的概念:
- DTO :数据传输对象是定义数据如何通过网络发送的对象。
- 接口 :类型脚本接口用于类型检查和定义可以传递给控制器或Nest服务的数据类型。
- 依赖注入 :依赖注入是一种用于提高应用程序的效率和模块化的设计模式。它经常被最大的框架用来保持代码的整洁和更容易使用。Js还利用它来创建基本的耦合组件。
使用此模式,很容易管理构建块(如控制器、提供程序和模块)之间的依赖关系。唯一需要的是定义依赖关系,例如在控制器的构造函数中定义一个UsersService()
,如下所示:
1...
2@Controller('users')
3export class UsersController {
4constructor(private readonly usersService: UsersService){}
5 ...
6}
简要介绍了其中的一些概念后,您现在可以继续下一节,在这里您将使用在本文中获得的所有知识,学习如何使用Nest.js无缝构建REST风格的API。
正如本文前面所述,您将创建一个样例应用程序,帮助您很好地掌握Nest.js的一些核心概念。
这个应用程序将专门针对书店。在这篇文章的末尾,你将创建一个微服务,使用户能够创建一本新书,并将其添加到现有的书单中。它可能来自数据库,但为了确保本文的简单性,我们还不会真正将应用程序连接到数据库。但是,我们将使用图书的模拟数据,一旦创建了新书,我们将推送它并将其添加到列表中。
第一步-安装Nest.js
为了搭建新的Nest.js应用程序,您需要全局安装Nest CLI应用程序。它是一个命令行工具,专门用来创建一个新的Nest.js应用程序,并提供对几个命令的访问,以生成不同的文件和结构良好的应用程序。
除了使用CLI工具之外,您还可以通过从[giHub]克隆Starter project](使用Git)(https://git-scm.com/),,但出于本教程的目的,运行以下命令来安装Nest CLI)来安装新的Nest.js应用程序:
1npm install -g @nestjs/cli
这将允许您访问用于工程安装的nest
命令和其他工程特定命令。
接下来,运行以下命令,在您的开发文件夹中安装一个名为bookstore-nest
的新项目:
1nest new bookstore-nest
安装过程中会询问您几个问题,只需按照提示进行操作并做出相应的响应即可。接下来,安装完成后,将您的工作目录切换到新创建的项目:
1cd bookstore-nest
使用以下命令启动应用程序:
1npm run start
为了将Nodemon用于项目,您还可以运行以下命令:
1// start the application using nodemon
2npm run start:dev
在浏览器中导航到http://localhost:3000
,您将看到Hello World !消息,如下图所示:
项目开始后,让我们创建根模块。
Step 2 -生成模块
让我们为书店生成一个模块。为此,您将利用Nest CLI的文件生成器。运行以下命令为应用程序搭建一个新模块:
1nest generate module books
这将在src
文件夹中创建一个名为books
的新文件夹。在books
文件夹中,您将找到一个books.mode.ts
文件:
1[label src/books/books/module.ts]
2
3import { Module } from '@nestjs/common';
4@Module({})
5export class BooksModule {}
这是由命令生成的,该模块也被添加到app.mode.ts
文件中,该文件恰好是应用程序的根模块。
接下来,您将为端点创建路径
Step 3 -创建路由和控制器
如前所述,路由存在于控制器中,因此您需要创建将处理各个端点的控制器。同样,使用Nest CLI生成控制器,运行以下命令:
1nest generate controller books
这将在books
文件夹中创建一个控制器。
由于我们现在不会连接到数据库,因此为书店创建一个示例模拟数据。在src
文件夹下,创建一个名为mocks
的子文件夹,并在新创建的文件夹中,创建一个名为books.mock.ts
的新TypeScript文件,并在其中添加以下代码:
1[label src/mocks/books.mock.ts]
2export const BOOKS = [
3 { id: 1, title: 'First book', description: "This is the description for the first book", author: 'Olususi Oluyemi' },
4 { id: 2, title: 'Second book', description: "This is the description for the second book", author: 'John Barry' },
5 { id: 3, title: 'Third book', description: "This is the description for the third book", author: 'Clement Wilfred' },
6 { id: 4, title: 'Fourth book', description: "This is the description for the fourth book", author: 'Christian nwamba' },
7 { id: 5, title: 'Fifth book', description: "This is the description for the fifth book", author: 'Chris anderson' },
8 { id: 6, title: 'Sixth book', description: "This is the description for the sixth book", author: 'Olususi Oluyemi' },
9];
接下来,您将创建一个服务来保存书店的所有逻辑。
第四步-设置服务
运行以下命令以生成服务:
1nest generate service books
此命令将在内创建一个名为
books.service.ts的新文件。/ src/books
文件夹。
接下来,打开新创建的文件并粘贴以下内容:
1[label /src/books/books.service.ts]
2
3 import { Injectable, HttpException } from '@nestjs/common';
4 import { BOOKS } from '../mocks/books.mock';
5
6 @Injectable()
7 export class BooksService {
8 books = BOOKS;
9
10 getBooks(): Promise<any> {
11 return new Promise(resolve => {
12 resolve(this.books);
13 });
14 }
15 getBook(bookID): Promise<any> {
16 let id = Number(bookID);
17 return new Promise(resolve => {
18 const book = this.books.find(book => book.id === id);
19 if (!book) {
20 throw new HttpException('Book does not exist!', 404);
21 }
22 resolve(book);
23 });
24 }
25 }
首先,您从Nest.js导入了requires模块,还从之前创建的mock数据导入了BOOKS
。
接下来,您创建了两个不同的方法,名为getBooks()
和getBook()
,用于从模拟数据中检索图书列表,并使用bookID
作为参数仅获取一本图书。
接下来,在getBook()
方法后面的/src/book/books.service.ts
中添加以下方法:
1[label src/books/books.service.ts]
2
3import { Injectable, HttpException } from '@nestjs/common';
4import { BOOKS } from '../mocks/books.mock';
5@Injectable()
6export class BooksService {
7 books = BOOKS;
8 ...
9 addBook(book): Promise<any> {
10 return new Promise(resolve => {
11 this.books.push(book);
12 resolve(this.books);
13 });
14 }
15}
上面的方法将用于将新书推送到现有列表
最后,添加最后一个删除特定图书的方法,参数为bookID
:
1[label src/books/books.service.ts]
2
3import { Injectable, HttpException } from '@nestjs/common';
4import { BOOKS } from '../mocks/books.mock';
5@Injectable()
6export class BooksService {
7 books = BOOKS;
8 ...
9 deleteBook(bookID): Promise<any> {
10 let id = Number(bookID);
11 return new Promise(resolve => {
12 let index = this.books.findIndex(book => book.id === id);
13 if (index === -1) {
14 throw new HttpException('Book does not exist!', 404);
15 }
16 this.books.splice(1, index);
17 resolve(this.books);
18 });
19 }
20}
第五步--将服务注入控制器
这里,您将使用依赖注入设计模式,通过构造函数将BooksService
传入BooksController
。打开前面创建的BooksController
,粘贴如下代码:
1[label src/books/books.controller.ts]
2
3import { Controller, Get, Param, Post, Body, Query, Delete } from '@nestjs/common';
4import { BooksService } from './books.service';
5import { CreateBookDTO } from './dto/create-book.dto';
6
7@Controller('books')
8export class BooksController {
9 constructor(private booksService: BooksService) { }
10
11 @Get()
12 async getBooks() {
13 const books = await this.booksService.getBooks();
14 return books;
15 }
16
17 @Get(':bookID')
18 async getBook(@Param('bookID') bookID) {
19 const book = await this.booksService.getBook(bookID);
20 return book;
21 }
22
23 @Post()
24 async addBook(@Body() createBookDTO: CreateBookDTO) {
25 const book = await this.booksService.addBook(createBookDTO);
26 return book;
27 }
28
29 @Delete()
30 async deleteBook(@Query() query) {
31 const books = await this.booksService.deleteBook(query.bookID);
32 return books;
33 }
34}
首先从@nestjs/Common
导入重要模块,同时导入BooksService
和CreateBookDTO
。CreateBookDTO是一个数据传输对象,是为检查类型和定义创建新书时对象的结构而创建的一个类型脚本类。我们将在稍后创建此DTO。
接下来,您使用构造函数
将BooksService
注入到控制器中,并创建了四个不同的方法,分别是:
getBooks()
:用于获取所有书籍的列表。它附加了@Get()
装饰器。这有助于将发送到/books的任何GET
请求映射到该控制器。getBook()
:用于通过传递bookID
作为参数来获取特定书籍的详细信息。addBook()
:用于创建新书并将其发布到现有的图书列表中。因为我们没有持久化到数据库中,所以新添加的书将只保存在内存中。deleteBook()
:通过传递bookID
作为查询参数来删除图书。
每个方法都附加了一个特殊的修饰符,这使得将每个HTTP请求路由到控制器中的特定方法变得非常容易。
Step 6 -定义DTO
在上一节中,您使用了一个名为CreateBookDTO
的数据传输对象。要创建它,请导航到./src/books
文件夹,并创建一个新的子文件夹名称dto
。接下来,在新创建的文件夹中,创建另一个文件,将其命名为Create-book.dto.ts
,并在其中粘贴以下内容:
1[label src/books/dto/create-book.dto.ts]
2
3export class CreateBookDTO {
4 readonly id: number;
5 readonly title: string;
6 readonly description: string;
7 readonly author: string;
8}
您几乎已经完成了应用程序。导航回您之前创建的./src/book/books.mode.ts
文件,并使用以下代码更新它:
1[label src/books/books.module.ts]
2
3import { Module } from '@nestjs/common';
4import { BooksController } from './books.controller';
5import { BooksService } from './books.service';
6@Module({
7 controllers: [BooksController],
8 providers: [BooksService]
9})
10export class BooksModule {}
如果应用程序目前未运行,请使用以下命令重新启动应用程序:
1npm run start
然后使用邮递员测试接口
创作一些新书:
使用ID获取一本书:
并删除一本书:
结论
在本教程中,您快速了解了Nest.js的基本原理和基本构建块,然后构建了一个REST风格的API。
您可以在GitHub](https://github.com/yemiwebby/bookstore-nest).上找到本教程的完整源代码