Создаем приложение на Node.JS, Express и Typescript с Jest, Swagger, log4js и Routing-controllers

Это пошаговая инструкция создания приложение на Node.JS, с использованием typescript и express. Новое приложение создается не часто, отсюда забываются шаги по его созданию. И я решил написать некую шпаргалку, в помощь самому себе и другим разработчикам. Помимо шагов, я так же снял небольшие видео ролики для наглядности. Существуют уже готовые фреймворки для Node.JS, которые уже содержат в себе все необходимые пакеты и можно работать с ними, но это уже другой путь. Идея была в том, чтобы не зависить целиком от какого-то фреймворка и в случае необходимости менять одни пакеты на другие.
Итак по шагам:
  1. Простое Web приложение youtu.be/7MIIcFDeSg4
    Ставим в определенном порядке пакеты и Node.JS, а так же прописываем настройки.
    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. Отладка и инициализация в Node.js youtu.be/hfST0e1ITGw
    Настраиваем режим отладки и создаем .env файл для установки входных значений.
    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. Добавление log4js и eslint к приложению на Node.JS 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 для Node.js youtu.be/_7z5Zubsdps
    Используем routing-controllers для более удобной работы.
    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. Node.JS добавляем post запрос, валидация входных данных, глобальный обработчик ошибок 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 документация в Node.JS приложении 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. Добавляем Unit тесты на Jest в приложение на Node.JS 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. Использование config для Node.JS, а так же другие полезные пакеты. youtu.be/8ZCHUN-JTck
    Пакет config позволяет устанавливать значения констант при инициализации в зависимости от значения NODE_ENV.
    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 - коммиты в гит
    semantic-release - формат коммитов и контроль версий
    
    pretty-quick - запускает prettier на измененных файлах
    prettier - формат кода
    eslint-config-prettier - разрешает конфликты между eslint и prettier
    eslint-plugin-prettier - запускает prettier как правила eslint
    
    mock-socket - мок для вебсокета
    jest-websocket-mock - тестирование вебсокета
    jest-sonar-reporter - конвертр из формата jest в формат sonar
    jest-mock-extended - мок объектов и интерфейсов
    
    ws - вебсокет
    
    typescript-string-operations - String.format
    lodash - библиотека дополнительных функций для js
    http-status-codes - константы для HTTP статусов
    moment - библиотека работы со временем в js
    
    ncp - копирование файлов
    js-yaml - загрузка yaml файлов
    
    mongodb - функции для работы с Mongo
    migrate-mongo - миграция для Mongo
    
    log-timestamp - запись даты в лог
    
    axios - HTTP клиент
    
    applicationinsights - интеграция с Azure Application Insights
    

Let’s block ads! (Why?)

Read More

Recent Posts

VK купила 40% билетной платформы Intickets.ru

VK объявляет о приобретении 40% компании Intickets.ru (Интикетс). Это облачный сервис для контроля и управления продажей билетов на мероприятия. Сумма…

4 часа ago

OpenAI готовится запустить поисковую систему на базе ChatGPT

OpenAI готовится запустить собственную поисковую систему на базе ChatGPT. Информацию об этом публикуют западные издания. Ожидается, что новый поисковик может…

13 часов ago

Роскомнадзор рекомендовал хостинг-провайдерам ограничить сбор данных с сайтов для иностранных ботов

Центр управления связью общего пользования (ЦМУ ССОП) Роскомнадзора рекомендовал компаниям из реестра провайдеров ограничить доступ поисковых ботов к информации на российских сайтах.…

1 день ago

Apple возобновила переговоры с OpenAI и Google для интеграции ИИ в iPhone

Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…

6 дней ago

Российская «дочка» Google подготовила 23 иска к крупнейшим игрокам рекламного рынка

Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…

7 дней ago

Google завершил обновление основного алгоритма March 2024 Core Update

Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…

7 дней ago