Nest.js is a progressive Node.js Web Framework that provides you with a robust backend for your frontend applications. It is highly comparable to Angular in terms of concepts like Module, Provider, etc. and is a clear choice by Angular developers.
If you are interested, read more about Nest.js at:
- Nest.js official documentation
- Node.js v10.16.2 or > 10.x
- Docker v19.03
- Code editor (Visual Studio Code, Sublime, etc.)
For this article, I've chosen to use the mysql database engine or "mysql". I will run an instance of mysql using a mysql Docker container, which I believe, is the cleanest, and easiest way to add a PostgreSQL database instance to your application.
Start by creating a new docker-compose.yml
at the root of the Angular app and paste the following content inside it:
# Run `docker-compose build` to build the images
# Run `docker-compose up` to run the containers
version: '3.5'
services:
mongo:
image: mongo
container_name: global-mongo-service
restart: unless-stopped
volumes:
- mongo_data:/data/configdb
- mongo_data:/data/db
ports:
- 27017:27017
networks:
- core_service_network
apis:
command: npm run debug
build: ./api-app
ports:
- 3000:3000
- 5858:5858
volumes:
- ./api-app/docker/node/node-docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh
- ./api-app:/app
env_file:
./api-app/.env
depends_on:
- mongo
networks:
- core_service_network
networks:
core_service_network:
driver: bridge
name: core_service_network
volumes:
mongo_data:
name: global_mongo
apis_modules:
name: apis_modules
My docker file for apis in nestjs
FROM node:carbon
WORKDIR /app
We can configure entrypoint for image bootstrap while initilize
- api-app/docker/node/node-docker-entrypoint.sh
#!/bin/sh
set -e
npm install
if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ]; then
set -- node "$@"
fi
exec "$@"
This docker-compose file instructs Docker to create a new mysql & node js Docker container with the following settings:
- The container name is api-app
- The Docker image mysql
- Create a new volume by mapping the physical folder named mysql_data to an internal folder inside the image. I will place an initialization script inside this folder so that Docker can run the first time it creates the mysql container.
- Finally, you expose the mysql instance to the host machine by mapping its internal port to a port used on the host machine 3306 is our port and also we are exposing node js container port which is 3000
As we are passing environments variables for mysql containers, it will create test database with defined user root so we don't need to manually create database and users
Add the following script under the script
node inside the package.json
file:
Finally, run the following command to start the container:
- docker-compose up &
- docker logs apis --tail 50 -f
This command will create the node js and mysql container in a detached mode. Now that the mysql database is up and running, let's move on and continue adding more features.
Add Mongoose module
Nest supports two methods for integrating with the MongoDB database. You can either use the built-in TypeORM module described here, which has a connector for MongoDB, or use Mongoose, the most popular MongoDB object modeling tool. In this chapter we'll describe the latter, using the dedicated @nestjs/mongoose package.
Start by installing the required dependencies:
$ npm install --save @nestjs/mongoose mongoose
$ npm install --save-dev @types/mongoose
Once the installation process is complete, we can import the MongooseModule into the root AppModule.
To start using TypeORM in the Nest.js application, we need to install a few NPM packages. Run the command:
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [MongooseModule.forRoot('mongodb://localhost/nest')],
})
export class AppModule {}
Let's take step back and see all steps one by one
- The
MongooseModule
@nestjs/mongoose
package represents the Nest.js wrapper for Mongoose.
Our simple basic tsconfig will look like this
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"strict": true,
"skipLibCheck": true
},
"exclude": ["node_modules", "dist"],
"include": ["src/**/*.ts"]
}
Build the Training
Now that the full-stack application is up and running with an active connection to the database, it’s time to start building the Training API.
This next section is a step by step on how to write apis with Monggo DB
- Add a new module in Nest.js
- Add model objects
- Add a Nest.js service
- Add a Nest.js controller to test the application.
Let’s get started. Lets try to see how we were developing apis in node js earlier with Mongoose
- creating schem Model
- crearting mongo connection with mongo url
- start running query in controller/service to fetch data from Mongo DB using Mongoose library
- creating express controllers and services and getting data for different api Routes
Lets Build simple App
Nest.js framework offers the Nest.js CLI. This component is similar to Angular CLI, or other CLI. The goal of the CLI, is to increase productivity by enhancing the software development process, and make it easier on the developer to add new Nest.js artifacts to the application.
Install the Nest.js CLI globally on your machine by running:
npm install -g @nestjs/cli
nest g module training --no-spec
The command creates a new Training module under the path /server/src/blog
. In addition, it also imports this module into the main app.module.ts
file.
Add model objects
We will create the Training entity objects
import * as mongoose from 'mongoose';
import { Document } from 'mongoose';
export const YouTubeSchema = new mongoose.Schema({
kind: String,
id: String,
etag: String,
contentDetails: mongoose.Schema.Types.Mixed,
snippet: mongoose.Schema.Types.Mixed,
status: mongoose.Schema.Types.Mixed,
created_at: { type: Date, default: Date.now },
},
);
export interface YouTube extends Document {
readonly kind: string;
readonly id: string;
readonly etag: string;
readonly contentDetails: object;
readonly snippet: object;
readonly description: string;
readonly status: object;
readonly created_at: Date;
}
Connecting to Mongo Database
- we have to follow simple steps to create database Module
- create controller
- create services
- creating root Module to run application
Connect to database using Mongoose Module
We just need connection url and use this module we can connect
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [MongooseModule.forRoot('mongodb://localhost/nest')],
})
export class AppModule {}
If we are getting configuration from some other Config Module then we can create dynamic Module for Mongoose and get DB connection
- dynamic way of creating Module by injecting services
- Config Mofule is providing congiguration
@Module({})
export class DatabaseModule {
public static getNoSqlConnectionOptions(config: ConfigService): MongooseModuleOptions {
const dbdata = config.get().mongo;
if (!dbdata) {
throw new CommonConfigError('Database config is missing');
}
return dbdata;
}
public static forRoot(): DynamicModule {
return {
module: DatabaseModule,
imports: [
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => DatabaseModule.getNoSqlConnectionOptions(configService),
inject: [ConfigService],
}),
],
controllers: [],
providers: [],
exports: [],
};
}
}
And finally we can use this DatabaseModule in our root Module to provide mongo DB connection, now we can register our schema for mongo db collections using MongooseModule.forFeature
MongooseModule.forFeature(
[
{ name: 'youtubes', schema: YouTubeSchema }
{ name: 'Training', schema: TrainingSchema }
{ name: 'Videos', schema: VideoSchema }
]
),
Once we are done with Module we can easily get and update data in collections using services and Controllers
- create YouTubeController
- create YouTubeService
Main Module
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { DatabaseModule } from '../../database/database.module';
import {VideoController} from '../controllers/videoController';
import {YouTubeController} from '../controllers/youtubeController';
import { YouTubeSchema} from './entity/mongoose.entity';
import { YouTubeService } from './services/crud.service';
@Module({
imports: [
DatabaseModule.forRoot(),
MongooseModule.forFeature([{ name: 'youtubes', schema: YouTubeSchema }]),
],
providers: [YouTubeService],
exports : [YouTubeService],
controllers: [YouTubeController, VideoController],
})
export class EntityModule {}
Now we can acess Mongoose Model in our services to fetch data by Injecting Model in services
Model injection
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { YouTube } from '../entity/mongoose.entity';
@Injectable()
export class YouTubeService {
constructor(@InjectModel('youtubes') private youtubeModel: Model<YouTube>) {}
public async findAll(): Promise<YouTube []> {
return await this.youtubeModel.find({}).exec();
}
public async getVideoById(id: string): Promise<YouTube [] | null> {
return await this.youtubeModel.find({id}).exec();
}
public async findVideoByName(name: string): Promise<YouTube []> {
return await this.youtubeModel.find({ title : { $regex: name, $options: 'i' }}).exec();
}
}
We can create Controller to make api calls and get data
@Controller('youtube')
export class YouTubeController {
constructor(public readonly service: YouTubeService) { }
@Get()
public async getHello(@Res() res: Response) {
const data = await this.service.findAll();
res.status(HttpStatus.OK).json({
data,
code: 200,
message: 'successfully fetched data',
success: true,
});
}
@Get(':name')
public async getYoutubeByName(@Param() params: YouTubeParams, @Res() res: Response) {
const data = await this.service.findVideoByName(params.name);
res.status(HttpStatus.OK).json({
data,
code: 200,
message: 'successfully fetched data',
success: true,
});
}
}
You can look more about Mongo DB here https://docs.nestjs.com/techniques/mongodb
In this simple application if we want to add redis service just to cahce some data then we can use redis-nestjs Module which is good enough for our needs or we can create nestjs microservices for redis client.
Redis Service for Api development
npm install nestjs-redis --save
Getting Started
Let's register the RedisModule in app.module.ts
import { Module } from '@nestjs/common'
import { RedisModule} from 'nestjs-redis'
@Module({
imports: [
RedisModule.register(options)
],
})
export class AppModule {}
With Async
import { Module } from '@nestjs/common';
import { RedisModule} from 'nestjs-redis'
@Module({
imports: [
RedisModule.forRootAsync({
useFactory: (configService: ConfigService) => configService.get('redis'), // or use async method
//useFactory: async (configService: ConfigService) => configService.get('redis'),
inject:[ConfigService]
}),
],
})
export class AppModule {}
And the config file look like this With single client
export default {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
db: parseInt(process.env.REDIS_DB),
password: process.env.REDIS_PASSWORD,
keyPrefix: process.env.REDIS_PRIFIX,
}
Or
export default {
url: 'redis://:authpassword@127.0.0.1:6380/4',
}
We have created a redis Module and servic so we can use redis caching for our APIs
@Module({})
export class CacheModule {
public static getRedisOption(config: ConfigService): RedisModuleOptions {
const redis = config.get().redis;
if (!redis) {
throw new CommonConfigError('redis config is missing');
}
return redis;
}
public static forRoot(): DynamicModule {
return {
module: CacheModule,
imports: [
RedisModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => CacheModule.getRedisOption(configService),
inject: [ConfigService],
}),
],
controllers: [],
providers: [CacheService],
exports: [CacheService],
};
}
}
We can create redis service to get and set keys
@Injectable()
export class CacheService {
constructor(
private readonly redisService: RedisService,
) { }
public async root(): Promise<boolean> {
const client = await this.redisService.getClient();
return client !== null;
}
}
You can explore more how nestjs use publish subscribe using @nestjs/microservices
The Redis transporter implements the publish/subscribe messaging paradigm and leverages the Pub/Sub feature of Redis. Published messages are categorized in channels, without knowing what subscribers (if any) will eventually receive the message. Each microservice can subscribe to any number of channels. In addition, more than one channel can be subscribed to at a time. Messages exchanged through channels are fire-and-forget, which means that if a message is published and there are no subscribers interested in it, the message is removed and cannot be recovered. Thus, you don't have a guarantee that either messages or events will be handled by at least one service. A single message can be subscribed to (and received) by multiple subscribers.
Comments