Koa +mysql+vue+socket. IO full stack development web API

The goal is to build a Web QQ project using the following technology stack:

  1. The backend is a Web API service layer based on KOA2, which provides HTTP interface for CURD operations. Json Web token is used for login verification, and CORS is used for cross-domain solution.
  2. The database uses mysql;
  3. For real-time communication, socket. IO framework based on WebSocket protocol is used.
  4. The front end uses VUE + vuex.

This article explains how to build the server side by using KOA instead of other wrapped frameworks such as egg.js and Thinkjs. Because IN my opinion, KOA2 is convenient enough and has enough plugins to build a framework that best fits your business needs like building blocks. This not only eliminates a lot of unnecessary plug-ins, makes the entire framework more streamlined, but also can understand the whole framework, reducing the impact of a lot of unpredictable factors.

Of course, I think the most important thing is that I am lazy 😄, do not want to learn other framework specific API, unique configuration. Because there are too many frameworks and apis to master at the front end, I think the priority of learning non-Internet recognized technical standards should be a little lower. Because of these frameworks, every day or two hot, there are simply too many to learn, ah, and KOA is basically the bottom of these frameworks, obviously much more reliable.

Basic framework construction

These koA plug-ins will be used in most projects:

  • Koa-body parses HTTP data
  • Koa – compress gzip compression
  • Koa – the router routing
  • Koa-static Sets a static directory
  • Koa2 – cors cross-domain cors
  • Log4js is a legacy logging component
  • Jsonwebtoken JWT components

Basic directory structure

public # public directory
src    # Front-end directory
server # backend directory├ ─ ─ common# tools├ ─ ─ the config# config file├ ─ ─ the controller# controller├ ─ ─ daos# Database access layer├ ─ ─ logs# log directory├ ─ ─ middleware# Middleware directory├ ─ ─ the socket# socketio directory├ ─ ─ app. Js# import file└ ─ ─ the router. Js# routing
Copy the code

Entry file app.js

There are just a few middleware configurations that need to be noted. Here the socket. IO service is also loaded. For the basics of socket. IO, see my previous article on using socket. IO.

//app.js
/ /...
const path = require("path");
const baseDir = path.normalize(__dirname + "/..");

// gzip
app.use(
  compress({
    filter: function(content_type) {
      return /text|javascript/i.test(content_type);
    },
    threshold: 2048.flush: require("zlib").Z_SYNC_FLUSH
  })
);

// Parse the request
app.use(
  koaBody({
    jsonLimit: 1024 * 1024 * 5.formLimit: 1024 * 1024 * 5.textLimit: 1024 * 1024 * 5.multipart: true.// Parse FormData
    formidable: { uploadDir: path.join(baseDir, "public/upload")}// Upload the file directory}));// Set the static directory
app.use(static(path.join(baseDir, "public"), { index: false }));
app.use(favicon(path.join(baseDir, "public/favicon.ico")));

//cors
app.use(
  cors({
    origin: "http://localhost:" + config.clientPort,
    credentials: true.allowMethods: ["GET"."POST"."DELETE"].exposeHeaders: ["Authorization"].allowHeaders: ["Content-Type"."Authorization"."Accept"]}));/ / json - web - token middleware
app.use(
  jwt({
    secret: config.secret,
    exp: config.exp
  })
);

// Login authentication middleware, exclude indicates the page that is not authenticated, include indicates the page to be authenticated
app.use(
  verify({
    exclude: ["/login"."/register"."/search"]}));// Error handling middleware
app.use(errorHandler()); 

/ / routing
addRouters(router);
app.use(router.routes()).use(router.allowedMethods());

/ / 404
app.use(async (ctx, next) => {
  log.error(` 404${ctx.message} : ${ctx.href}`);
  ctx.status = 404;
  ctx.body = { code: 404.message: "404! not found !" };
});

// Handle middleware and system errors
app.on("error", (err, ctx) => {
  log.error(err); //log all errors
  ctx.status = 500;
  ctx.statusText = "Internal Server Error";
  if (ctx.app.env === "development") {
    //throw the error to frontEnd when in the develop mode
    ctx.res.end(err.stack); //finish the response
  } else {
    ctx.body = { code: - 1.message: "Server Error"}; }});if (!module.parent) {
  const { port, socketPort } = config;
  /** * koa app */
  app.listen(port);
  log.info(`=== app server running on port ${port}= = = `);
  console.log("app server running at: http://localhost:%d", port);

  /** * socket.io */
  addSocket(io);
  server.listen(socketPort);
}
Copy the code

Cross-domain CORS and JSON Web tokens

Here I explain the setting of THE KOA-CORS parameter. My project uses json Web token, and I need to add the Authorization field to the header. When the front-end obtains the header field and sends the HTTP request to the background, Then bring the Authorization.

  • If you want to access a header field or set a cookie, the asterisk (*) will not do.
  • Credentials: These are used to obtain cookies for the front end.
  • AllowMethods: methods that allow access;
  • ExposeHeaders: If you want to obtain the header field, you must specify it (for json Web token).
  • AllowHeaders: Fields added to the header;

As for the principle of JSON Web token, there is complete information on the web, which will not be introduced here.

app.use(
  cors({
    origin: "http://localhost:" + config.clientPort, // To access the header, specify the specific domain name
    credentials: true.// Expose the credentials so that the front end can get the cookie
    allowMethods: ["GET"."POST"."DELETE"].exposeHeaders: ["Authorization"].// Expose the header field
    allowHeaders: ["Content-Type"."Authorization"."Accept"] // Allow fields to be added to the header}));Copy the code

Middleware middleware

Koa’s middleware is the best tool for Web development. It makes it very easy to implement AOP aspect programming in strongly typed languages, and THE KOA2 middleware is simple enough to write koAJS.

The project is packaged with middleware in the following areas, simplifying much of the repetitive boilerplate code.

  • Json Web Token (JWT)
  • Verify login
  • Error Handling (errorHandler)

Take the simplest error-handling middleware as an example. Without error-handling middleware, we would need each controller method to do a try{… } catch {… }, other middleware written in a similar way, will not be introduced.

/** * Error handler middleware */
module.exports = (a)= > {
    return async (ctx, next) => {
        try {
            await next();// No errors go to the next middleware
        } catch (err) {
            log.error(err);
            let obj = {
                code: - 1.message: 'Server error'
            };
            if (ctx.app.env === 'development') {
                obj.err = err;
            }
            ctx.body = obj
        }
    };
};

// Controller code using error handler middleware, each method does not need to try catch, log error, processing logic is centralized in the middleware.
exports.getInfo = async function(ctx) {
    // try {
        const token = await ctx.verify();
        const [users, friends] = await Promise.all([
            userDao.getUser({ id: token.uid }),
            getFriends([token.uid])
        ]);

        const msgs = applys.map(formatTime);
        ctx.body = {
            code: 0.message: "Friends List".data: {
                user: users[0].friends: mergeReads(friends, reads),
                groups,
                msgs
            }
        };
    // } catch (err) {
    // log.error(err);
    // let obj = {
    // code: -1,
    // message: "Server error"
    / /};
    // if (ctx.app.env === "development") {
    // obj.err = err;
    / /}
    // ctx.body = obj;
    // }
};
Copy the code

The routing configuration

The route configuration uses only get and post methods, of course put and delete with names changed.

// router.js
const { uploadFile } = require('./controller/file')
const { login, register } = require('./controller/sign')
const { addGroup, delGroup, updateGroup } = require('./controller/group')
/ /...

module.exports = function (router) {
    router
        .post('/login', login)
        .post('/register', register)
        .post('/upload', uploadFile)
        .post('/addgroup', addGroup)
        .post('/delgroup', delGroup)
        .post('/updategroup', updateGroup)
  			/ /...
};
Copy the code

The controller

In the case of the updateInfo method, KOA2 already fully supports async await and is written not much differently than synchronous code.

exports.updateInfo = async function (ctx) {
    const form = ctx.request.body;
    const token = await ctx.verify();
    const ret = await userDao.update([form, token.uid]);
    if(! ret.affectedRows) {return ctx.body = {
            code: 2.message: 'Update failed'
        };
    }
    ctx.body = {
        code: 0.message: 'Update successful'
    };
}
Copy the code

subsequent

The next installment is to build a database access layer based on mysql.