preface

Continue my self-built blog journey, Node build blog background, using KOA + mysql to achieve. A back-end API project that is essential for response handling is associated with exception handling. How do I catch exceptions and return the expected response? 🤔 I know not much, can only according to my current level of encapsulation.

Project source: github.com/Ray-daydayu…

Realize the principle of

Koa async and await are very nice to use. I’m a little confused about async exception handling, especially in MDN. On exception handling, there’s a great piece by a big guy on the Nuggets 👉 portal 👈.

He makes several points in his article:

  1. usetry catchCatch exceptions.
  2. If errors are not caught by default, they will bubble up to the top layer and finally throw exceptions. In business scenarios, a mechanism should be established to catch errors at the top layer for unified processing.

Although I do not understand so much advanced knowledge inside, and the big guy’s writing method, but according to this, I have my own processing ideas.

Koa’s middleware mechanism (portal 👈 is well written here) allows us to handle errors at the top level. Throw ([status], [MSG], [properties]) for response handling and exception handling.

Code implementation

I just define a piece of middleware at the top, and my processing middleware looks like this 👇

const { ErrorModel } = require('.. /models/http-error')
// Format error response
const format = (err, ctx) = > {
  ctx.response.status = err.statusCode
  ctx.response.body = {
    code: err.code,
    msg: err.message || err.msg,
    request: ctx.method + '> >' + ctx.url
  }
}

module.exports = async (ctx, next) => {
  try {
    await next()
  } catch (err) {
  // Determine if there is a known error
    if (err.flag === 'ErrorModel') {
      format(err, ctx)
    } else {
    // Returns a unified message for unknown errors
      console.log(err)
      format(new ErrorModel(), ctx)
    }
  }
}
Copy the code

The logic is simple: use ctx.throw to throw an error, catch it at the top level, determine the type of error, and return the response in the format.

So the key is how to define ErrorModel, see 👇

class ErrorModel {
	constructor(code = 500, MSG = "unknown server error ", statusCode = 500) {this.code = code // Data carries an internal exception status code
		this.msg = msg / / message
		this.statusCode = statusCode // The outer status code
	}
	throwErr(ctx) {
	// Throw an error
		ctx.throw(this.statusCode, this.msg, {
			code: this.code,
			flag: "ErrorModel",}}}// the 400 parameter is incorrect
class ParameterError extends ErrorModel {
	constructor(code, MSG = "request error ") {super(code, msg, 400)}}/ / 401 error
class AuthError extends ErrorModel {
	constructor(code, MSG = "token authentication failed ") {super(code, msg, 401)}}/ / 404
class NotFoundError extends ErrorModel {
	constructor(code, MSG = "API not found ") {super(code, msg, 404)}}/ / 500
class InternalServerError extends ErrorModel {
	constructor(code, MSG = "server internal error ") {super(code, msg, 500)}}module.exports = {
	ErrorModel,
	ParameterError,
	AuthError,
	NotFoundError,
	InternalServerError,
}
Copy the code

Once defined, we simply create the corresponding instance where the error occurred and call the throwErr function.

200 response processing

If no exception occurs, the data should be wrapped and the response returned, and I have defined a class to implement a uniform format for the response 👇

class SuccessModel {
  constructor(code, msg, data) {
    this.code = code || 200
    this.msg = msg || 'Operation successful'
    if (data) {
      this.data = data
    }
  }
  success(ctx) {
    // All responses are json, which koA handles directly
    ctx.body = this}}module.exports = SuccessModel
Copy the code

After a successful response, create the instance and then call the SUCCESS function.

Further encapsulation of common status codes

In a project, the status code is defined by convention, and when we return the response message, it is very troublesome to find the canonical write status code and message, so we usually wrap it

const SuccessModel = require(".. /models/http-success")
const {
	ParameterError,
	AuthError,
	NotFoundError,
	InternalServerError,
} = require(".. /models/http-error")
// 200 The request succeeded
const SUCCESS = async (ctx, data, msg) =>
	new SuccessModel(200, msg, data).success(ctx)
// Permissions are restricted
const USER_NO_PERMISSION = async (ctx, msg = "No access") = >new SuccessModel(2100, msg).success(ctx)
// User error
const USER_NOT_LOGIN = async (ctx) =>
	new SuccessModel(2001."User not logged in").success(ctx)
const USER_ACCOUNT_EXPIRED = async (ctx) =>
	new SuccessModel(2002."Account has expired").success(ctx)
const USER_ACCOUNT_DISABLE = async (ctx) =>
	new SuccessModel(2003."Account is not available").success(ctx)
const USER_ACCOUNT_NOT_EXIST = async (ctx) =>
	new SuccessModel(2004."Account does not exist").success(ctx)
const USER_ACCOUNT_ALREADY_EXIST = async (ctx, msg = "Account already exists") = >new SuccessModel(2005, msg).success(ctx)
const USER_ACCOUNT_USE_BY_OTHERS = async (ctx) =>
	new SuccessModel(2006."Account offline").success(ctx)
const USER_PWD_ERROR = async (ctx) =>
	new SuccessModel(2007."Password error").success(ctx)

/ / 400
const PARAM_NOT_VALID = async (ctx, msg = "Request parameters invalid") = >new ParameterError(1001, msg).throwErr(ctx)
const PARAM_IS_BLANK = async (ctx, msg = "Request parameters are empty") = >new ParameterError(1002, msg).throwErr(ctx)
const PARAM_TYPE_ERROR = async (ctx, msg = "Request parameter type error") = >new ParameterError(1003, msg).throwErr(ctx)
const PARAM_NOT_COMPLETE = async (ctx, msg = "Request parameters missing") = >new ParameterError(1004, msg).throwErr(ctx)
/ / 401
const TOKEN_IS_BLANK = async (ctx) =>
	new AuthError(4004."The token is empty." ").throwErr(ctx)
const TOKEN_EXPIRED = async (ctx) =>
	new AuthError(4001."The token expired").throwErr(ctx)
const TOKEN_INVALID = async (ctx) =>
	new AuthError(4002."The token is invalid").throwErr(ctx)
const AUTHENTICATION_FAIL = async (ctx, msg = "Authentication failed") = >new AuthError(4003, msg).throwErr(ctx)
/ / 404
const NotFound = async (ctx) =>
	new NotFoundError(
		404."API not found, please check request path and request method for error"
	).throwErr(ctx)

/ / 500
const FAIL = async (ctx, msg) => new InternalServerError(500, msg).throwErr(ctx)
const FILE_UPLOAD_FAIL = async (ctx) =>
	new InternalServerError(5001."File upload failed").throwErr(ctx)
Copy the code

In the above code, the commonly used status codes are encapsulated, and each status code is semantically defined. When necessary, the corresponding functions are called and CTX and some information are passed in.

Business use

Directly on the code (lazy 😁)

  1. An exception is thrown
/ / 404
app.use(async (ctx, next) => {
    / / 404
	await NotFound(ctx)
})
Copy the code
  1. A successful response
async function updateService(ctx, params) {
  const userInfo = ctx.state.user
  if(userInfo.permission ! = =1) {
    / / call
    await USER_NO_PERMISSION(ctx)
    return
  }
  if (params.hasOwnProperty('title')) {
    const selectRes = await articles.select(params.title)
    if (selectRes[0]) {
    / / call
      await USER_ACCOUNT_ALREADY_EXIST(ctx, 'Article title already exists')
      return}}const articlesResult = await articles.update(params)
  / / call
  await SUCCESS(ctx, {
    line: articlesResult.affectedRows || articlesResult[0].affectedRows
  })
}
Copy the code

conclusion

Using KOA middleware mechanism to achieve global response handling and exception handling, but there are still many shortcomings

  • Their code level is not enough, some of the code is not good enough, slowly progress, just wrote a long time ago
  • rightkoaThere should be a better way to handle it, and some features are not used
  • For errors and better comments, please leave a comment, thanks to 🙏