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
- 782