NestJS Chapter 01 – New Project

When you use an app, whether it's a web app or a desktop app, that communicates with a remote server over the internet, you can be 99% sure there is an API server involved. API servers are the hidden part of the application where almost all the magic happens. One option for creating APIs is NestJS, and we will explore it a little bit.

Key Benefits of NestJS:

  • Scalability: NestJS is built with scalability in mind, making it easy to manage large-scale applications. Its modular architecture allows developers to split their applications into small, reusable modules.
  • TypeScript Support: As a framework built on top of TypeScript, NestJS offers all the benefits of TypeScript, including type safety, improved code readability, and maintainability.
  • Extensive Ecosystem: NestJS has a rich ecosystem of libraries and tools, which simplifies the development process. It integrates seamlessly with other popular libraries and frameworks like Express, Fastify, and more.
  • Ease of Use: The framework is designed to be developer-friendly, with a well-documented API and a robust CLI that helps in generating boilerplate code, thereby reducing development time.
  • Dependency Injection: NestJS uses a powerful dependency injection system, which makes it easier to manage dependencies and promotes better code organization and testability.

Disclaimer: This article is not primarily intended for people who are new to programming. It is written with the expectation of some experience in programming. To be honest, my motivation for writing this article is to compile my NestJS developer notes, and I've chosen to present them in article form. It will not explain NestJS in detail, nor its architecture. Instead, it will focus on solving specific problems I have encountered. I hope you find it useful.

In Chapter 1, we will install NestJS and bring the API server to life.

Make sure you are using the latest LTS version of NodeJS.

For project creation and management, we will use @nestjs/cli. The easiest way is to install it globally.

npm i -g @nestjs/cli

Then, in your projects folder, simply use the nest command to create a new project.

nest new hardwired-nestjs-api-chapter-01

You will be asked which package manager you want to use. I use npm.

When it is done, just delete the .spec.ts files from the src folder. We don't care about tests right now. A bunch of things are already set up, like prettier, eslint, and TypeScript configuration. Useful scripts are prepared in package.json.

"scripts": {
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },

For now, we only need start:dev to run the development version. In the src folder, you can find main.ts, which is the entry point to our API, along with app.controller.ts, app.module.ts, and app.service.ts. You can find a perfect explanation of the NestJS architecture in the chapters Controllers, Providers, and Modules in the NestJS documentation.

main.ts is pretty simple. Just includes @netjs/core, app.module and bootstrap our application.

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

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

bootstrap();

When you run npm run start:dev, the API will run on port 3000, and you will be able to access it at http://localhost:3000.

Controllers are where you define your routes. In app.controller.ts, there is one GET route.

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();
  }
}

The @Get decorator without parameters in app.controller.ts indicates that you will access this route with /. It's the root route, and you can access it at http://localhost:3000. This route calls the service method getHello(), which returns the string Hello World!. Services are where your business logic resides.

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

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

You can access http://localhost:3000 in your browser, and if the development server is running, you will see Hello World!. However, as an API developer, you may want a better way to call your API during development. I recommend using Insomnia.

When you call http://localhost:3000 using Insomnia, you will get the same result as in the browser. However, as you add more routes and JSON responses, you will appreciate the usefulness of Insomnia.

Hello World!

Hello World is okay, but not very useful. Let's build something more meaningful.

First, we should discuss how to organize our routes to avoid a mess later. A good way is to organize them into resources. A resource can be user, article, comment, etc. For resource bases, we will use plurals, for example, users, articles, and comments. It is a good practice to follow REST principles when creating an API. One of my favorite articles on this topic is Best Practices for Designing a Pragmatic RESTful API.

Sometimes it is easier for programmers to use just GET and POST methods for all routes, but this does not adhere to RESTful principles. RESTful principles provide strategies to handle CRUD actions. It is better to use the full potential of HTTP methods. For example, when we take the resource user:

  • GET /users - retrieves a list of users
  • GET /users/1 - retrieves the user with id 1
  • POST /users - creates a new user
  • PUT /users/1 - updates the user with id 1
  • PATCH /users/1 - partially updates the user with id 1
  • DELETE /users/1 - deletes the user with id 1

You can also combine resources. For instance, to get the articles for a user, one route could be GET /users/1/articles.

If you want to like an article, the API endpoint could be PUT /articles/1/like. You can use aliases, filtering, sorting, limiting, and pagination constructs, but this is a complex and highly opinionated field suitable for many articles. I think the best quick dive is provided in the article I mentioned earlier, written by Vinay Sahni.

Earlier in the article, I said "Let's build something more meaningful," but that was a lie. 🙂 For a quick demonstration of how to create a resource and follow methods, we will use something completely useless.

We will create a route GET /zodiac-signs. This route will return a list of zodiac signs with date ranges. I borrowed the list from here.

To create a NestJS resource, we will use the nest cli.

nest g res zodiac-signs --no-spec
  • nest - nest command
  • g - generate
  • res - resource
  • zodiac-sign - resource-name
  • --no-spec - do not generate tests

After you enter the command, you will be asked what type of transport layer you want to use. Choose REST API. The next question will be whether you would like to create CRUD entry points. I choose no; I will create them on my own.

When it is done, in your src folder, you will see a folder named zodiac-signs, and it will contain zodiac-signs.controller.ts, zodiac-signs.module.ts, and zodiac-signs.service.ts.

Nest CLI automatically connects your new resource/module to the app.

We will start with zodiac-signs.service.ts.

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

@Injectable()
export class ZodiacSignsService {
    signs = {
        aries: {
            symbol: '♈',
            dateMin: '2000-03-21',
            dateMax: '2000-04-20',
        },
        taurus: {
            symbol: '♉',
            dateMin: '2000-04-21',
            dateMax: '2000-05-21',
        },
        gemini: {
            symbol: '♊',
            dateMin: '2000-05-22',
            dateMax: '2000-06-21',
        },
        cancer: {
            symbol: '♋',
            dateMin: '2000-06-22',
            dateMax: '2000-07-22',
        },
        leo: {
            symbol: '♌',
            dateMin: '2000-07-23',
            dateMax: '2000-08-22',
        },
        virgo: {
            symbol: '♍',
            dateMin: '2000-08-23',
            dateMax: '2000-09-23',
        },
        libra: {
            symbol: '♎',
            dateMin: '2000-09-24',
            dateMax: '2000-10-23',
        },
        scorpio: {
            symbol: '♏',
            dateMin: '2000-10-24',
            dateMax: '2000-11-22',
        },
        sagittarius: {
            symbol: '♐',
            dateMin: '2000-11-23',
            dateMax: '2000-12-21',
        },
        capricorn: {
            symbol: '♑',
            dateMin: '2000-12-22',
            dateMax: '2000-01-20',
        },
        aquarius: {
            symbol: '♒',
            dateMin: '2000-01-21',
            dateMax: '2000-02-19',
        },
        pisces: {
            symbol: '♓',
            dateMin: '2000-02-20',
            dateMax: '2000-03-20',
        },
    };

    findAll() {
        return this.signs;
    }
}

In the ZodiacSignsService class, we have our logic. The list of signs is stored as a class property named signs. The method that returns this list is findAll(). It is a good practice for the route GET /zodiac-signs to use the service method findAll, and for GET /zodiac-signs/:name to use the service method findOne. This uniform naming system will help you orient yourself in a large code base.

Next will be zodiac-signs.controller.ts.

import { Controller, Get } from '@nestjs/common';
import { ZodiacSignsService } from './zodiac-signs.service';

@Controller('zodiac-signs')
export class ZodiacSignsController {
  constructor(private readonly zodiacSignsService: ZodiacSignsService) {}

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

We create a method findAll decorated with @Get decorator, which, without parameters, instructs the app router to point to the findAll method in the ZodiacSignsController in response to a GET /zodiac-signs request.

That's all for now. Make sure the development server is running (npm run start:dev). Then, you can access http://localhost:3000/zodiac-signs. The expected result is the same JSON as we have in ZodiacSignsService.

Okay, it's not much, but it's enough to get started. In the next chapter, we'll take a look at DTOs (Data Transfer Objects).

You can find project on my GitHub.

Happy coding!

Loading