background

Because of the recent learning of SSR related content, and need to do some internal tool system; Consider getting familiar with Koa2+TypeScript; A few times around, TypeScript+Koa combinations are rare; So there you have it, now the summary output

The project structure

Json and SRC /@types configuration, and many libraries need to introduce TypeScript type declarations.

For example, jS-MD5, @types/ js-MD5 should also be introduced

Specific optimization points

The middleware

logger.ts

The log4JS library is a good way to collect HTTP request methods, return status, request URL, IP address, request time, and so on, to print a defined log. Can be used instead of console.log(); When using this middleware, it must be placed in the first middleware to ensure that all requests and operations will be logged by logger before going to the next middleware.

import { Context, Next } from 'koa'
import { LogPath } from '.. /config/constant'

const fs = require('fs')
const path = require('path')
const log4js = require('log4js')

// Create a new directory to store logs
const logsDir = path.parse(LogPath).dir
if(! fs.existsSync(logsDir)) { fs.mkdirSync(logsDir) }/ / configuration log4. Js
log4js.configure({
  appenders: {
    console: { type: 'console' },
    dateFile: {
      type: 'dateFile'.filename: LogPath,
      pattern: '-yyyy-MM-dd',}},categories: {
    default: {
      appenders: ['console'.'dateFile'].level: 'error',}}})export const logger = log4js.getLogger('[Default]')

// Logger middleware
export const loggerMiddleware = async (ctx: Context, next: Next) => {
  // Request start time
  const start = +new Date(a)await next()
  // End time
  const ms = +new Date() - start
  // Print the request parameters
  const remoteAddress = ctx.headers['x-forwarded-for'] || ctx.ip || ctx.ips
  const logText = `${ctx.method} ${ctx.status} ${ ctx.url }Request parameters:The ${JSON.stringify(ctx.request.body)}Response parameters:The ${JSON.stringify(
    ctx.body
  )} - ${remoteAddress} - ${ms}ms`
  logger.info(logText)
}

Copy the code

cors.ts

In front and back interface requests, different domain names may cross domains due to browser restrictions. This example is to use koA in the setting of cross-domain; The koa2 – cors library

app.use(Cors(corsHandler))

import { Context } from 'koa'

export const corsHandler = {
  origin: function (ctx: Context) {
    return The '*'
  },
  exposeHeaders: ['Authorization'].maxAge: 5 * 24 * 60 * 60.// credentials: true,
  allowMethods: ['GET'.'POST'.'OPTIONS'.'DELETE'].allowHeaders: ['Content-Type'.'Authorization'.'Accept'.'X-Requested-With'],}Copy the code

When configuring cross-domain credentials, note the following: When withCredentials is set to true, access-control-allow-Origin cannot be set to *

const instance = axios.create({
  baseURL: baseURL,
  timeout: 30000.// withCredentials: true,
  headers: {
    'Content-Type': 'application/json; charset=UTF-8',}})Copy the code

In CORS, the ‘access-control-allow-origin’ in the header of the HTTP response is set to the wildcard ‘*’

response.ts

The new response.ts middleware is mainly used for unified processing of the response returned to the front end

import { logger } from './logger'
import { Context, Next } from 'koa'

export const responseHandler = async (ctx: Context, next: Next) => {
  if(ctx.result ! = =undefined) {
    ctx.type = 'json'
    ctx.body = {
      code: 200.msg: ctx.msg || 'success'.data: ctx.result,
    }
    await next()
  }
}

export const errorHandler = (ctx: Context, next: Next) = > {
  return next().catch((err) = > {
    if (err.code == null) {
      logger.error(err.stack)
    }
    if (err.status === 401) {
      ctx.status = 401
      ctx.body = 'Protected resource, use Authorization header to get access\n'
    } else {
      ctx.body = {
        code: err.code || -1.data: null.msg: err.message.trim() || 'failure',
      }
      ctx.status = 200
    }
    return Promise.resolve()
  })
}

Copy the code

jwt.ts

JSON Web Token (ABBREVIATED JWT) is the most popular cross-domain authentication solution;

This project mainly uses two plug-ins: KOA-JWT and JsonWebToken

import jsonwebtoken from 'jsonwebtoken'
const { jwtSecret } = require('.. /.. /config/index')

export interface UserParams {
  username: string name? : string avatar? : string email? : string gender? : number phone? : numberaccessToken: string
}
export default class JwtAuth {
  /** * Obtain user token *@static
   * @param {UserParams} userData
   * @param {*} [options]
   * @return {*}  {string}
   * @memberof JwtAuth* /
  public staticsignUserToken(userData: UserParams, options? : any): string {try {
      return jsonwebtoken.sign(userData, jwtSecret, options)
    } catch (error) {
      console.log(error)
    }
  }

  /** * Verify user token value *@static
   * @param {string} token
   * @return {*}  {Object}
   * @memberof JwtAuth* /
  public static verifyUserToken(token: string): any {
    try {
      const authorization = token && token.split(' ') [1]
      return jsonwebtoken.verify(authorization, jwtSecret)
    } catch (error) {
      console.log(error)
      throw { code: 401.message: 'no authorization'}}}}Copy the code

Swagger integration generates interface documentation

Write an interface, how can you do without the interface document; Here we use koA’s swagger integration to generate the interface document.

Specific steps:

  1. The introduction ofkoa2-swagger-ui.swagger-jsdoc
  2. To establishswagger.config
import path from 'path'
import swaggerJSDoc from 'swagger-jsdoc'
import AddressIp from 'ip'
import { PORT } from '.. /.. /config/constant'

const swaggerDefinition = {
  info: {
    // API informations (required)
    title: 'Account system'.// Title (required)
    version: '1.0.0'.// Version (required)
    description: 'Accounts and Permissions'.// Description (optional)
  },
  host: `http://${AddressIp.address()}:${PORT}`.// Host (optional)
  basePath: '/'.// Base path (optional)
}

const options = {
  swaggerDefinition,
  apis: [path.join(__dirname, '.. /.. /routes/*.ts')].// all api
}

const jsonSpc = swaggerJSDoc(options)
export default jsonSpc

Copy the code
  1. configuration/docrouting
import Router from 'koa-router'
import { Context } from 'koa'
import swaggerJSDoc from '.. /middlewares/swagger/swagger.conf'
const routerInit = new Router()

routerInit.get('/docs'.(ctx: Context) = > {
  ctx.body = swaggerJSDoc
})
export default routerInit
Copy the code
  1. application
// swagger
app.use(
  koaSwagger({
    routePrefix: '/swagger'.swaggerOptions: {
      url: '/docs',}}))Copy the code

For example:

/** * @swagger * /v1/menu/list/${appId}: * post: * description: get menu list * tags: [menu module] * produces: * application/json * parameters: * in: "body" * name: "body" * description: "query parameter" * schema: * $ref: "#/definitions/Menu" * responses: * 200: * description: * schema: * type: object * properties: * total: * type: number * rows: * type: array * items: * $ref: '#/definitions/MenuModel' * */Copy the code

Here’s the result:

The routing configuration

The current route configuration is manually added and registered

/ / routing
app.use(Role.routes()).use(Role.allowedMethods())
app.use(User.routes()).use(User.allowedMethods())
app.use(Menu.routes()).use(Menu.allowedMethods())
app.use(Auth.routes()).use(Auth.allowedMethods())
Copy the code

Searching for routing autoload solutions, no library suitable for TypeScript was found

Consider the library require-directory if you are using JS

Joi parameter verification

When we implement some functionality with NodeJS, we often need to validate the data entered by the user. Validation is a trouble thing, however, is likely to need to verify your data type, length, specific rules, etc., on the front end do form validation, our common practice is to use a regular, regular expressions may one pace reachs the designated position, but he will only give you true or false, if you want to know what conditions do not conform to the data, For your judgment, let’s share a more readable and easy to use implementation.

Joi is the data verification module of HaPIJS. It has a high encapsulation of commonly used verification functions. This article is about how to use Joi to verify data gracefully. I’m sure you’ll like him. Easy for everyone to understand, take login as an example, generally divided into two ways: A or B (input password or TWO-DIMENSIONAL code), then joI configuration can be verified as follows:

const Joi = require('joi')
const schema = Joi.object({
  name: Joi.string().empty(' '),
  pageSize: Joi.number().required(),
  pageNo: Joi.number().required(),
  appId: Joi.number().required(), }) schema.validateAsync({ ... request })Copy the code

Sequelize transactions resolve data inconsistency issues

When a business for a number of database operations, with thumb up function, for example, the first thing you have to record the increase in the table of the thumb up, then you have to add the thumb up several of the corresponding object 1, this is two operations must be done together, if you have a successful operation, another operating problems, it will lead to inconsistent data, it is a very serious security problem.

To do this (put multiple database operations into one transaction) :

await sequelize.transaction(async (t: any) => {
  await roleModel.update(
    {
      roleName: request.roleName,
      remark: request.remark || '',
    },
    {
      where: {
        id: request.id,
      },
      transaction: t,
    }
  )
  await roleMenuModel.destroy({
    where: {
      roleId: request.id,
    },
    force: true,
    transaction: t,
  })
})
Copy the code

Pm2 configuration

PM2 is a process management tool for Nodejs that can be used in production environments, and it has a built-in load balancing tool. Not only does it keep the service online without interruption, but it also offers a 0-second reload function, as well as a host of other process management and monitoring capabilities. And it’s very simple to use. The official document of PM2 has given detailed configuration instructions, which will not be briefly described here. The main topic is how to manage or deploy my KOA project with PM2. You can also combine it with package.json and run it with a custom command. We configure the script in package.json

  "scripts": {
    "dev": "cross-env NODE_ENV=development nodemon --exec ts-node src/app.ts"."build-ts": "tsc"."build:test": "rm -fr dist && npm run lint && npm run build-ts"."serve:test": "cross-env NODE_ENV=development pm2 startOrReload pm2-start.json --no-daemon",}Copy the code

pm2-start.json

{
  "apps": [{"name": "xl-account-server"."script": "./dist/app.js"."instances": "2"."exec_mode": "cluster"."watch": false."watch_delay": 4000."ignore_watch" : [
        "node_modules"."src"]."max_memory_restart": "1000M"."min_uptime": "5s"."max_restarts": 5."error_file": "./logs/pm2_xl_account_err.log"."out_file": "/dev/null"."log_date_format": "YYYY-MM-DD HH:mm Z"}}]Copy the code

Koa2 + TypeScript Github templates

Github.com/kkxiaojun/k…