NestJS——轻松开发全栈 TypeScript Web 应用程序的缺失部分

2025-06-07

NestJS——轻松开发全栈 TypeScript Web 应用程序的缺失部分

我想大家都知道这个问题:你需要开发一个新的 Web 应用程序,因此需要在后端和前端实现应用程序的核心。两者都可能非常耗时,而且如果一开始就选择了错误的架构决策,随着时间的推移,它会变得难以维护。

此外,项目通常由一个小型核心团队启动。因此,团队提供可靠的架构并能够在时间和预算内提供良好的首个原型至关重要。因此,我认为从全栈 TypeScript 方法开始是合理的,即在前端使用Angular ,在后端使用NestJS 。

在本文中,我将向您介绍 NestJS(从现在开始在本文中称为 Nest),以及为什么我认为这种全栈 TypeScript Web 应用程序可能是 Web 应用程序的一个不错的技术堆栈选择。

为什么我应该在后端使用 TypeScript?

在我看来,这仅在以下情况下才有意义:

  1. 您有一个小型核心团队,他们拥有良好的 TypeScript 知识,并且该技术栈可以满足您现在和将来的项目需求。
  2. 在一个具有多个微服务的项目中,您有一个特定的微服务,它专门作为前端的后端,由前端团队维护。

如果你现有的后端团队效率很高,并且对自己的技术栈很满意,那么至少在我看来,没有必要在后端使用 Web 技术。但至少要保持开放的心态,或许它也适合你的项目。

Nest 是什么?

Nest 网站

网站上的官方描述是:

Nest 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,完全支持 TypeScript(但仍允许开发人员使用纯 JavaScript 编写代码),并融合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数式响应式编程)的元素。

Nest 在底层使用了强大的 HTTP 服务器框架,例如 Express(默认)和 Fastify。Nest 在这些框架之上提供了一定程度的抽象,但同时也可以直接向开发者公开其 API。这使得开发者可以轻松使用适用于各个平台的众多第三方模块。

所以基本上它使用了 Node.js 和 Express 等知名的现有框架,并充当了其上层。但为什么它现在如此特别呢?

我认为它之所以如此出色是因为:

  • 它完全用 TypeScript 编写,您也可以使用 TypeScript 开箱即用地实现您的应用程序
  • 提供开箱即用的应用程序架构,深受 Angular 的启发

但即使它受到了 Angular 的启发,Nest 也是一个独立的项目,完全独立于 Angular 项目本身。你可以构建一个与前端无关的 API,该 API 可以与其他框架和库(如 React、Vue.js 等)一起使用。

TypeScript

Nest 网站

Nest 应用程序是用TypeScript编写的,但您也可以选择用 JavaScript 编写(我不推荐这样做)。TypeScript 是 JavaScript 的超集,它兼具 JavaScript 的灵活性以及类型语言的安全性和稳健性。

TypeScript 被编译为 JavaScript,因此编译器可以在编译期间捕获可能出现的运行时错误。该语言自称是“可扩展的 JavaScript”,而 Nest 也希望提供可扩展的后端架构,所以我认为这是一个不错的选择。

此外,我强烈建议您在当前使用的前端框架中使用 TypeScript。Angular 提供了开箱即用的 TypeScript,但您当然也可以在其他流行的框架(例如 React 或 Vue)中使用它。

这样,您的前后端代码就拥有了通用语言,甚至可以共享类型定义。这种方法大大减少了因语言间上下文切换而造成的效率损失,并可以整体提升团队绩效。

Nest 架构

嵌套模块

如前所述,Nest 的架构深受 Angular 的启发。

通常,服务器端 JavaScript 框架更注重灵活性,而不是为代码提供可扩展的架构。通常,你需要自己投入时间以清晰的方式组织代码并定义规范。

Nest 提供了一个开箱即用的优秀架构,我们现在将对其进行更详细的分析。它基本上由模块、控制器和服务组成。

文件结构

Nest 项目具有与 Angular 项目类似的文件结构:

.
|-- app.controller.spec.ts
|-- app.controller.ts
|-- app.module.ts
|-- app.service.ts
|-- main.ts
|-- news
    |-- news.controller.spec.ts
    |-- news.controller.ts
    |-- news.module.ts
    |-- news.service.spec.ts
    |-- news.service.ts
Enter fullscreen mode Exit fullscreen mode

依赖注入

Nest 与 Angular 类似,也是围绕依赖注入设计模式构建的。你可以在Angular 官方文档中找到一篇关于此模式的优秀文章

让我们看一个简单的例子:

如果我们需要另一个类的实例,我们只需要在构造函数中定义它:

constructor(private readonly newsService: NewsService) {}
Enter fullscreen mode Exit fullscreen mode

Nest 将创建并解析一个 实例NewsService。在单例模式下,如果该实例已被请求,它将返回现有实例。每个可注入的类都需要声明该@Injectable注解,正如您在后面的服务部分中看到的那样。

模块

模块是每个 Nest 应用程序的基本构建块,它将服务和控制器等相关功能分组。如果您创建新的 Nest 应用程序,则会AppModule自动启用这些模块。

理论上,你可以将整个应用程序编写在一个模块中,但大多数情况下这不是正确的方法。建议将每个功能分组到一个模块中,例如,一个模块NewsModule和一个模块UserModule

一个简单的模块示例:

@Module({
  controllers: [NewsController],
  providers: [NewsService],
})
export class NewsModule {}
Enter fullscreen mode Exit fullscreen mode

Angular 使用相同的模块概念,您甚至在代码中以相同的方式定义它们。

控制器

在 Nest 中,你可以使用注解来定义控制器,就像在Spring Boot等框架中一样。控制器负责处理传入的请求并将响应返回给客户端。

你可以使用所需的装饰器来装饰你的控制器类@Controller,并传入一个路径作为该控制器的主路由。控制器类中的每个方法都可以使用常见的装饰器进行注解,例如@Get@Post@Put@Delete

@Controller('news')
export class NewsController {
  @Get()
  findAll(): string {
    return 'This action returns all news';
  }
}
Enter fullscreen mode Exit fullscreen mode

由于我们没有向方法@Get的装饰器添加路径信息findAll,因此 Nest 会将GET /cats请求映射到此处理程序。

服务

Nest 使用服务来保持控制器的精简并封装逻辑。

@Injectable()
export class NewsService {
  private readonly news: News[] = [{ title: 'My first news' }];

  create(news: News) {
    this.news.push(news);
  }

  findAll(): News[] {
    return this.news;
  }
}
Enter fullscreen mode Exit fullscreen mode

现在我们可以在控制器中使用此服务:

@Controller('news')
export class NewsController {
  constructor(private readonly newsService: NewsService) {}

  @Get()
  async findAll(): Promise<News[]> {
    return this.newsService.findAll();
  }
}
Enter fullscreen mode Exit fullscreen mode

测试

测试模因

Nest 为我们提供了单元、集成和端到端测试的设置。

单元测试

Nest 使用Jest作为单元测试框架。如果您在前端也使用相同的测试框架,这将非常有益,并能显著提升团队的绩效。

我们的 NewsService 的一个简单单元测试:

describe('NewsService', () => {
  let service: NewsService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [NewsService],
    }).compile();

    service = module.get<NewsService>(NewsService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('should return first news article', () => {
    expect(service.findAll()).toEqual([{ title: 'My first news' }]);
  });
});
Enter fullscreen mode Exit fullscreen mode

由于依赖注入,模拟服务也变得非常容易,例如在控制器测试中:

describe('News Controller', () => {
  let controller: NewsController;

  const testNews = {
    title: 'Test News',
  };

  class NewsServiceMock {
    public findAll() {
      return [testNews];
    }
  }

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [NewsController],
      providers: [{ provide: NewsService, useClass: NewsServiceMock }],
    }).compile();

    controller = module.get<NewsController>(NewsController);
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  it('should get news', () => {
    expect(controller.findAll()).toEqual([testNews]);
  });
});
Enter fullscreen mode Exit fullscreen mode

端到端测试

通过端到端测试,您可以测试 API 的全部功能,而不仅仅是某个特定的功能。

Nest 使用Supertest来模拟 HTTP 请求:

describe('News Controller (e2e)', () => {
  let app;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [NewsModule],
    }).compile();

    app = module.createNestApplication();
    await app.init();
  });

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/news')
      .expect(200)
      .expect([{ title: 'My first news' }]);
  });
});
Enter fullscreen mode Exit fullscreen mode

昂首阔步

如果您开发公共 API,则可能需要使用OpenAPI(Swagger)规范来描述 RESTful API。Nest 提供了一个模块来集成它。

我们只需要安装 swagger 包(@nestjs/swagger)并在我们的 main.ts 中添加几行。

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const options = new DocumentBuilder()
    .setTitle('News example')
    .setDescription('The news API description')
    .setVersion('1.0')
    .addTag('news')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

在 main.ts 中,我们指定 API 文档位于/api。因此,如果我们打开浏览器并导航至http://localhost:3000/api,我们将获得以下 API 文档。

NestJS Swagger

入门

我强烈推荐官方 Nest 文档作为起点。

Nest 提供了 Nest CLI,这是一个非常优秀的命令行界面工具,可帮助您初始化和开发应用程序。您可以像在Angular CLI中一样搭建新项目或添加新服务、组件等。

基本上,您只需要这三个命令就可以让基本项目在http://localhost:3000/本地运行:

$ npm i -g @nestjs/cli
$ nest new project-name
$ npm run start
Enter fullscreen mode Exit fullscreen mode

Nest 的未来

Nest 目前在GitHub上拥有超过 1.4 万颗星,在npm上每周下载量超过 10 万次。许多公司已经在生产环境中使用它,其中最大的用户是阿迪达斯。

Nest 作为服务器端 JavaScript 框架,由于其使用 TypeScript 并与 Angular 关联,而显得独树一帜,但它缺乏大型企业的支持。这虽然只是一个小问题,但如果你为你的技术栈选择一个框架,就应该考虑到这一点。

结论

在我看来,Nest 非常适合全栈 Typescript Web 应用程序技术栈,特别是如果您在前端选择 Angular。

Nest 中的文件夹结构和设计模式主要基于 Angular。由于这种简单且相似的结构,我们作为开发人员可以更专注于端点的设计,而无需浪费时间进行应用程序结构设计。Nest 将大量繁琐的 Node.js 代码和丑陋的 JavaScript 代码隐藏在注解、结构和模式之后。由于使用 TypeScript 作为后端语言,从前端到后端代码的切换更加轻松便捷,并且无需进行上下文切换。

文章来源:https://dev.to/mokkaapps/nestjs-the-missing-piece-to-easily-develop-full-stack-typescript-web-applications-34ga
PREV
多元化与包容性:理解差异定义为何重要?共建更美好的科技世界
NEXT
我最常问的 React 面试问题