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:
- The introduction of
koa2-swagger-ui
.swagger-jsdoc
- To establish
swagger.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
- configuration
/doc
routing
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
- 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…