Web Server Framework Based on Koa and Mysql

Posted by jac38 on Fri, 24 May 2019 22:31:54 +0200

Recently, I was writing a custom interface for a project. For the first time, I used node.js to implement it. I searched books and materials for several days. I planned to use express + mongodb to implement it. Later, I found that Koa, good boy, mysql is the final choice of the database. Old fashion, I built a framework based on Koa + Mysql:

Koa

Koa is a small, expressive and robust Web framework built by Express's original team. By compiling web applications with koa and combining different generator s, the redundant nesting of callback functions can be avoided, and the efficiency of error handling can be greatly improved. Koa does not bind any Middleware in the kernel method. It only provides a lightweight and elegant Library of functions, which makes it easy to write Web applications.

General framework


General framework

The project is mainly divided into config and app:
config: Configuration file
app: Following the MVC architecture, application files

Third party package


Third party package

sequelize is the ORM framework under node. It is very good and powerful. It will be shown in the following examples.

Main configuration

Server.js


/**
 * Created by vslimit on 2017/9/8.
 */
'use strict';
require('dotenv').config();
const Koa =require('koa');
const app = new Koa();
const fs = require('fs');
const join = require('path').join;
const bodyParser = require('koa-bodyparser');
const model = join(__dirname, 'app/model');
var Router = require('koa-router');
var router = new Router();
const rest = require('./config/rest');

const config = require('./config');

const port = process.env.PORT || 3000;

module.exports = app;
app.use(bodyParser());
// app.use(async ctx => {
//     ctx.body = ctx.request.body;
// });
app.use(rest.restify());
app.use(router.routes()).use(router.allowedMethods());

fs.readdirSync(model)
    .filter(file => ~file.search(/^[^\.].*\.js$/))
    .forEach(file => require(join(model, file)));


// let files = fs.readdirSync(model);


require('./config/routes')(router);

listen();

module.exports = model;

function listen() {
    if (router.get('env') === 'test') return;
    app.listen(port);
    console.log('Express app started on port ' + port);
}


Development example

We use this framework to develop a Restful interface for function registration, login and loading. Let's look at the model first.

User

/**
 * Created by vslimit on 2017/9/10.
 */
const db = require('../util/db');
const crypto = require('crypto');
const uuid = require('node-uuid');

const User = db.defineModel('users', {
    name: {
        type: db.STRING(),
        allowNull: true
    },
    email: {
        type: db.STRING(),
        unique: true,
        allowNull: true
    },
    password: db.VIRTUAL(),
    mobile: {
        type: db.STRING(),
        unique: true
    },
    provider: db.STRING(),
    hashed_password: db.STRING(),
    salt: db.STRING(),
    auth_token: {
        type: db.STRING(),
        allowNull: true
    },
    access_token: {
        type: db.STRING(),
        allowNull: true
    }

});

User.beforeValidate(function (user) {
    if (user.isNewRecord) {
        let salt = this.methods.makeSalt();
        user.set('salt', salt);
        user.set('hashed_password', this.methods.encryptPassword(user.password, salt));
    }
});

User.afterCreate(function (user) {
    console.log(JSON.stringify(user));
    user.access_token = this.methods.makeAccessToken(user.id);
    console.log(user.access_token);
    user.save();
});

User.methods = {
    authenticate: function (password, salt, hashed_password) {
        return this.encryptPassword(password, salt) === hashed_password;
    },

    /**
     * Make salt
     *
     * @return {String}
     * @api public
     */

    makeSalt: function () {
        return Math.round((new Date().valueOf() * Math.random())) + '';
    },

    /**
     * Encrypt password
     *
     * @param {String} password
     * @return {String}
     * @api public
     */

    encryptPassword: function (password, salt) {
        if (!password) return '';
        try {
            return crypto
                .createHmac('sha1', salt)
                .update(password)
                .digest('hex');
        } catch (err) {
            return '';
        }
    },

    makeAccessToken: function (id) {
        return crypto
            .createHmac('sha1', id.toString())
            .update(uuid.v4() + Date.now())
            .digest('hex');
    },

   load: function (condition) {
        return User.findOne({where: condition});
    },

    count: function (condition) {
        return User.count({where: condition});
    },
};

module.exports = User;

Then controller

users

/**
 * Created by vslimit on 2017/9/12.
 */
'use strict';
const User = require('../model/User');
const ApiResult = require('../../config/rest').APIResult;

/**
 *  Create user
 */

exports.create = async(ctx, next) => {
    let mobile = ctx.request.body.mobile;
    let password = ctx.request.body.password;

    console.log(mobile);
    console.log(password);
    if (!mobile || !password) {
        ctx.rest(ApiResult("", -102, "Mobile number or password cannot be empty"));
    } else {
        let count = await User.methods.count({mobile: mobile});
        console.log(count);
        if (count > 0) {
            ctx.rest(ApiResult("", -101, "Mobile number already exists"));
        } else {
            let user = await User.create({
                mobile: mobile,
                password: password,
                provider: 'local'
            });
            ctx.rest(ApiResult(user.access_token));
        }
    }

};

exports.login = async(ctx, next) => {
    let mobile = ctx.request.body.mobile;
    let password = ctx.request.body.password;
    if (!mobile || !password) {
        ctx.rest(ApiResult("", -102, "Mobile number or password cannot be empty"));
    } else {
        let user = await User.methods.load({mobile: mobile});
        if (user) {
            if (User.methods.authenticate(password, user.salt, user.hashed_password)) {
                ctx.rest(ApiResult({
                    name: user.name,
                    mobile: user.mobile,
                    access_token: user.access_token
                }));
            } else {
                ctx.rest(ApiResult("", -105, "User password error"));
            }
        } else {
            ctx.rest(ApiResult("", -103, "user does not exist"));
        }
    }
};


exports.load = async(ctx, next) => {
    var u = await User.findById(ctx.params.id);
    ctx.rest(ApiResult(u));
};

Configure route

    app.post('/api/users', users.create);
    app.post('/api/login', users.login);
    app.get('/api/users/:id', users.load);

Function


Create user

response

Login response

Loading personal information

So far, the framework of Web Server based on Koa and Mysql has been built, and the functions of registration, login and loading user's personal information have been realized.

Reference material

The main reference in the project

General framework
sequelize document

Part of the code comes from Liao Xuefeng's official website
Official website
git

Code

All the code in this article has been submitted to git. If you like, go to star t under git.

Koa-Server's code is detailed in:[ https://github.com/vslimit/koa-server.git)

Topics: Mobile REST MySQL git