Как стать автором
Обновить

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

Node.JS *
Это пошаговая инструкция создания приложение на 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
    

Теги:
Хабы:
Всего голосов 6: ↑4 и ↓2 +2
Просмотры 16K
Комментарии Комментарии 11