Pull to refresh

Creating Node.JS web server application with Express, Typescript, Jest, Swagger, log4js and routing-controllers

Reading time7 min
Views4K
Original author: It is my article. alex_29 (Alexander Korolev )
This is a step by step instruction for creating Node.JS web server application with typescript and express. Creating of new web application on Node.JS is is not making often, so the steps how to do it can be forgotten. So this article is a kind of reminder for me and other developers. Also I added in article links to video clips for more clarification. Here I show how to create application on Node.JS without using any frameworks for it. Just Node.JS and packages.

So, step by step:

  1. Simple Web application youtu.be/7MIIcFDeSg4

    Install in right order Node.JS, packages and configure them.

    1) node.js download, 
    2) Create directory for your project, 
    3) npm init, 
    4) in package.json
     "main": "dist/index.js",
      "scripts": {
        "build": "tsc",
        "start": "node ."
      }
    5) npm install --save-dev typescript, 
    6) in tsconfig.json 
      {
      "compilerOptions": {
        "esModuleInterop": true,
        "outDir": "dist",
        "baseUrl": "."
      }
    }
    8) npm install express, 
    9) npm install @types/express, 
    10) create src folder, 
    11) create src/index.ts with code below:
    import express from 'express'
    
    const app = express();
    const port = 5000;
    app.get('/', (request, response) => {
      response.send('Hello world!');
    });
    app.listen(port, () => console.log(`Running on port ${port}`));
    13) npm run build, 
    14) npm run start, 
    15) localhost:5000
    

  2. Debug and initialization in Node.js youtu.be/hfST0e1ITGw

    Set up debug mode and create an .env file to set input values.

    1) in tsconfig.json add: "sourceMap": true
    2) int package.json add: "prestart": "npm run build",
    3) In IntelliJ IDEA in Run/Debug Configurations choose: "npm" and add script
    4) npm i ts-node-dev --save-dev
    5) int package.json add: "server:watch": "ts-node-dev --respawn --transpile-only src/index.ts"
    6) add IntelliJ IDEA npm for "server:watch" script
    7) npm install dotenv
    8) in index.ts add: dotenv.config();
    9) create .env file in root dir of your project and add text below in .env file:
    PORT = 5000
    const port = process.env.PORT;
    

  3. Adding log4js and eslint to Node.JS application youtu.be/qcSpd6N7ZJ8

    1) npm install log4js
    2) in index.ts file:
        import log4js from 'log4js';
        ...
        const logger = log4js.getLogger();
        logger.level = process.env.LOG_LEVEL;
        ...
    4) in .env file: LOG_LEVEL=error
    5) in index.ts file:
        ...
        logger.info('log4js log info');
        logger.debug('log4js log debug');
        logger.error('log4js log error');
        ...
    6) npm install eslint --save-dev
    7) eslint --init
    8) "prebuild": "npm run lint"
    9) "lint:fix": "eslint --cache --ext .ts . --fix",
    10) "lint": "eslint --cache --ext .ts .",
        !!! --cache (only changed), .
    11) IntelliJ IDEA -- file -- setting -- eslint -- automatic
    12) "rules": {
            "semi": ["error", "always"]
        }
    

  4. Routing controllers for Node.js youtu.be/_7z5Zubsdps

    Use routing-controllers for more convenient work.

    1) npm install routing-controllers
    2) npm install reflect-metadata
    3) npm install express body-parser multer
    4) npm install class-transformer class-validator
    5) tsconfig.json
       "compilerOptions": {
          ...
          "emitDecoratorMetadata": true,
          "experimentalDecorators": true
          ...
       }
    6) in index.ts
    // const app = express();
    
    // logger.info('log4js log info');
    // logger.debug('log4js log debug');
    // logger.error('log4js log error');
    
    // app.get('/', (request, response) => {
    //   response.send('Hello world2!');
    // });
    7) in index.ts
       import { createExpressServer } from 'routing-controllers';
       import { UserController } from './UserController';
    
       const app = createExpressServer({
         controllers: [UserController], // we specify controllers we want to use
    });
    
    8) controller/user-controller.ts
       import { Controller, Get, Param } from 'routing-controllers';
       import 'reflect-metadata';
    
       @Controller()
       export class UserController {
         @Get('/users/:id')
         getOne (@Param('id') id: number) {
           return 'This action returns user #' + id;
         }
       }
    9) http://localhost:3001/users/1
    

  5. Node.JS middleware, interceptor, http context youtu.be/iWUMUa7gTTQ

    1) middleware -- middleware.ts
    2) middleware.ts
    export function loggingBefore (request: any, response: any, next?: (err?: any) => any): any {
      console.log('do something Before...');
      next();
    }
    
    export function loggingAfter (request: any, response: any, next?: (err?: any) => any): any {
      console.log('do something After...');
      next();
    }
    3) user-controller.ts in class
    @UseBefore(loggingBefore)
    @UseAfter(loggingAfter)
    console.log('do something in GET function...');
    4) user-controller.ts in function
     @UseBefore(loggingBefore)
     @UseAfter(loggingAfter)
    5) user-controller.ts in function
     @UseInterceptor(function (action: Action, content: any) {
        console.log('change response...');
        return content;
      })
    6) npm install express-http-context
    7) index.ts
     
     const app: Express = express();
            app.use(bodyParser.json());
     app.use(httpContext.middleware);
     useExpressServer(app, {
       controllers: [UserController]
     });
    
     app.use((req, res, next) => {
       httpContext.ns.bindEmitter(req);
       httpContext.ns.bindEmitter(res);
     });
    
    8) middleware.ts loggingBefore
        import httpContext from 'express-http-context';
        
        console.log('set traceId = 123');
        httpContext.set('traceId', 123);
    9) middleware.ts loggingAfter
        console.log(`tracedId = ${httpContext.get('traceId')}`);
    

  6. Adding POST request to Node.JS web application, validation of input data, global error handler youtu.be/onBVkkLEuw4

    1) in user-controller.ts add:
      ...
      @Post('/users/:id')
      @OnUndefined(204)
      postOne (@Param('id') id: number, @Body() info: any) {
        console.log(JSON.stringify(info));
      }
      ...
    2) in postman
     http://localhost:3001/users/1
     {
     "country":"Russia",
     "city":"SPb"
     }
    3) model -- info.ts
    4) 
    import { IsDefined } from 'class-validator';
    
    export class Info {
      @IsDefined()
      country: string;
      @IsDefined()
      city: string;
    }
    8) postOne (@Param('id') id: number, @Body() info: Info) {
    9) middleware -- global-error-handler.ts
    10) 
    import { ExpressErrorMiddlewareInterface, Middleware } from 'routing-controllers';
    
    @Middleware({ type: 'after' })
    export class GlobalErrorHandler implements ExpressErrorMiddlewareInterface {
      error (error: any, request: any, response: any, next: () => any) {
        response.send({ ERROR: error });
        next();
      }
    }
    11) 
    useExpressServer(app, {
      controllers: [UserController], // we specify controllers we want to use
      middlewares: [GlobalErrorHandler],
      defaultErrorHandler: false
    });
    

  7. Swagger documentation in Node.JS web application youtu.be/-uoIasCbsq8

    1) npm install swagger-ui-express
    2) tsconfig.json -- "resolveJsonModule": true
    3) src -- swagger -- openapi.json
    4) index.ts
    import swaggerUi from 'swagger-ui-express';
    import * as swaggerDocument from '../src/swagger/openapi.json';
    ...
    app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
    5) change port to 3000
    in .env file set PORT=3000
    6) npm install cors
    7) npm install @types/cors
    8) in index.ts 
    import cors from 'cors';
    ...
    app.use(cors() as RequestHandler);
    ...
    9) Swagger Editor (example for test project)
    
    openapi
    
    openapi: 3.0.1
    info:
      title: test API
      version: v1
    servers:
      - url: 'http://localhost:3000'
    tags:
      - name: API functions
        description: >-
          API functions of our application
          
    paths:
      /users/{id}:
        get:
         summary: returns simple answer from get
         tags: 
          - API functions
         parameters:
           - name: id
             in: path
             required: true
             description: simple parameter
             schema:
               type : string
               example: '1'
         description: parameter id just for test
         responses:
          '200': #status code
           description: OK
           content:
                document:
                  schema:
                    type: string
                    example: some text
        post:
         summary: returns simple answer from post
         tags: 
          - API functions
         requestBody:
            required: true
            content:
              application/json:
                schema:
                   $ref: '#/components/schemas/Info'          
                example:
                  country: Russia
                  city: Spb
         parameters:
           - name: id
             in: path
             required: true
             description: simple parameter
             schema:
               type : string
               example: '1'
         description: parameter id just for test
         responses:
          '204': #status code
           description: OK
    components:
      schemas:
        Info:
          type: object
          properties:
            country:
              type: string
            city:  
              type: string
    

  8. Adding Unit tests on Jest in Node.JS web application youtu.be/rCIRpTMVEMM

    0) in global-error-handler.ts
    response.status(error.statusCode || error.httpCode).json(error);
        next();
    
    1) npm install --save-dev jest
    2) npm i -D ts-jest @types/jest
    3) npm i -D ts-jest
    4) package.json -- 
    {
    ...
    scripts {
    ...
    "test:unit": "jest --config=jest.config.js",
    },
    ...
    }
    5) create jest.config.js with code below:
    
    process.env.NODE_ENV = 'UNITTEST';
    module.exports = {
        clearMocks: true,
        collectCoverage: true,
        collectCoverageFrom: [
            './src/**/*.ts'
        ],
        coverageDirectory: '<'rootDir>/test/coverage',
        testEnvironment: 'node',
        testMatch: ['**/*.test.ts'],
        preset: 'ts-jest'
    };
    
    6) .eslintignore
    *.js
    node_modules
    dist
    coverage
    }
    7) .eslintrc.json
    {
    ...
    "env": {
       "jest": true
    } 
    ...
    } 
    8) test -- controller -- user-controller.test.ts
    
    describe('UserController', () => {
      afterEach(() => {
        jest.restoreAllMocks();
      });
    
      it('postOne', () => {
        const userController = new UserController();
        const testBody = {
          city: 'SPb'
        };
        const res = userController.postOne(1, testBody as Info);
        expect(res).toBeUndefined();
      });
    }
    
    9) in IDEA
    add script - test:unit
    set in environment - NODE_ENV=UNITTEST
    10) Simple variant of jest.config.js for IDEA:
    process.env.NODE_ENV = 'UNITTEST';
    module.exports = {
      clearMocks: true,
      collectCoverage: false,
      testEnvironment: 'node',
      testMatch: ['**/*.test.ts'],
      preset: 'ts-jest'
    };
    11) npm i -D supertest @types/supertest
    12) in user-controller.test.ts
    ...
    let server;
    ...
    beforeAll(async () => {
        server = express();
        server.use(bodyParser.json());
        useExpressServer(server, {
          controllers: [UserController], // we specify controllers we want to use
          middlewares: [GlobalErrorHandler],
          defaultErrorHandler: false
        });
      });
    ...
    it('postOne with validations', done => {
        request(server)
          .post('/users/1')
          .send({
            country: 'Russia',
            city: 'SPb'
          } as Info)
          .expect(204)
          .end((err, res) => {
            if (err) throw new Error(JSON.stringify(res.body));
            done();
          });
      });
    

  9. Using Config package for Node.JS and other useful packages youtu.be/8ZCHUN-JTck

    Config package allows to set value for constants depending on NODE_ENV value.

    1) npm install config
    2) npm install @types/config
    3) config
    4) default.yaml PORT: 3000 
       DEV.yaml PORT: 3001 
       LOCAL.yaml PORT: 3002 
    5) index.ts
       // const port = process.env.PORT;
          const port = config.get('PORT');
    6) IDEA server:watch -- Environment
        NODE_ENV=DEV
        NODE_ENV=LOCAL
    
    -- packages:
    
    husky - commits in Git
    semantic-release - Commits formatting and version control
    
    pretty-quick - run prettier for changed files
    prettier - code formtatter
    eslint-config-prettier - resolve conflicts between eslint and prettier
    eslint-plugin-prettier - run prettier as eslint rules
    
    mock-socket - websocket mock
    jest-websocket-mock - websocket testing
    jest-sonar-reporter - convert reports from jest format to sonar format
    jest-mock-extended - mocks for objects and interfaces
    
    ws - websocket
    
    typescript-string-operations - String.format
    lodash - useful functions for js
    http-status-codes - constants for HTTP statuses
    moment - time library for js
    
    ncp - copy files
    js-yaml - working with yaml files
    
    mongodb - package with functions for Mongo
    migrate-mongo - migration for Mongo
    
    log-timestamp - write current time in log messages
    
    axios - HTTP client
    
    applicationinsights - integration with Azure Application Insights
    

Tags:
Hubs:
Total votes 1: ↑0 and ↓1-1
Comments1

Articles