Nest JS Understanding power of RX Observables
What is NestJS?
As described in the Nestjs website, Nestjs is a progressive Node.js framework for building efficient, reliable and scalable server-side applications.
Github Link https://github.com/tkssharma/blogs/tree/master/nestjs-rest-apis-docs
Nestjs combines the best programming practice and the cutting-edge techniques from the NodeJS communities.
- A lot of NestJS concepts are heavily inspired by the effort of the popular frameworks in the world, esp. Angular .
- Nestjs hides the complexities of web programming in NodeJS, it provides a common abstraction of the web request handling, you are free to choose Expressjs or Fastify as the background engine.
- Nestjs provides a lot of third party project integrations, from database operations, such as Mongoose, TypeORM, etc. to Message Brokers, such as Redis, RabbitMQ, etc.
If you are new to Nestjs like me but has some experience of Angular , TypeDI or Spring WebMVC, bootstraping a Nestjs project is really a piece of cake.
Generating a NestJS project
Make sure you have installed the latest Nodejs.
npm i -g @nestjs/cli
When it is finished, there is a nest
command available in the Path
. The usage of nest
is similar with ng
(Angular CLI), type nest --help
in the terminal to list help for all commands.
❯ nest --help
Usage: nest <command> [options]
Options:
-v, --version Output the current version.
-h, --help Output usage information.
Commands:
new|n [options] [name] Generate Nest application.
build [options] [app] Build Nest application.
start [options] [app] Run Nest application.
info|i Display Nest project details.
update|u [options] Update Nest dependencies.
add [options] <library> Adds support for an external library to your project.
generate|g [options] <schematic> [name] [path] Generate a Nest element.
Available schematics:
┌───────────────┬─────────────┐
│ name │ alias │
│ application │ application │
│ class │ cl │
│ configuration │ config │
│ controller │ co │
│ decorator │ d │
│ filter │ f │
│ gateway │ ga │
│ guard │ gu │
│ interceptor │ in │
│ interface │ interface │
│ middleware │ mi │
│ module │ mo │
│ pipe │ pi │
│ provider │ pr │
│ resolver │ r │
│ service │ s │
│ library │ lib │
│ sub-app │ app │
└───────────────┴─────────────┘
Now generate a Nestjs project via:
nest new nestjs-sample
Open it in your favorite IDEs, such as Intellij WebStorm or VSCode.
Exploring the project files
Expand the project root, you will see the following like tree nodes.
.
├── LICENSE
├── nest-cli.json
├── package.json
├── package-lock.json
├── README.md
├── 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
The default structure of this project is very similar with the one generated by Angular CLI.
- src/main.ts is the entry file of this application.
- src/app* is the top level component in a nest application.
- There is an app.module.ts is a Nestjs
Module
which is similar with AngularNgModule
, and used to organize codes in the logic view. - The app.service.ts is an
@Injectable
component, similar with the service in Angular or Spring’s Service, it is used for handling business logic. A service is annotated with@Injectable
. - The app.controller.ts is the controller of MVC, to handle incoming request, and responds the handled result back to client. The annotatoin
@Controller()
is similar with Spring MVC’s@Controller
. - The app.controller.spec.ts is test file for app.controller.ts. Nestjs uses Jest as testing framework.
- There is an app.module.ts is a Nestjs
- test folder is for storing e2e test files.
We can simple Controller and service in the sample code that looks something like this
import { Get, Post, Body, Put, Delete, Param, Controller, UsePipes } from '@nestjs/common';
import { Request } from 'express';
import { UserService } from './user.service';
import { UserRO } from './user.interface';
import { CreateUserDto, UpdateUserDto, LoginUserDto } from './dto';
import { HttpException } from '@nestjs/common/exceptions/http.exception';
import { User } from './user.decorator';
import { ValidationPipe } from '../shared/pipes/validation.pipe';
import {
ApiBearerAuth, ApiTags
} from '@nestjs/swagger';
@ApiBearerAuth()
@ApiTags('user')
@Controller()
export class UserController {
constructor(private readonly userService: UserService) {}
@Get('user')
async findMe(@User('email') email: string): Promise<UserRO> {
return await this.userService.findByEmail(email);
}
@Put('user')
async update(@User('id') userId: number, @Body('user') userData: UpdateUserDto) {
return await this.userService.update(userId, userData);
}
}
User service
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, getRepository, DeleteResult } from 'typeorm';
import { UserEntity } from './user.entity';
import {CreateUserDto, LoginUserDto, UpdateUserDto} from './dto';
const jwt = require('jsonwebtoken');
import { SECRET } from '../config';
import { UserRO } from './user.interface';
import { validate } from 'class-validator';
import { HttpException } from '@nestjs/common/exceptions/http.exception';
import { HttpStatus } from '@nestjs/common';
import * as argon2 from 'argon2';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>
) {}
async findAll(): Promise<UserEntity[]> {
return await this.userRepository.find();
}
async findOne({email, password}: LoginUserDto): Promise<UserEntity> {
const user = await this.userRepository.findOne({email});
if (!user) {
return null;
}
if (await argon2.verify(user.password, password)) {
return user;
}
return null;
}
}
And Finally main Module
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ArticleModule } from './article/article.module';
import { UserModule } from './user/user.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Connection } from 'typeorm';
import { ProfileModule } from './profile/profile.module';
import { TagModule } from './tag/tag.module';
@Module({
imports: [
TypeOrmModule.forRoot(),
ArticleModule,
UserModule,
ProfileModule,
TagModule
],
controllers: [
AppController
],
providers: []
})
export class ApplicationModule {
constructor(private readonly connection: Connection) {}
}
This is sample how we write the controller and service now lets see how can we do same using observable and RX JS Operators
RxJS (Reactive Extensions for JavaScript) is a library that provides a set of powerful tools for reactive programming in JavaScript. It is based on the concept of Observables, which are a way to handle asynchronous data streams and perform operations on those streams in a functional and declarative manner.
In traditional programming, you typically write imperative code that explicitly specifies how to perform actions and handle data. However, with RxJS and the concept of Observables, you can write code that reacts to changes in data and events, allowing you to build more flexible and responsive applications.
Observables represent data streams over time. They can emit multiple values, including asynchronous data such as HTTP responses, events, or user input. Observables can be created from various sources such as arrays, events, timers, or even custom sources.
Observables provide a wide range of operators that allow you to transform, filter, combine, and manipulate the data streams. These operators follow functional programming principles and enable you to create complex data processing pipelines with ease. Some common operators include map, filter, reduce, merge, and debounce, among many others.
One of the key benefits of using Observables is that they support composition. You can combine multiple observables, apply operators to them, and create new observables as a result. This composability makes it easy to build complex asynchronous workflows and handle data dependencies.
Another important aspect of Observables is that they support handling errors and completion. Observables can emit error notifications when something goes wrong during data processing, allowing you to handle and recover from errors gracefully. They also emit a completion notification when the stream of data has ended, indicating that no more values will be emitted.
Overall, RxJS and Observables provide a powerful and expressive way to handle asynchronous and event-based programming in JavaScript. They enable you to write code that is more reactive, declarative, and efficient when dealing with complex asynchronous scenarios, making it easier to manage and process data streams in a predictable and composable way.
Using RX JS with Nest JS Project
Using RxJS with a NestJS application can enhance its capabilities for handling asynchronous operations and creating reactive pipelines. Here are some ways you can integrate RxJS into your NestJS application:
-
Asynchronous Operations: NestJS applications often involve interacting with external services or performing time-consuming operations. RxJS can help manage these asynchronous tasks. You can use Observables to represent asynchronous data streams and apply operators to handle data processing. For example, you can use the
from
operator to convert promises or callback-based functions into Observables. -
Reactive Controllers: NestJS controllers handle incoming requests and generate responses. By leveraging RxJS, you can create reactive endpoints that respond to changes in data or events. You can use Observables to represent data streams, apply operators to transform the data, and then return the result as a response.
-
Inter-service Communication: In a microservices architecture, NestJS applications may need to communicate with other services. RxJS can facilitate this communication by using Observables as a means of streaming data between services. You can use operators like
switchMap
ormergeMap
to handle data dependencies and make multiple service calls in a reactive manner. -
Middleware and Pipes: NestJS provides middleware and pipes for intercepting and modifying incoming requests and outgoing responses. You can use RxJS operators to handle asynchronous operations within middleware or pipes. For example, you can use the
map
operator to transform the data or thecatchError
operator to handle errors. -
Event-driven Programming: NestJS applications can benefit from event-driven programming, where components react to events and trigger actions accordingly. RxJS provides a rich set of operators to handle event streams. You can use subjects or event emitters as Observables to represent events and use operators like
filter
ordebounceTime
to handle event stream transformations. -
Testing: RxJS offers testing utilities that can be used to write unit tests for NestJS applications. You can use operators like
toArray
ortoPromise
to convert Observables into arrays or promises, allowing you to assert the emitted values during testing.
Remember to import the necessary RxJS modules, such as Observable
and required operators, in your NestJS components to start using RxJS functionality effectively within your application.
By integrating RxJS into your NestJS application, you can harness the power of reactive programming, handle asynchronous operations gracefully, and create responsive, event-driven architectures.
Here’s an example code snippet that demonstrates how to use RxJS within a NestJS application:
import { Controller, Get } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Controller('example')
export class ExampleController {
@Get()
getData(): Observable<any> {
return this.fetchData().pipe(
map(data => {
// Transform the data using RxJS operators
return this.processData(data);
}),
);
}
private fetchData(): Observable<any> {
// Simulate an asynchronous data retrieval
return new Observable(observer => {
setTimeout(() => {
observer.next('Example data');
observer.complete();
}, 1000);
});
}
private processData(data: any): any {
// Perform some processing on the data
return data.toUpperCase();
}
}
In the above example, we have a simple NestJS controller with a single endpoint (/example
) that retrieves and processes data using RxJS. Here’s a breakdown of the code:
- We import the necessary modules from
@nestjs/common
andrxjs
. - The
ExampleController
class is decorated with the@Controller
decorator, specifying the base route path as'example'
. - The
getData()
method is decorated with@Get()
to handle HTTP GET requests to the/example
route. - Within
getData()
, we call thefetchData()
method, which returns an Observable representing an asynchronous data retrieval. We simulate this retrieval by using asetTimeout
function to emit a single value ('Example data'
) and complete the observable after 1 second. - The
map
operator is applied to the Observable returned byfetchData()
. This operator transforms the emitted data by passing it to theprocessData()
method, which converts it to uppercase. - The transformed data is returned as the response of the
/example
endpoint.
This example demonstrates how you can use RxJS to handle asynchronous operations and apply transformations to the data stream within a NestJS controller. You can build upon this foundation to implement more complex asynchronous workflows and handle data dependencies using RxJS operators and observables.
Here’s a continuation of the example code, focusing on consuming the Observable in a NestJS service:
import { Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { HttpClient } from '@nestjs/common';
@Injectable()
export class ExampleService {
constructor(private readonly http: HttpClient) {}
fetchData(): Observable<any> {
const apiUrl = 'https://api.example.com/data';
return this.http.get(apiUrl);
}
processData(data: any): any {
// Perform any processing on the data
return {
...data,
processed: true,
};
}
}
In this example, we have a NestJS service (ExampleService
) that is responsible for fetching and processing data using RxJS Observables. Here’s an explanation of the code:
- We import the necessary modules from
@nestjs/common
andrxjs
, including theHttpClient
module for making HTTP requests. - The
ExampleService
class is decorated with the@Injectable()
decorator, indicating that it can be injected as a dependency. - We inject the
HttpClient
into the service’s constructor using dependency injection. - The
fetchData()
method uses thehttp.get()
method from theHttpClient
to make an HTTP GET request to an API endpoint (apiUrl
). The method returns an Observable that represents the asynchronous data retrieval. - The
processData()
method takes the retrieved data and performs any necessary processing on it. - You can add additional methods to the service to handle further data operations using RxJS operators and observables.
By separating the data retrieval and processing logic into a service, you can leverage the power of RxJS Observables within your NestJS application. This allows you to handle asynchronous operations, make API requests, process data, and respond to events in a reactive and composable manner.
Remember to import the required modules and configure the HttpClient
module properly in your NestJS application’s module file (app.module.ts
) to enable the use of the HttpClient
within services.
By utilizing RxJS Observables in your NestJS service, you can create a robust and reactive data pipeline that seamlessly integrates with external APIs, performs data transformations, and enables efficient handling of asynchronous operations.
Another example
Here’s an example of how you can create a NestJS API for a Todo application using RxJS Observables:
import { Controller, Get, Post, Body, Param, Delete } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
interface Todo {
id: string;
title: string;
completed: boolean;
}
@Controller('todos')
export class TodoController {
private todos: Todo[] = [];
@Get()
getAllTodos(): Observable<Todo[]> {
return new Observable(observer => {
observer.next(this.todos);
observer.complete();
});
}
@Post()
createTodo(@Body() todo: Todo): Observable<Todo> {
return new Observable(observer => {
this.todos.push(todo);
observer.next(todo);
observer.complete();
});
}
@Delete(':id')
deleteTodoById(@Param('id') id: string): Observable<void> {
return new Observable(observer => {
const index = this.todos.findIndex(todo => todo.id === id);
if (index !== -1) {
this.todos.splice(index, 1);
}
observer.next();
observer.complete();
});
}
@Post(':id/complete')
completeTodoById(@Param('id') id: string): Observable<Todo> {
return new Observable(observer => {
const todo = this.todos.find(todo => todo.id === id);
if (todo) {
todo.completed = true;
observer.next(todo);
}
observer.complete();
});
}
}
In this example, we have a TodoController
that handles CRUD operations for a Todo application. Here’s an explanation of the code:
- We import the necessary modules from
@nestjs/common
andrxjs
. - The
TodoController
class is decorated with the@Controller
decorator, specifying the base route path as'todos'
. - The
getAllTodos()
method handles the GET request to/todos
and returns an observable that emits the list of todos. - The
createTodo()
method handles the POST request to/todos
and returns an observable that emits the created todo. - The
deleteTodoById()
method handles the DELETE request to/todos/:id
and returns an observable that emits no value upon successful deletion. - The
completeTodoById()
method handles the POST request to/todos/:id/complete
and returns an observable that emits the updated todo upon successful completion.
In this example, we use simple in-memory storage (this.todos
) to store the todo items. However, you can replace this with a database or any other persistent storage mechanism.
By returning RxJS Observables from the controller methods, you can leverage the power of reactive programming to handle asynchronous operations, perform data transformations, and compose multiple operations using operators such as map
, filter
, or switchMap
.
Remember to import the required modules and configure the routing appropriately in your NestJS application’s module file (app.module.ts
) to enable the use of the TodoController
.
With this example, you have a foundation for building a Todo API in NestJS using RxJS Observables. You can extend and customize this code to fit the specific needs of your application, including additional CRUD operations, validation, authentication, and more.