Back-end TypeScript apps inspired by Angular

Node.js – a great platform for implementing APIs

But what good frameworks do we really have?

Express!

var express = require('../../');

var app = express();

app.get('/', function(req, res){
  res.send('Hello World');
});

/* istanbul ignore next */
if (!module.parent) {
  app.listen(3000);
  console.log('Express started on port 3000');
}

No, wait. Koa!

const Koa = require('koa');
const app = module.exports = new Koa();

app.use(async function(ctx) {
  ctx.body = 'Hello World';
});

if (!module.parent) app.listen(3000);

Or Hapi?..

const Hapi   = require('hapi');
const Server = new Hapi.Server();
const Hello  = require('./lib/hello');

Server.connection({ port: 3000 });

Server.route({
    method: 'GET',
    path: '/hello/{user}',
    handler: function (request, reply) {
        const result = Hello(decodeURIComponent(request.params.user));
        reply(result);
    }
});

They all are good, but...

...don't provide enough

Wait, there is one guy!

Nest.js!

Nest.js

  • was built mainly to eliminate disorganized codebases
  • give Node.js application a moderate and reasonable structure out of the box
  • heavily inspired by Angular, was built with TypeScript
  • uses Express.js under hood (compatibility with the majority of express middleware)

Nest.js was introduced to solve the architectural problem of Node.js by giving backend applications a modular structure for organising code into separate modules.

  • Fully built with TypeScript
  • Similar project structure as it is for Angular applications
  • Key concepts of Angular
  • it surrounds your route handler body with try..catch blocks

  • it makes every route handler async

  • it creates a global express router

  • it creates a separated router for each controller

  • it binds error-handling middleware

  • it binds body-parser middleware (both json and extended urlencoded)

Controllers

// users.controller.ts 

import { Controller, Get } from '@nestjs/common';

@Controller('users')
export class UsersController {
 @Get()
 findAll() { 
   return 'This will return all the users';
 }
}

Providers

// users.service.ts

import { Injectable } from '@nestjs/common';
import { User } from './interfaces/user.interface';

@Injectable()
export class UsersService {
  private readonly users: User[] = [];

  create(user: User) { 
    this.users.push(user);   }

  findAll(): User[] {
    return this.users;
  }
}

Modules

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller.ts';
import { UsersService } from './users.service.ts';

@Module({
  controllers: [UsersController],
  providers: [UsersService]
})

export class UsersModule {}
...
import { UsersModule } from './users/users.module';

@Module({
  ...
})

export class AppModule { }

Guards

// auth.guard.ts

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

Other important concepts:

  • DTO
  • Interfaces
  • Dependency injection
...
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService){}
 ...
}

How to get started with Nest.js?

Installing

npm i -g @nestjs/cli

Creating a project

nest new bookstore-nest

Starting 

// change directory
cd bookstore-nest


// start the application
npm run start

// or start the application using nodemon
npm run start:dev

Generating a module

nest generate module books
// ./src/books/books/module.ts

import { Module } from '@nestjs/common';
@Module({})
export class BooksModule {}

Creating routes

nest generate controller books

Setting up a service

nest generate service books

Implementing: get books

//  ./src/books/books.service.ts

  import { Injectable, HttpException } from '@nestjs/common';
  import { BOOKS } from '../mocks/books.mock';

  @Injectable()
  export class BooksService {
      books = BOOKS;

      getBooks(): Promise<any> {
          return new Promise(resolve => {
              resolve(this.books);
          });
      }

      getBook(bookID): Promise<any> {
          let id = Number(bookID);
          return new Promise(resolve => {
              const book = this.books.find(book => book.id === id);
              if (!book) {
                  throw new HttpException('Book does not exist!', 404);
              }
              resolve(book);
          });
      }
  }

Implementing: add book

//  ./src/books/books.service.ts

import { Injectable, HttpException } from '@nestjs/common';
import { BOOKS } from '../mocks/books.mock';
@Injectable()
export class BooksService {
    books = BOOKS;
    ...
    addBook(book): Promise<any> {
        return new Promise(resolve => {
            this.books.push(book);
            resolve(this.books);
        });
    }
}

Implementing: delete book

//  ./src/books/books.service.ts

import { Injectable, HttpException } from '@nestjs/common';
import { BOOKS } from '../mocks/books.mock';
@Injectable()
export class BooksService {
    books = BOOKS;
    ...
    deleteBook(bookID): Promise<any> {
        let id = Number(bookID);
        return new Promise(resolve => {
            let index = this.books.findIndex(book => book.id === id);
            if (index === -1) {
                throw new HttpException('Book does not exist!', 404);
            }
            this.books.splice(1, index);
            resolve(this.books);
        });
    }
}

Injecting service into controller 

// ./src/books/books.controller.ts

import { Controller, Get, Param, Post, Body, Query, Delete } from '@nestjs/common';
import { BooksService } from './books.service';
import { CreateBookDTO } from './dto/create-book.dto';

@Controller('books')
export class BooksController {
    constructor(private booksService: BooksService) { }

    @Get()
    async getBooks() {
        const books = await this.booksService.getBooks();
        return books;
    }
    @Get(':bookID')
    async getBook(@Param('bookID') bookID) {
        const book = await this.booksService.getBook(bookID);
        return book;
    }
    @Post()
    async addBook(@Body() createBookDTO: CreateBookDTO) {
        const book = await this.booksService.addBook(createBookDTO);
        return book;
    }
    @Delete()
    async deleteBook(@Query() query) {
        const books = await this.booksService.deleteBook(query.bookID);
        return books;
    }
}

The DTO

// ./src/books/dto/create-book.dto.ts

export class CreateBookDTO {
    readonly id: number;
    readonly title: string;
    readonly description: string;
    readonly author: string;
}

Techniques from official docs:

  • Authentication
  • Database
  • Mongo
  • File upload
  • Validation
  • Caching
  • Serialization
  • Logger
  • Security
  • Configuration
  • Compression
  • HTTP module
  • Model-View-Controller
  • Performance (Fastify)

Supports:

  • GraphQL
  • Microservices
  • Websockets

Nest.js – good choice for your next API

Back-end TypeScript Apps inspired by Angular

By Nikita Malik

Back-end TypeScript Apps inspired by Angular

Developing back-end APIs with Nest.js

  • 702