Lesson 32-NestJS Testing, Deployment, Logging, and Monitoring

Testing with Jest or Mocha

Testing with Jest

npm install --save-dev jest @types/jest ts-jest

Configuring Jest

Create a jest.config.js file in the project root to configure Jest.

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  // Other configuration options...
};

Writing Test Files

Test files are typically placed in a tests directory and end with .spec.ts. For example, to test app.controller.ts, create a app.controller.spec.ts file.

// tests/app.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
import { AppController } from '../src/app.controller';

describe('AppController (e2e)', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });
});

Running Tests

Add a test script to package.json:

{
  "scripts": {
    "test": "jest"
  }
}

Then run the tests:

npm run test

Testing with Mocha

npm install --save-dev mocha @types/mocha ts-node

Configuring Mocha

Create a mocha.opts file in the project root to configure Mocha.

// mocha.opts
--require ts-node/register

Writing Test Files

Test files are typically placed in a tests directory and end with .test.ts. For example, to test app.controller.ts, create a app.controller.test.ts file.

// tests/app.controller.test.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
import { AppController } from '../src/app.controller';

describe('AppController', () => {
  let app: INestApplication;

  before(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  after(async () => {
    await app.close();
  });

  it('should respond with "Hello World!"', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });
});

Running Tests

Add a test script to package.json:

{
  "scripts": {
    "test": "mocha -r ts-node/register tests/**/*.test.ts"
  }
}

Then run the tests:

npm run test

Notes

  • Ensure test files follow Jest or Mocha naming conventions.
  • Use beforeEach and afterEach to set up and clean up the environment for each test case.
  • Use supertest to simulate HTTP requests.
  • To mock services or modules, use the overrideProvider method of TestingModule.
  • Consider using jest.mock or sinon to mock external dependencies.

Deploying to Cloud Platforms (e.g., AWS, Google Cloud, Heroku)

Deploying to AWS

Using Elastic Beanstalk

Create an Elastic Beanstalk Application:

eb init -p node.js

Create an Environment:

eb create my-env

Deploy the Application:

eb deploy

Using EC2 + Load Balancer

Create an EC2 Instance:

  • Log in to the AWS Management Console and select the EC2 service.
  • Create a new instance, choosing an appropriate AMI and instance type.

Configure Security Group:

  • Set security group rules to allow HTTP and HTTPS traffic.

Install Node.js:

  • SSH into the EC2 instance.
  • Install Node.js and npm.

Upload Code:

  • Use SCP or SFTP to upload the code to the EC2 instance.

Start the Application:

  • Run the application on the EC2 instance.

Configure Load Balancer:

  • Create a load balancer and add the EC2 instance to the target group.

Deploying to Google Cloud

Using Google Cloud Run

Create a Cloud Run Service:

  • Log in to the Google Cloud Console and select the Cloud Run service.
  • Create a new service and specify the Docker image.

Build the Docker Image:

  • Create a Dockerfile.
  • Use gcloud builds submit to build the image.

Deploy the Service:

  • Use gcloud run deploy to deploy the service.

Using Google Kubernetes Engine (GKE)

Create a Kubernetes Cluster:

  • Log in to the Google Cloud Console and select the Kubernetes Engine service.
  • Create a new cluster.

Create Deployment and Service:

  • Write Kubernetes YAML files to define the Deployment and Service.

Deploy the Application:

  • Use kubectl apply -f <filename>.yaml to deploy the application.

Deploying to Heroku

Creating a Heroku Application

Install Heroku CLI:

npm install -g heroku

Log in to Heroku:

heroku login

Create an Application:

heroku create my-nest-app

Configuring the Application

Set the Build Environment:

heroku buildpacks:set heroku/nodejs

Push Code:

git push heroku master

Configuring Environment Variables

Set Environment Variables:

heroku config:set NODE_ENV=production

Scaling the Application

Increase Dyno Count:

heroku ps:scale web=1

Example
Suppose you have a NestJS project named my-nest-app. Here are the steps to deploy it to Heroku:

Initialize a Git Repository:

git init
git add .
git commit -m "Initial commit"

Link the Heroku Application:

heroku create my-nest-app

Set the Build Environment:

heroku buildpacks:set heroku/nodejs

Push Code:

git push heroku master

Set Environment Variables:

heroku config:set NODE_ENV=production

Increase Dyno Count:

heroku ps:scale web=1

Containerizing with Docker

Creating a Dockerfile

Create a Dockerfile in the project root to define the steps for building the image.

# Use the official Node.js image as the base
FROM node:16-alpine

# Set the working directory
WORKDIR /usr/src/app

# Copy package.json and package-lock.json to the container
COPY package*.json ./

# Install dependencies
RUN npm ci

# Copy the project source code to the container
COPY . .

# Install Winston or Morgan
RUN npm install winston morgan --save

# Set environment variables
ENV NODE_ENV=production

# Expose the port
EXPOSE 3000

# Run the application
CMD ["npm", "run", "start"]

Building the Docker Image

Build the Docker image with:

docker build -t my-nest-app .

Running the Docker Container

Run the Docker container with:

docker run -p 3000:3000 --name my-nest-container my-nest-app

Logging with Winston or Morgan

Logging with Winston

Installing Winston

Install Winston in your project:

npm install winston --save

Configuring Winston

Create a logging configuration file, e.g., logger.js:

// logger.js
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.splat(),
    winston.format.printf(info => `${info.level}: ${[info.timestamp]}: ${info.message}`)
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ],
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

module.exports = logger;

Using Winston

Import and use Winston in your application:

// app.module.ts
import { Logger } from './logger';

const logger = new Logger();

logger.info('Application started.');

Logging with Morgan

Installing Morgan

Install Morgan in your project:

npm install morgan --save

Configuring Morgan

Configure Morgan in your main application file:

// main.ts
import express from 'express';
import morgan from 'morgan';

const app = express();

// Use Morgan to log requests
app.use(morgan('combined'));

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

app.listen(3000, () => {
  console.log('Listening on port 3000');
});

Example
Suppose you have a NestJS project named my-nest-app. Here are the steps to containerize the application with Docker and use Winston for logging:

Create a Dockerfile:

# Dockerfile
FROM node:16-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm install winston morgan --save

ENV NODE_ENV=production

EXPOSE 3000

CMD ["npm", "run", "start"]

Build the Docker Image:

docker build -t my-nest-app .

Run the Docker Container:

docker run -p 3000:3000 --name my-nest-container my-nest-app

Configure Winston:

// logger.js
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.splat(),
    winston.format.printf(info => `${info.level}: ${[info.timestamp]}: ${info.message}`)
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ],
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

module.exports = logger;

Use Winston:

// app.module.ts
import { Logger } from './logger';

const logger = new Logger();

logger.info('Application started.');

Process Management with PM2

PM2 is a powerful process management tool for managing the lifecycle of Node.js applications, including starting, restarting, stopping, and monitoring application status. Below are the steps to manage a NestJS application with PM2.

Installing PM2

Install PM2 globally using npm:

npm install pm2 -g

Starting the Application

Start the NestJS application with PM2:

pm2 start npm --name "my-nest-app" -- start

Here, --name "my-nest-app" specifies the application name, and -- start indicates that the npm start command should be used to start the application.

Viewing Application Status

Check the status of the application:

pm2 list

This lists all applications managed by PM2 and their statuses.

Restarting the Application

Restart the application:

pm2 restart "my-nest-app"

Stopping the Application

Stop the application:

pm2 stop "my-nest-app"

Deleting the Application

Delete the application:

pm2 delete "my-nest-app"

Log Management

PM2 automatically manages log files for each application. View the application logs with:

pm2 logs "my-nest-app"

Configuring PM2

To better manage the application, create a configuration file, e.g., ecosystem.config.js:

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'my-nest-app',
    script: 'npm',
    args: 'start',
    instances: 1,
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env: {
      NODE_ENV: 'development'
    },
    env_production: {
      NODE_ENV: 'production'
    }
  }]
};

Starting the Application with a Configuration File

Start the application using the configuration file:

pm2 start ecosystem.config.js --env production

Here, --env production specifies the use of the env_production environment variables from the configuration file.

Example

Suppose you have a NestJS project named my-nest-app. Here are the steps to manage it with PM2:

Install PM2:

npm install pm2 -g

Start the Application:

pm2 start npm --name "my-nest-app" -- start

View Application Status:

pm2 list

Restart the Application:

pm2 restart "my-nest-app"

Stop the Application:

pm2 stop "my-nest-app"

Delete the Application:

pm2 delete "my-nest-app"

Configure PM2:

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'my-nest-app',
    script: 'npm',
    args: 'start',
    instances: 1,
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env: {
      NODE_ENV: 'development'
    },
    env_production: {
      NODE_ENV: 'production'
    }
  }]
};

Start the Application with the Configuration File:

pm2 start ecosystem.config.js --env production
Share your love