使

使用 Nest 和 Typescript 创建您的第一个 Node.js REST API

2025-05-25

使用 Nest 和 Typescript 创建您的第一个 Node.js REST API

在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris

用于构建高效、可靠且可扩展的服务器端应用程序的渐进式 Node.js 框架。

在本文中,我们将了解 Nest 库。这个库让编写 API 成为一种非常愉快的体验。如果您来自 Angular 世界,您一定会对它所使用的概念、强大的 CLI 以及 Typescript 的出色运用感到熟悉。

请注意,虽然它不是 Angular,但是以最好的方式来说,它非常接近 Angular。

本文是 Nest 系列文章的一部分,因为我们不可能在一篇文章中涵盖所有内容。

我们将介绍以下内容:

  • 为什么选择 Nest?让我们来看看它的销售宣传,并了解 Nest 有哪些特性,使其成为您下一个 API 的绝佳选择。
  • 您的第一个 CRUD 项目 - 涵盖基础知识,让我们搭建一个项目并讨论基本构造

 为什么选择 Nest

让我们看看主页上的销售宣传

  • 可扩展,得益于模块化架构,允许使用任何其他库
  • 多功能,适应性强的生态系统,适用于各种服务器端应用程序
  • 渐进式,利用最新的 JavaScript 功能、设计模式和成熟的解决方案

好吧,听起来不错,但请给我一些能让同事们惊叹的东西

它完全支持 TypeScript,但如果您愿意,也可以使用纯 JavaScript。

它使用库ExpressFastify底层功能,但如果需要也可以公开它们的 API。

听起来很有趣,告诉我更多

它带有一个 CLI,因此您可以搭建项目以及添加工件。

那很好

最重要的是,你可以使用 Jest 轻松编写单元测试和 E2E 测试,并且可以轻松地用它构建 GraphQL API

别说了,你胡编乱造

不,看看Nest 和 GraphQL

资源

我们将在本文中提及一些很棒的资源。如果您错过了我们提到的链接,可以在这里找到。

你的第一个项目 - 涵盖基础知识

好的,那就开始吧。在开始创建我们的第一个项目之前,我们需要 CLI 来创建和运行我们的项目以及执行其他许多操作。我们可以使用以下命令轻松安装 CLI:



npm i -g @nestjs/cli


Enter fullscreen mode Exit fullscreen mode

接下来我们需要搭建一个项目。接下来我们开始吧:



nest new hello-world


Enter fullscreen mode Exit fullscreen mode

您可以hello-world用您选择的项目名称进行替换。

好的,我们得到了很多文件。从上面的图片来看,我们似乎已经得到了一个 Node.js 项目,package.json并用 Jest 设置了一些测试,当然还有一些看起来像 Nest 特有的构件,比如controllermoduleservice。让我们仔细看看这个搭建好的项目:

它是如何工作的?

在运行我们刚刚搭建的项目之前,我们先来仔细看看它的生命周期。首先,我们来看一下main.ts。这是我们应用的入口点。更具体地说,它是bootstrap()通过运行以下代码来启动所有程序的方法:



// main.ts

const app = await NestFactory.create(AppModule);
await app.listen(3000);


Enter fullscreen mode Exit fullscreen mode

好的,NestFactory调用create()来实例化 ,AppModule我们得到一个app似乎在监听端口 的实例3000。让我们去AppModule看看那里发生了什么:



//app.module.ts

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}


Enter fullscreen mode Exit fullscreen mode

好的,我们似乎有一个AppModule@Module装饰器装饰的类,该类指定了一个控制器AppController和一些被归类为提供者的东西AppService

这一切是如何运作的?

好吧,控制器AppController响应路由请求,所以让我们看看如何设置它:



// app.controller.ts

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}


Enter fullscreen mode Exit fullscreen mode

装饰器@Get()确保我们将某个 GET 请求映射到类上的某个方法。在本例中,默认路由/将使用该方法进行响应,getHello()该方法又会调用appService.getHello()。我们来看看app.service.ts



// app.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}


Enter fullscreen mode Exit fullscreen mode

这似乎是一个非常简单的类,具有getHello()返回字符串的方法。

现在,让我们回到app.controller.ts

从我们所看到的,appService它是在构造函数中注入的,AppController就像这样:



// excerpt from app.controller.ts

constructor(private readonly appService: AppService) {}


Enter fullscreen mode Exit fullscreen mode

它怎么知道该怎么做呢?

这里有两个答案:

  1. 如果将装饰器添加Injectable()到任何服务,则意味着它可以注入其他工件,例如控制器或服务。
  2. 这引出了第二步。我们需要将上述服务添加到providers模块的数组中,以使 DI 机制正常工作。

哦?

是的,让我们试着通过添加一条新路线来巩固一下理解。但在此之前,我们先启动这个项目,并验证它是否真的像我们说的那样有效:



npm start


Enter fullscreen mode Exit fullscreen mode

现在,让我们进入浏览器:

 添加路线

我们刚刚学会了如何搭建一个项目,以及如何运行它。我们自认为对模块控制器服务的概念已经有了相当的理解,但没有什么比添加一条新路线并添加所有必要的构件更能巩固这些知识了。我们将进行以下操作:

我们将创建一条新路线/products,为此,我们需要执行以下步骤

  1. 添加新服务
  2. 添加新的控制器并注入我们的服务
  3. 连接 DI 机制
  4. 运行我们的应用程序并确保一切正常。

我们要做的第一件事是学习如何正确使用 Nest 项目。目前,我们运行了npm start,它编译了 TypeScript 代码并将我们的应用程序托管在端口上,3000但在开发过程中,我们可能需要一些能够监听更改并自动编译的功能。为此,我们改为运行命令npm run start:dev,它会监听更改并在需要时重新编译。



npm run start:dev


Enter fullscreen mode Exit fullscreen mode

注意,在我们开始使用上述命令之前,让我们先搭建所有需要的文件,然后当我们在特定的代码文件中乱搞并希望我们的更改能够反映出来时,我们可以运行上述命令。

创建服务

让我们创建产品服务。现在,先将数据设为静态,稍后再考虑添加 HTTP 调用。让我们按照 Nest 的方式操作,并使用 CLI



nest generate service products


Enter fullscreen mode Exit fullscreen mode

或较短的版本



nest g s products


Enter fullscreen mode Exit fullscreen mode

好的,打开文件products/products.service.ts。它应该如下所示:



import { Injectable } from '@nestjs/common';


@Injectable()
export class ProductsService {}


Enter fullscreen mode Exit fullscreen mode

现在添加方法getProducts(),它现在看起来像这样:



import { Injectable } from '@nestjs/common';


@Injectable()
export class ProductsService {
  getProducts() {
    return [{
      id: 1,
      name: 'A SPA app'
    },
    {
      id: 2,
      name: 'A Nest API'
    }]
  }
}


Enter fullscreen mode Exit fullscreen mode

添加控制器

现在该创建控制器了,接下来我们来做。同样,我们只需要使用 CLI,如下所示:



nest generate controller products


Enter fullscreen mode Exit fullscreen mode

或,较短版本



nest g co products


Enter fullscreen mode Exit fullscreen mode

打开products/products.controller



import { Controller } from '@nestjs/common';

@Controller('products')
export class ProductsController {}


Enter fullscreen mode Exit fullscreen mode

下一步是添加一个方法getProducts()并确保我们调用我们的服务,当然我们不要忘记用@Get()装饰器来装饰它。

您的代码现在应如下所示:



import { Controller, Get } from '@nestjs/common';
import { ProductsService } from './products.service';

@Controller('products')
export class ProductsController {
  constructor(private productsService: ProductsService) {}

  @Get()
  getProducts() {
    return this.productsService.getProducts();
  }
}


Enter fullscreen mode Exit fullscreen mode

让我们尝试一下:



npm run start:dev


Enter fullscreen mode Exit fullscreen mode

上面我们可以看到我们的/products路由似乎已经添加,并且ProductsController会响应该路由上的任何请求。但这怎么可能呢?我们还没有做任何app.module.ts连接 DI 的操作,对吧?

让我们看看app.module.ts

我们可以看到ProductsController, 和ProductsService分别被添加到controllersproviders中。CLI 在生成控制器和服务时就已经帮我们添加了它。

我们几乎忘记了在浏览器中运行我们的应用程序的某些东西,所以让我们这样做:

注意,CLI 功能强大,它不仅可以创建必要的文件,还可以进行一些连接,但如果您不使用 CLI,它知道您需要做什么。

添加剩余的 CRUD 路由

好的,我们添加了一个路由来支持/products路由。不过众所周知,我们需要的路由远不止这些POST,例如PUT,,DELETE以及通配符路由等等。

我们如何添加它们?

很简单,我们只需要为每个人创建方法并添加装饰器来支持它,如下所示:



// products.controller.ts

import { Controller, Get, Param, Post, Body, Put, Delete } from '@nestjs/common';
import { ProductsService } from './products.service';

interface ProductDto {
  id: string;
  name: string;
}

@Controller('products')
export class ProductsController {
  constructor(private productsService: ProductsService) {}

  @Get()
  getProducts() {
    return this.productsService.getProducts();
  }

  @Get(':id') 
  getProduct(@Param() params) {
    console.log('get a single product', params.id);
    return this.productsService.getProducts().filter(p => p.id == params.id);
  }

  @Post()
  createProduct(@Body() product: ProductDto) {
    console.log('create product', product);
    this.productsService.createProduct(product);
  }

  @Put()
  updateProduct(@Body() product: ProductDto) {
    console.log('update product', product);
    this.productsService.updateProduct(product);
  }

  @Delete()
  deleteProduct(@Body() product: ProductDto) {
    console.log('delete product', product.id);
    this.productsService.deleteProduct(product.id);
  }
}


Enter fullscreen mode Exit fullscreen mode

现在products.service.ts看起来像这样:



import { Injectable } from '@nestjs/common';

@Injectable()
export class ProductsService {
products = [{
id: 1,
name: 'A SPA app'
},
{
id: 2,
name: 'A Nest API'
}];

getProducts() {
return this.products;
}

createProduct(product) {
this.products = [...this.products, {...product}];
}

updateProduct(product) {
this.products = this.products.map(p => {
if (p.id == product.id) {
return { ...product};
}
return p;
});
}

deleteProduct(id) {
this.products = this.products.filter(p => p.id != id);
}
}

Enter fullscreen mode Exit fullscreen mode




 概括

希望您现在已经意识到 Nest 的结构是多么完善,以及创建 API 并读取查询参数和主体以支持完整的 CRUD API 是多么容易。我们还引入了 CLI,它是您生成所需代码的最佳助手,确保您无需考虑如何连接。

下一部分我们将探讨如何测试代码,这将是一次真正令人愉悦的体验。敬请期待。

文章来源:https://dev.to/itnext/nest-creating-a-rest-api-has-never-felt-so-good-4i1
PREV
为什么你应该学习递归
NEXT
学习全栈 GraphQL