首页 / NestJS 入门教程 / 第一个应用

NestJS 入门教程

第一个应用

本教程共 47 篇 · 第 3 篇 · 更新于 2026-08-09 · 约 10 分钟阅读

NestJSmain.tsAppModuleControllerService启动流程

本节目标:拆解脚手架生成的代码,搞清楚每个文件在干什么,然后自己加路由、加模块。

入口文件 main.ts

每个 NestJS 应用都有一个入口文件 src/main.ts。它做的事情很简单——启动 HTTP 服务。

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

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

这段代码做了两件事:

  1. NestFactory.create(AppModule) —— 用根模块创建一个 NestJS 应用实例
  2. app.listen(3000) —— 在 3000 端口启动 HTTP 服务
Note

bootstrap() 函数名是约定俗成的叫法,你也可以叫别的名字。但大家都叫 bootstrap,就别特立独行了。

NestFactory 是 NestJS 提供的工厂类,负责创建应用实例。它返回的 app 对象实现了 INestApplication 接口,提供了一系列方法,比如设置全局前缀、启用 CORS 等等。后面会用到。

根模块 app.module.ts

NestJS 是模块化的框架,整个应用从根模块开始组装。

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

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

@Module() 装饰器接收一个配置对象,有四个属性:

属性作用
imports导入其他模块(依赖)
controllers注册本模块的控制器
providers注册本模块的服务(提供者)
exports导出服务,让别的模块能用

你可以把模块想象成一个”功能包”。AppModule 是根模块,相当于整个应用的”总装配图”——它告诉 NestJS:“我这个应用有这些控制器、这些服务、依赖了这些模块。”

Tip

现在 imports 是空的,后面加功能模块时会往里面填。

控制器 app.controller.ts

控制器负责接收 HTTP 请求,然后返回响应。

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

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

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

几个关键点:

  • @Controller() 标记这是一个控制器。不传参数就是根路径 /
  • @Get() 标记这个方法处理 GET 请求
  • 构造函数里注入了 AppService,调用它的 getHello() 方法

这里有个你可能注意到的细节:private readonly。这是 TypeScript 的语法糖,一行代码同时完成了”声明属性 + 赋值 + 只读”三件事。

服务 app.service.ts

服务是真正干活的地方,业务逻辑都写在这里。

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

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

@Injectable() 装饰器告诉 NestJS:“这个类可以被依赖注入系统管理。”

你可能会问:为什么不直接在控制器里写业务逻辑?

因为职责分离。控制器只负责”接请求、回响应”,服务负责”干活”。这样以后逻辑变了,只改服务就行,控制器不用动。而且服务可以被多个控制器复用。

Note

你可能会觉得现在这个项目很简单,分不分无所谓。但养成好习惯很重要——等代码多了再想拆分就晚了。

启动流程

整个应用的启动流程可以简化成这样:

1. 执行 main.ts 中的 bootstrap()

2. NestFactory.create(AppModule)
   - 加载根模块
   - 解析模块依赖树
   - 初始化依赖注入容器
   - 实例化所有 Provider

3. app.listen(3000)
   - 创建 HTTP 服务器
   - 绑定端口
   - 开始监听请求

4. 应用就绪,等待请求

你不需要记住每个细节。知道”main.ts 启动 → 加载模块 → 启动服务”这个大流程就够了。

加几个路由试试

光看代码不够直观,来动手改改。

app.controller.ts 里加两个路由:

import { Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';

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

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

  @Get('welcome')
  getWelcome(): string {
    return 'Welcome to NestJS!';
  }

  @Post('create')
  create(): string {
    return 'Resource created!';
  }
}

启动项目后:

  • 访问 GET http://localhost:3000/ → 返回 “Hello World!”
  • 访问 GET http://localhost:3000/welcome → 返回 “Welcome to NestJS!”
  • 发送 POST http://localhost:3000/create → 返回 “Resource created!”

路由装饰器一览:

装饰器HTTP 方法用途
@Get()GET获取资源
@Post()POST创建资源
@Put()PUT完整更新资源
@Patch()PATCH部分更新资源
@Delete()DELETE删除资源
@All()所有方法匹配所有 HTTP 方法

用 CLI 创建一个用户模块

来点实际的。用 CLI 生成一个用户模块,包含控制器和服务:

nest generate module users
nest generate controller users
nest generate service users

三条命令可以合成一条:

nest g resource users

不过 nest g resource 会生成更多文件(包括 DTO、Entity),后面讲到再讲。现在先用手动的方式一步步来。

执行完后,src/ 下会多一个 users/ 目录:

src/
├── users/
│   ├── users.module.ts
│   ├── users.controller.ts
│   └── users.service.ts
├── app.module.ts
├── app.controller.ts
├── app.service.ts
└── main.ts

写用户服务

// users/users.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  private users = [
    { id: 1, name: '张三', email: 'zhangsan@example.com' },
    { id: 2, name: '李四', email: 'lisi@example.com' },
  ];

  findAll() {
    return this.users;
  }

  findOne(id: number) {
    return this.users.find(user => user.id === id);
  }

  create(createUserDto: { name: string; email: string }) {
    const newUser = {
      id: this.users.length + 1,
      ...createUserDto,
    };
    this.users.push(newUser);
    return newUser;
  }
}

这里用了内存数组模拟数据库。数据存在内存里,重启就没了。后面会接真正的数据库。

写用户控制器

// users/users.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(+id);
  }

  @Post()
  create(@Body() createUserDto: { name: string; email: string }) {
    return this.usersService.create(createUserDto);
  }
}

@Controller('users') 设置了路由前缀为 /users。所以:

  • GET /users → 调用 findAll()
  • GET /users/1 → 调用 findOne():id 是路径参数
  • POST /users → 调用 create()@Body() 提取请求体

注册到根模块

// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';

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

注意 imports: [UsersModule] 这一行。根模块导入了用户模块,NestJS 才知道有这个模块存在。

Tip

nest g module users 生成模块时,CLI 会自动把 UsersModule 加到 AppModuleimports 里。手动创建的话别忘了这一步。

测试 API

启动项目,用 curl 测试:

# 获取所有用户
curl http://localhost:3000/users

# 获取单个用户
curl http://localhost:3000/users/1

# 创建用户
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name":"王五","email":"wangwu@example.com"}'

你也可以用 Postman、Insomnia 或者 VS Code 的 REST Client 插件来测试。

应用配置小技巧

main.ts 里的 app 对象还能做很多事情,这里介绍几个常用的:

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

  // 启用 CORS(前后端分离时必须开)
  app.enableCors();

  // 设置全局路由前缀
  app.setGlobalPrefix('api');

  await app.listen(3000);
}
bootstrap();

设了全局前缀后,所有路由都会加上 /api

  • /users/api/users
  • /users/1/api/users/1
Note

enableCors() 在开发时经常用到。前端跑在 5173 端口,后端跑在 3000 端口,不开 CORS 前端请求会被浏览器拦截。

小结

这一章拆解了 NestJS 脚手架项目的四个核心文件:

  • main.ts:启动入口,创建应用实例并监听端口
  • app.module.ts:根模块,组装整个应用
  • app.controller.ts:控制器,处理 HTTP 请求
  • app.service.ts:服务,封装业务逻辑

然后动手加了一个用户模块,体验了 NestJS 的开发流程。

你可能已经感受到了:NestJS 的代码组织很清晰。控制器管路由,服务管逻辑,模块管组装。各干各的事,互不干扰。