NestJS is a framework to build server-side application with Node.js and using typescript by default.

This framework more than just a web server comes with the promise to help developers build larger backend application following best practices. Making it according to the documentation testable, scalable and easily maintainable. The Architecture of NestJS applications is inspired by Angular framework structure.

If you are familiar with springboot in Java, then the learning curve shouldn’t be too steep. Let’s create a simple REST API in typescript using NestJS! 💪

Get Started

Install the NestJS CLI

To get started with a NestJS app, install the Nest.js CLI:

npm install -g @nestjs/cli

The CLI (Command Line Interface), in the case of NestJS is far more than for just a one time use to initialize the project. It will be useful throughout its lifecycle, we’ll detail it as we go.

Initialise the NestJS app

To create an example app, run with the installed nest cli:

nest new example -p npm

It will create a new simple NestJS project in the example folder with npm as the node package manager (you can use yarn as well) and do the installation of the modules. The project is ready to run using npm start!

By default, the NesJS apps run on port 3000.

Project structure

Now that the example project is created, you should see something like:

.
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── src
   ├── app.controller.spec.ts
   ├── app.controller.ts
   ├── app.module.ts
   ├── app.service.ts
   └── main.ts
├── test
   ├── app.e2e-spec.ts
   └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

Here is a brief explanation of each file:

  • nest-cli.json: Configuration file for the NestJS CLI tool.
  • src: The main source code directory.
    • app.controller.spec.ts: Unit tests for app.controller. (Directly next to the file and not in test 😲)
    • app.controller.ts: The main controller file which handles incoming requests and returns responses.
    • app.module.ts: A module is a class annotated with @Module() decorator. The root module of the application is AppModule.
    • app.service.ts: A service that AppModule uses. Services can be injected into modules using Nest’s dependency injection (DI) system.
    • main.ts: The entry file of the application which uses the core function NestFactory to create a Nest application instance.
  • test: Contains end-to-end tests for the application.
    • app.e2e-spec.ts: Example end-to-end test for the NestJS application.
    • jest-e2e.json: Configuration file for end-to-end tests run with Jest. (used for npm run test:e2e)
  • tsconfig.build.json and tsconfig.json: Configuration files for TypeScript compiler. The tsconfig.build.json is mainly used for production builds.

These files together form the basic structure of a NestJS application. NestJS follows the modular structure, and has a main module (AppModule) which is the entry point for the application. Any additional modules will usually be children of this main module.

Developing the application

Let’s have a look on how to continue the implementation of a NestJS application with a new resource. We will have a new controller, and a new service.

1. Controller

Generates using the CLI

Use the NestJS CLI to create the resource and the base controller for you.

nest g controller MyResource

It will link the new controller created in the new resource called my-resource (the nomenclature was updated automatically) and then linked it to the application via the AppModule in app.module.ts.

└── src
    ├── # ...other files
    ├── app.module.ts
    └── my-resource
        ├── my-resource.controller.spec.ts
        └── my-resource.controller.ts

Let’s have a look at the newly created files.

Implementation

We have the newly created controller which does nothing:

@Controller('my-resource')
export class MyResourceController {}

But now we can start adding new Rest controls, like a GET using the annotations:

@Controller('my-resource')
export class MyResourceController {
    @Get(':id')
    findOne(@Param('id') id: string): string {
        return `Returns #${id}`;
    }
}

On a GET call to my-resource/id, it will return 'Returns #id'. As you can see the resource’s name is used in the URI, and matches the controller.

End-to-end test

Now that we have a controller which advertise the resource, let see how the testing works. In the app.e2e-spec.ts, you should see a test example, we’ll create one of our own for the created controller.

describe('AppController (e2e)', () => {
    
    // Autogenerated code with Test application
    
    describe('My Resource', () => {
        it('can be retrieved', () => {
            return request(app.getHttpServer())
                .get('/my-resource/id')
                .expect(200)
                .expect('Returns #id');
        });
    });
});

Using supertest a HTTP testing library we make a real HTTP call to a test application and assert that it returns the expected result. The application built for the test is part of the autogenerated code and built via the imports from @nestjs/testing.

Tenfold the resource’s creation

We demonstrated how to add one Rest controller and test it. But you can autogenerate the creation of the controller for CRUD operation or via GraphQL using:

npm g resource MyNewResource

For a REST controller, this will create more objects, with the DTOs (Data Transfer Objects) and Entities (Domain object), which follow the layered architecture pattern for separates the business logic from the transport layer.

2. Service

Generation via CLI

Now that we have our controller ready, we can enhance it using a service which will be injected as a dependency to it. Using a service in a controller allows you to decouple the controller logic from the service’s business logic.

Let’s create a new service using the NestJS CLI:

nest g service MyResource

This will create a service named ExampleService, we will also create some DTO and an entity, so that we can easily create the transfer object to a domain object.

Implementation

We will update the created service with a create method to add example object to a private list:

@Injectable()
export class MyResourceService {
  private readonly resources: MyResource[] = [];

  create(createMyResourceDto: CreateMyResourceDto): MyResource {
    const resource = MyResource.from(createMyResourceDto);
    this.resources.push(resource);
    return resource;
  }
}

Now we can use that service in our controller, since it’s also about the same resource MyResource, it is under the same folder. We can definitely see that NestJS pushes for domain driven development, as everything is sorted around the resource.

@Controller('my-resource')
export class MyResourceController {
  constructor(private service: MyResourceService) {}

  @Post()
  @HttpCode(204)
  create(@Body() createMyResourceDto: CreateMyResourceDto): MyResource {
    return this.service.create(BusinessCat.fromCreatedCat(createCatDto));
  }
}

This injection of services is similar to a @Service that is @Autowired from springboot, here in NestJS, it’s used with the @Injectable notation and passing the service as a constructor parameter.

On create, it will save the new resource and return 204 created.

Unit test

We could have a new end-to-end test for this new POST on /my-resource, but that would be trivial since we have made one earlier. Instead, let’s look at the *.spec.ts which is the unit test file that was created at the same time as we generated the controller or the service.

For the MyController test file, I have added our service as a provider and we have:

describe('MyResourceController', () => {
  let controller: MyResourceController;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [MyResourceController],
      providers: [MyResourceService], // Added manually once the service was created
    }).compile();

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

Which means that the unit test in this file using this test module will only load the specified controllers and providers! If you miss a dependency, you will get an error, here is the one I would have had without the service in the providers for the test:

Error: Nest can't resolve dependencies of the MyResourceController (?). Please make sure that the argument MyResourceService at index [0] is available in the RootTestModule context.

That’s because the service is now injected in the controller and is now needed to start the controller, even for testing purpose. Let’s add a unit test for the create method in our controller:

it('should create a resource', () => {
  expect(controller.create({ id: 'example' })).toEqual({ id: 'example' });
})

In this case our DTO is very similar to the domain object, but we make sure that the code within service is called and returns the proper value. What’s great about those unit tests is that they are by default encapsulated, meaning if I create a resource in one test, it won’t affect the next one.

3. Module

Generating via the CLI

Create a module from the created controller and service for my-resource using the CLI:

nest g module MyResource

This will create a new file my-resource.nodule.ts and update the AppModule. By default, it creates an empty class with an empty @Module annotation. But this class is actually useful to regroup the controller, services and such as one module, making it easier to track as dependency.

Implementation

Let’s update the MyResource module with our controller and service:

@Module({
  controllers: [MyResourceController],
  providers: [MyResourceService],
})
export class MyResourceModule {}

Now that we have defined our module, we can use that definition in the AppModule to tidy it up a little. As it’s been updated for each controller and service generation, we in fact only need to import the module instead of each parts of it.

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

Now we can see that the MyResourceController and MyResourceService are now encapsulated in the module.

Module in tests

Now that we have created our module, we can use it in our test, for example for the my-resource.controller.spec.ts, instead of adding the controller and provider when creating the testing module, you could just do:

const module: TestingModule = await Test.createTestingModule({
  imports: [MyResourceModule],
}).compile();

For module specific tests, it will ensure that your unit test stays up to par with the added dependency of your controller.

Conclusion

That’s it for now, you should have enough to get started with your NesJS application. The documentation provided is well-made, so make sure to have a look if you are in a bind.

A bit sceptic at first with the annotations and the framework, as I feared it might be too rigid and create too much abstraction. But in the end, with the CLI helping you to create the resources, you feel more confident in working within the application.

It’s like having guard rail to make sure your application stays in well-structured, which is appreciable.
There are some similarity with Springboot, for less hassle it seems thanks to the command line interface, and the fact it was designed to be used in a pre-defined way.

I believe this could be a good framework for a sizeable project with a range of developers from novice to more advanced.