Back to topics

1 Getting Started

This article is based on nest.js version 10.0 and briefly introduces how to create a Nest project from scratch, as well as common Nest concepts,
such as Controller, Service, Module, Interceptor, Pipe, Guard, Middleware, etc.

npm i -g @nestjs/cli

nest new project-name

// Generate controller
nest g controller name

// Generate service
nest g service name

// Generate module
nest g module name

// Generate CRUD
nest g resource name

Controller

Controller is used to handle HTTP routes, while complex logic is passed to Service.

import { Controller, Get, Req } from "@nestjs/common";
import { Request } from "express";

@Controller("cat") // prefix
export class CatController {
  @Get() // path
  // Parameter can get the Request object from express
  findAll(@Req() request: Request): string {
    // Function name, meaningless
    return "This action returns all cats"; // In standard mode: primitive type return values, object types automatically serialized to JSON
  }
}

Service

Service is responsible for the application logic of business modules.

In the constructor of Controller, you can inject Service. After injection, methods can be called via this.catsService.

@Controller("cats")
export class CatsController {
  // Inject service
  constructor(private catsService: CatsService) {}
}

Try Using Fastify and Versioning

import { NestFactory } from "@nestjs/core";
import { VersioningType } from "@nestjs/common";
import { AppModule } from "./app.module";
import {
  FastifyAdapter,
  NestFastifyApplication,
} from "@nestjs/platform-fastify";

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter()
  );

  app.enableVersioning({
    type: VersioningType.URI,
  });

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

Add Versioning to a Specific API

Add version numbers to APIs using the @Version decorator.

@Version('1')
  @Get()
  async findAll(@Query() query: ListAllEntities) {
    return this.catService.findAll(query);
  }

I encountered a pitfall: after adding versioning, accessing http://localhost:3000/V1/cat returned 404. I overlooked the fact that routes are case-sensitive.

Add Versioning to a Specific Controller

@Controller({
  path: "cat",
  version: "1",
})
export class CatController {
  // some code
}

Add Versioning to the Entire App

app.enableVersioning({
  type: VersioningType.URI,
  defaultVersion: "1",
});

Add Global Middleware

Provide a unified format for API response results.

Ignoring the complex type annotations, this class is essentially a simple interceptor.

If you don't understand next.handle().pipe() for now, you can ignore it.

// common/interceptors/transform.interceptor.ts
// Global middleware

import {
  CallHandler,
  NestInterceptor,
  ExecutionContext,
  Injectable,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs";

@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(map((data) => ({ data, message: "success" })));
  }
}

After unifying the response format, add a global exception interceptor for the App based on the interceptor.

// common/exceptions/base.exception.filter.ts
// Global exception interceptor
// common/exceptions/http.exception.filter.ts
// Handle HTTP exceptions

Debugging with VS Code

Click the Debug panel in VS Code to create a configuration file.
Delete the default configuration, click Add Configuration at the bottom right, enter via npm, select the option, and the configuration below will be added.

via npm means starting debugging via npm.

start:debug is the script defined in package.json.

runtimeVersion is the Node.js version; VS Code will switch Node versions based on nvm.

internalConsoleOptions is a VS Code configuration; neverOpen means not to open the internal console.

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch via NPM",
      "request": "launch",
      "runtimeArgs": ["run-script", "start:debug"],
      "runtimeExecutable": "npm",
      "runtimeVersion": "20.17.0",
      "internalConsoleOptions": "neverOpen",
      "skipFiles": ["<node_internals>/**"],
      "type": "node"
    }
  ]
}

After creating the configuration file, you no longer need to start debugging via the command line; just click the Debug button in VS Code.

Debugging with Chrome

Sometimes you need to debug online issues in a production environment, and you can use Chrome debugging.

Nest provides the start:debug script, which essentially enables inspect mode.

Then you can navigate to chrome://inspect in the browser. In Remote Target, you will see a Nest App. Click inspect to launch DevTools for debugging.

Common Pitfalls at the Start

  • Routes are case-sensitive
  • Returning a string directly causes frontend errors (cannot parse JSON)
  • Forgetting to return in a Controller function results in an empty response to the frontend
  • Trying to return a response inside setTimeout in a Service function to simulate delay