Node JS Learning Notes (XV) - - A Simple Example of Node.js + Koa2 Building Website

Keywords: Javascript Session MySQL Redis Database

Catalog

Preface

A simple example of building a website with Node.js+Express was written earlier: http://www.cnblogs.com/zhongweiv/p/nodejs_express_webapp.html

This article is built with Node.js+Koa2 as an example.

 

  Koa:   https://github.com/koajs/koa

       http://koa.bootcss.com (Chinese)

 

Koa has not introduced much, and has written Express before. The same team has created Express articles before. By contrast, we can see some advantages.

 

Construction project and other preparatory work

Create a database

CREATE DATABASE IF NOT EXISTS nodesample CHARACTER SET UTF8;

USE nodesample;

SET FOREIGN_KEY_CHECKS=0;

DROP TABLE IF EXISTS `userinfo`;
CREATE TABLE `userinfo` (
  `Id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key',
  `UserName` varchar(64) NOT NULL COMMENT 'User name',
  `UserPass` varchar(64) NOT NULL COMMENT 'User password',
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='User Information Table';

Create Koa2 Project

Install koa-generator: https://github.com/17koa/koa-generator

npm install -g koa-generator

The following figure after successful installation (version: 1.1.16)

 

Then create the Koa2 project and install the dependencies

cd working directory
koa2 project name
cd project directory & NPM install

Other Requirements Packages for Installation Project

1. Install packages needed to use MySQL

npm install --save mysql

If you haven't used it, you can read the related operation articles I wrote before. http://www.cnblogs.com/zhongweiv/p/nodejs_mysql.html

2. Install EJS (koa2 defaults to jade, I am used to ejs)

npm install --save ejs

If you haven't used it, you can read the related operation articles I wrote before. http://www.cnblogs.com/zhongweiv/p/nodejs_express.html

3. Install Session to store related packages (stored in redis)

npm install koa-session  https://github.com/koajs/session

npm install --save koa-session

koa-session-redis https://github.com/Chilledheart/koa-session-redis

npm install --save koa-session-redis

Remove redundant files and re-plan project catalogs

1. Delete the files under views and routes that came with the creation of the project

2. Re-plan the project catalogue as follows

Explanation of Catalogue Rules:

1. Add new pub directory: mainly for the unified storage of "data access", "business logic", "public method files", "database help files", "configuration files" and so on.

2. Add utils directory under pub directory: mainly for the uniform storage of public files such as "common function file", "return value file", "enumeration file" and so on.

3. Add config directory under pub directory: Mainly for storing all kinds of configuration files uniformly

4. Add db directory under pub directory: Mainly for storing all kinds of database help classes, such as "mysql-helper.js", "mongo-helper.js" and so on.

5. Add the model directory under the pub directory: Mainly for the unified storage of various database tables CURD operation

6. Add bll directory under pub directory: mainly for the unified storage of the specific implementation of various business logic

configuration file

As can be seen from the figure above, I created a new config.js under the new config directory under pub.

In this config.js, you will write the configurations required in the "development environment" and "publishing environment" with the following code

/**
 * configuration file
 */
//Release configuration
const production = {

    //Server Port
    SERVER_PORT : 3000,

    //REDIS To configure
    REDIS: {
        host: 'localhost',            
        port: 6379,
        password: "abcd",
        maxAge: 3600000
    },

    //MYSQL Database Configuration
    MYSQL: {
        host: "localhost",
        user: "root",
        password: "abcd",
        port: "3306",
        database: "nodesample",
        supportBigNumbers: true,
        multipleStatements: true,
        timezone: 'utc'
    }

}

//Development and configuration
const development = {

    //Server Port
    SERVER_PORT : 3000,

    //REDIS To configure
    REDIS: {
        host: 'localhost',            
        port: 6379,
        password: "abcd",
        maxAge: 3600000
    },

    //MYSQL Database Configuration
    MYSQL: {
        host: "localhost",
        user: "root",
        password: "abcd",
        port: "3306",
        database: "nodesample",
        supportBigNumbers: true,
        multipleStatements: true,
        timezone: 'utc'
    }

}

const config = development

module.exports = config

Plan sample routing and create new related files

The example will have the function of registration and login. First, we plan the routing, create new files under routes and views (such as files in project catalog diagram), and modify app.js files.

const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')

const config = require('./pub/config/config.js');
const session = require('koa-session');
const RedisStore = require('koa2-session-redis');

const index = require('./routes/index')
const reg = require('./routes/reg')
const login = require('./routes/login')
const logout = require('./routes/logout')

// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))

app.use(views(__dirname + '/views', {
  extension: 'ejs'
}))

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

app.keys = ['Porschev'];
const redis_conf = {  
  key: 'Porschev',
  maxAge: config.REDIS.maxAge,
  overwrite: true,
  httpOnly: true,  
  rolling: false,
  sign: true,
  store: new RedisStore({
    host: config.REDIS.host,
    port: config.REDIS.port,    
    password: config.REDIS.password    
  })
};

app.use(session(redis_conf, app));

// routes
app.use(index.routes(), index.allowedMethods())
app.use(reg.routes(), reg.allowedMethods())
app.use(login.routes(), login.allowedMethods())
app.use(logout.routes(), logout.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

app.listen(config.SERVER_PORT, () => {
  console.log(`Starting at port ${config.SERVER_PORT}!`)
});

module.exports = app

Pay attention to the modifications or additions to the red markers

Implementing Data Access and Business Logic Related Approaches

1. Write a mysql-helper.js to facilitate operation in connection pool

const config = require('./../config/config.js')
const mysql = require("mysql")

const pool = mysql.createPool(config.MYSQL)
  
let query = function(sql, args) {
  
    return new Promise((resolve, reject) => {
        pool.getConnection(function(err, connection) {
            if (err) {
                resolve(err)
            } else {
                connection.query(sql, args, (err, result) => {
        
                    if (err) {
                        reject(err)
                    } else {
                        resolve(result)
                    }
                    connection.release()

                })
            }
        })
    })
  
}

module.exports = { 
    query 
}

2. Write data access related methods (userinfo.js in the model directory), as follows

const mysqlHelper = require('./../db/mysql-helper.js')

const userinfo = {

  /**
   * Add a data
   * @param  {object} args  parameter
   * @return {object}       Result
   */
  async add ( args ) {
    let sql = 'INSERT INTO userinfo(UserName, UserPass) VALUES(?, ?)'
    let params = [args.username, args.userpass]
    let result = await mysqlHelper.query(sql, params)
    return result
  },

  /**
   * Get a piece of data from UserName
   * @param  {object} args  parameter
   * @return {object}       Result
   */
  async getByUserName( args ){
    let sql = 'SELECT Id, UserName, UserPass FROM userinfo WHERE UserName = ?'
    let params = [args.username]
    let result = await mysqlHelper.query(sql, params)
    return result
  },

   /**
   * Get the number based on UserName
   * @param  {object} args  parameter
   * @return {object}       Result
   */
  async getCountByUserName( args ){
    let sql = 'SELECT COUNT(1) AS UserNum FROM userinfo WHERE UserName = ?'
    let params = [args.username]
    let result = await mysqlHelper.query(sql, params)
    return result
  },

}

module.exports = userinfo

3. Plan the return value (retcode.js in utils directory) before writing business logic

/*
 * Return code
 */
const RetCode = {
    SessionExpired: -1,             //session Be overdue
    Fail: 0,                        //fail
    Success: 1,                     //Success
    ArgsError: 2,                   //Parameter error
    UserExisted: 10,                //Users already exist
    UsernameOrPasswordError: 11,    //Error in username or password      
    UserNotExist: 12,               //user does not exist    
};

module.exports = RetCode
retcode.js

4. Write business logic such as "login" and "registration" (userinfo.js under bll)

const usermodel = require('./../model/userinfo.js')
const retCode = require('./../utils/retcode.js')

const userinfo = {

  /**
   * register
   * @param  {object} ctx   context
   * @return {object}       Result
   */
  async register ( ctx ) {
    let form = ctx.request.body
    
    const args = {
        username: form.username,
        userpass: form.userpass
    }
        
    let result = {
        code: retCode.Success,    
        data: null
    }
    
    //Validation is not empty.
    if(!args.username || !args.userpass){
        result.code = retCode.ArgsError        
        return result
    }

    //Get the number of users based on the username
    let userNumResult = await usermodel.getCountByUserName(args)

    //User name has been registered
    if(userNumResult[0].UserNum > 0){
        result.code = retCode.UserExisted        
        return result
    }

    //Insert registration data
    let userResult = await usermodel.add(args)

    if(userResult.insertId <= 0){
        result.code = retCode.Fail        
        return result
    }

    return result
  },

  /**
   * Sign in
   * @param  {object} ctx   context
   * @return {object}       Result
   */
  async login ( ctx ) {
    let form = ctx.request.body
    
    const args = {
        username: form.username,
        userpass: form.userpass
    }
        
    let result = {
        code: retCode.Success,    
        data: null
    }
    
    //Validation is not empty.
    if(!args.username || !args.userpass){
        result.code = retCode.ArgsError        
        return result
    }

    //Getting User Information Based on User Name
    let userResult = await usermodel.getByUserName(args)

    //user does not exist
    if(userResult.length == 0){
        result.code = retCode.UserNotExist        
        return result
    }
    
    //ERROR Incorrect username or password
    if(userResult[0].UserName != args.username || userResult[0].UserPass != args.userpass){
        result.code = retCode.UsernameOrPasswordError        
        return result
    }

    //Users ID Deposit in Session in
    ctx.session = {id: userResult[0].Id}

    return result
  },

}

module.exports = userinfo

register

1.views directory reg.ejs

<html>
<head>
<title>Nodejs Learning Notes (Fifteenth Five)--- Node.js + Koa2 A simple example of building a website</title>
</head>
<body>
<h1><%= title %></h1>
//Login name:<input type="text" id="txtUserName" maxlength="20" />
<br/>
<br/>
//Password:<input type="password" id="txtUserPwd" maxlength="12" />
<br/>
<br/>
//Password:<input type="password" id="txtUserRePwd" maxlength="12" />
<br/>
<br/>
<input type="button" id="btnSub" value="register" />
</body>
</html>

<script src="/javascripts/jquery-1.11.2.min.js" type="text/javascript"></script>
<script src="/javascripts/md5.js" type="text/javascript"></script>

<script type="text/javascript">   
    $(function(){
        $('#btnSub').on('click', function(){
            var $txtUserName = $('#txtUserName'),
                txtUserNameVal = $.trim($txtUserName.val()),
                $txtUserPwd = $('#txtUserPwd'),
                txtUserPwdVal = $.trim($txtUserPwd.val()),
                $txtUserRePwd = $('#txtUserRePwd'),
                txtUserRePwdVal = $.trim($txtUserRePwd.val());
                       
            if(txtUserNameVal.length == 0){
                alert('User name cannot be empty');                
                return false;
            }

            if(txtUserPwdVal.length == 0){                
                alert('Password cannot be empty');                
                return false;
            }

            if(txtUserRePwdVal.length == 0){
                alert('Duplicate passwords cannot be empty');   
                return false;
            }

            if(txtUserPwdVal != txtUserRePwdVal){                 
                alert('Two password inconsistencies');                 
                return false;
            }

            $.ajax({
                url: '/reg',
                type: 'POST',
                dataType: 'json',
                data: {
                    username: txtUserNameVal,                    
                    userpass: hex_md5(txtUserPwdVal)                                        
                },
                beforeSend: function (xhr) {},
                success: function (res) {
                    if (res != null && res.code) {

                        var retVal = parseInt(res.code);

                        switch (retVal) {
                            case 2:
                                alert('Incorrect input');
                                break;
                            case 0:
                                alert('login has failed');
                                break;
                            case 1:
                                alert('login was successful!');
                                location.href = '/login'                                
                                break;
                            case 10:
                                alert('User Registered');
                                break;                         
                        }
                    }
                    else {
                        alert('operation failed');
                    }

                },
                complete: function (XMLHttpRequest, textStatus) {},
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert('operation failed');
                }
            });            
        })
    });

</script>
reg.ejs

2. reg.js in routes directory

const router = require('koa-router')()
const userBll = require('./../pub/bll/userinfo.js')
const title = 'register'

router.prefix('/reg')

router.get('/', async (ctx, next) => {
  await ctx.render('reg', { title })
})

router.post('/', async (ctx, next) => {

  let result = await userBll.register(ctx)

  ctx.body = result;

})

module.exports = router

Sign in

1. login.ejs in the views directory

<html>
<head>
<title>Nodejs Learning Notes (Fifteenth Five)--- Node.js + Koa2 A simple example of building a website</title>
</head>
<body>
<h1><%= title %></h1>
//Login name:<input type="text" id="txtUserName" maxlength="20" />
<br/>
<br/>
//Password:<input type="password" id="txtUserPwd" maxlength="12" />
<br/>
<br/>
<input type="button" id="btnSub" value="Sign in" />
</body>
</html>

<script src="/javascripts/jquery-1.11.2.min.js" type="text/javascript"></script>
<script src="/javascripts/md5.js" type="text/javascript"></script>

<script type="text/javascript">   
    $(function(){
        $('#btnSub').on('click', function(){
            var $txtUserName = $('#txtUserName'),
                txtUserNameVal = $.trim($txtUserName.val()),
                $txtUserPwd = $('#txtUserPwd'),
                txtUserPwdVal = $.trim($txtUserPwd.val());
                       
            if(txtUserNameVal.length == 0){
                alert('User name cannot be empty');                
                return false;
            }

            if(txtUserPwdVal.length == 0){                
                alert('Password cannot be empty');                
                return false;
            }
           
            $.ajax({
                url: '/login',
                type: 'POST',
                dataType: 'json',
                data: {
                    username: txtUserNameVal,                    
                    userpass: hex_md5(txtUserPwdVal)                                        
                },
                beforeSend: function (xhr) {},
                success: function (res) {
                    if (res != null && res.code) {

                        var retVal = parseInt(res.code);

                        switch (retVal) {
                            case 2:
                                alert('Incorrect input');
                                break;
                            case 0:
                                alert('Login failed');
                                break;
                            case 1:
                                alert('Login successfully!');
                                location.href = '/'                                
                                break;
                            case 11:
                                alert('Error in username or password');
                                break;
                            case 12:
                                alert('user does not exist');
                                break;
                        }
                    }
                    else {
                        alert('operation failed');
                    }

                },
                complete: function (XMLHttpRequest, textStatus) {},
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert('operation failed');
                }
            });            
        })
    });

</script>
login.ejs

2. login.js in routes directory

const router = require('koa-router')()
const userBll = require('./../pub/bll/userinfo.js')
const title = 'Sign in'

router.prefix('/login')

router.get('/', async (ctx, next) => {
  await ctx.render('login', { title })
})

router.post('/', async (ctx, next) => {
  
    let result = await userBll.login(ctx);

    ctx.body = result;
  
})

module.exports = router

home page

1. index.ejs in the views directory

<html>
<head>
<title>Nodejs Learning Notes (Fifteenth Five)--- Node.js + Koa2 A simple example of building a website</title>
</head>
<body>
<h1><%= title %></h1>

<% if(id != null) {%>
    <h3>log on user ID: <%= id %> <a id="btnLogOut" href="javascript:void(0);">Safe exit</a></h3>
<% } %>
</body>
</html>

<script src="/javascripts/jquery-1.11.2.min.js" type="text/javascript"></script>

<script type="text/javascript">   
    $(function(){
        $('#btnLogOut').on('click', function(){
           
           if(!confirm('Are you sure you want to quit??')){
                return;
           }

            $.ajax({
                url: '/logout',
                type: 'POST',
                dataType: 'json',
                data: {},
                beforeSend: function (xhr) {},
                success: function (res) {
                    if (res != null && res.code) {

                        var retVal = parseInt(res.code);

                        switch (retVal) {                           
                            case 0:
                                alert('fail');
                                break;
                            case 1:
                                alert('Success!');
                                location.href = '/login'                                
                                break;                           
                        }
                    }
                    else {
                        alert('operation failed');
                    }

                },
                complete: function (XMLHttpRequest, textStatus) {},
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert('operation failed');
                }
            });            
        })
    });

</script>
index.ejs

2. index.js under routes directory

const router = require('koa-router')()
const title = 'home page'

router.get('/', async (ctx, next) => {  
  //Judgement login
  if(!ctx.session || !ctx.session.id){
    await ctx.redirect('/login')  
  }else{    
    const id = ctx.session.id;
    await ctx.render('index', { title, id })
  }  
})

module.exports = router

Implement in index.js file to jump back to login page if session does not exist

Safe exit

1. logout.js in routes directory

const router = require('koa-router')()
const retCode = require('./../pub/utils/retcode.js')

router.prefix('/logout')

router.get('/', async (ctx, next) => {
  await ctx.render('logout', {})
})

router.post('/', async (ctx, next) => {
  
  ctx.session = null;

  let result = {
    code: retCode.Success,    
    data: null
  }

  ctx.body = result;
  
})

module.exports = router

After writing

Without going into some details of API, this article is mainly for comparison. Node JS Learning Notes (7) - - A Simple Example of Node.js + Express Building Website Look, it's a one-parent example, but this time Koa2 is used to make it easier for you to see the difference between Koa2 and express.

Generally speaking, Koa2 is better at it. async and await are also more friendly for those with C# language foundation, so they don't need to be understood twice.

Koa2 is more elegant and readable than the nested callbacks used in express.

Examples are limited, and other operations are done by finding API s or github components on the official website, such as some of the most commonly used functions: cookies, uploading files, session storage to other media, etc.

 

References: https://koa.bootcss.com/

 

Old planning does not put source code, although it is an example structure, but try to follow the usual idea of doing projects to achieve, interested hands-on projects to do will understand some ideas, the code is placed in the article, there are questions left a message ^ uuuuuuuuuuu

Posted by mrdamien on Sat, 05 Jan 2019 09:48:09 -0800