I’ve always wanted to try to write something with Node. After learning about it, I wrote a simple background project with MVC pattern from scratch using KOA framework and Sequelize ORM, so I’ll take notes here.

1. Build projects

1. Create folder and initialize project

mkdir node-koa-demo # create project
cd node-koa-demo # enter directory
cnpm init -y # generated package. Json
cnpm install koa koa-body koa-router koa-static koa2-cors path -S Install the KOA plugin
touch app.js Generate entry file
Copy the code

2. Define the project startup command

/ / package. Json {" name ":" the node - koa - demo ", "version" : "1.0.0", "description" : ""," main ":" index. Js ", "scripts" : { "test": "echo \"Error: no test specified\" && exit 1", "start": "node app.js" }, "keywords": [], "author": "" and" license ", "ISC", "dependencies" : {" koa ":" ^ 2.11.0 ", "koa - body" : "^ 4.4.1", "koa - the router" : "^ 7.4.0", "koa - static" : "^ 5.0.0 koa2 - cors", "" :" ^ 2.0.6 ", "path" : "^ 0.12.7"}}Copy the code

3. Define the entry file

// app.js
const Koa = require('koa')
const app = new Koa()
const config = require('./config')
const path = require('path')
const koaBody = require('koa-body') ({// // Parses the body's middleware
  multipart: true.// Support file upload
  encoding:'gzip'.formLimit: '5mb'.// Limit the size of the form request body
  jsonLimit: '5mb'.// The size limit of the JSON data body
  textLimit: '5mb'.// Limit the size of the text body
  formidable:{
    uploadDir: path.join(__dirname, '/public/upload'), // Set the file upload directory
    keepExtensions: true.// Keep the file suffix
    maxFieldsSize: 200 * 1024 * 1024.// Set the maximum size of a file to be uploaded. The default size is 2 MB
    onFileBegin: (name, file) = > { // Set the file before uploading
      console.log(`name: ${name}`)
      console.log(file)
    }
  }
})
const static = require('koa-static')

// Parse the body middleware
app.use(koaBody)
app.use(static(path.join(__dirname)))

app.listen(config.service.port, () => {
  console.log('server is running')})Copy the code

4. Define the configuration file

1) Define the local configuration file env file:

The env file:

// .envSERVE_PORT=[project port] SERVE_ENVIROMENT=[Project environment]Copy the code

Install the Dotenv plugin to reference env files in your project

cnpm install dotenv -S Env to configure environment variables
Copy the code

Import file import plug-in:

const dotenv = require('dotenv') // Import the configuration file
dotenv.config()
Copy the code
2) Define the config file config.js in the project:

The config. Js:

module.exports = {
  service: {
    port: process.env['SERVE_PORT'].enviroment: process.env['SERVE_ENVIROMENT'] | |'dev'}}Copy the code

Import file config.js:

const config = require('./utils/config')
global.config = config
Copy the code

Start project: CNPM run start

5. Hot update of the project

Installing a plug-in

cnpm install nodemon -S
Copy the code
Add command:
// package.json
"start:dev": "nodemon node app.js"
Copy the code

Second, development interface

1. Sequelize connect to mysql

1) Install dependencies
cnpm install mysql2 sequelize -S
Copy the code
2) Create the database configuration folder DB
mkdir db
touch db/index.js
Copy the code
3) Define the database connection configuration and import it
// .envDB_DATABASE=[database name] DB_USER=[database username] DB_PSW=[database connection password] DB_HOST=[database port]Copy the code
4) Define the Sequelize file

// db/index.js
const Sequelize = require('sequelize')

const sequelize = new Sequelize(
  process.env['DB_DATABASE'],
  process.env['DB_USER'],
  process.env['DB_PSW'] and {host: process.env['DB_HOST'].// Database address
    dialect: 'mysql'.// Database type
    dialectOptions: { / / character set
      charset:'utf8mb4'.collate:'utf8mb4_unicode_ci'.supportBigNumbers: true.bigNumberStrings: true
    },
    pool: {
      max: 5.// Maximum number of links in the connection pool
      min: 0.// Minimum number of connections
      idle: 10000 // If a thread has not been used for 10 seconds, the connection pool is released
    },
    timezone: '+ 08:00'.// Time zone 8 EAST
    logging: (log) = > {
      console.log('dbLog: ', log)
      return false
    } // Some SQL logs will be printed during execution. If set to false, they will not be displayed})module.exports = sequelize
Copy the code
5) Define the model
mkdir model
touch model/User.js
Copy the code
const Sequelize = require('sequelize')
const sequelize = require('.. /db')

const User = sequelize.define('user', {
  id: {
    type: Sequelize.INTEGER,
    allowNull: false.// When set to false, NOT NULL (non-null) constraints are added and non-null validation is performed when data is saved
    comment: 'ID'.// Field description (Since 1.7+, this description is not added to the database
    autoIncrement: true.// Whether to increment automatically
    primaryKey: true.// Specify whether it is a primary key
    unique: true.// When set to true, a unique constraint is added to the column
  },
  password: {
    type: Sequelize.STRING(20),
    validate: {}, // The validation object that is called each time the model is saved. You can use the validation functions in validator.js (see DAOValidator), or custom validation functions
    allowNull: false.// When set to false, NOT NULL (non-null) constraints are added and non-null validation is performed when data is saved
    comment: 'password' // Field description (since 1.7+, this description is not added to the database)
  },
  name: {
    type: Sequelize.STRING(20),
    validate: {
      notEmpty: true
    }, // The validation object that is called each time the model is saved. You can use the validation functions in validator.js (see DAOValidator), or custom validation functions
    allowNull: false.// When set to false, NOT NULL (non-null) constraints are added and non-null validation is performed when data is saved
    comment: 'User name' // Field description (since 1.7+, this description is not added to the database)
  },
  email: {
    type: Sequelize.STRING(20),
    validate: {
      isEmail: true
    }, // The validation object that is called each time the model is saved. You can use the validation functions in validator.js (see DAOValidator), or custom validation functions
    allowNull: false.// When set to false, NOT NULL (non-null) constraints are added and non-null validation is performed when data is saved
    comment: 'email' // Field description (since 1.7+, this description is not added to the database)
  },
  phone: {
    type: Sequelize.STRING(11),
    allowNull: false.// When set to false, NOT NULL (non-null) constraints are added and non-null validation is performed when data is saved
    comment: 'Mobile number' // Field description (since 1.7+, this description is not added to the database)
  },
  birth: {
    type: Sequelize.DATE,
    validate: {
      isDate: true
    }, // The validation object that is called each time the model is saved. You can use the validation functions in validator.js (see DAOValidator), or custom validation functions
    allowNull: false.// When set to false, NOT NULL (non-null) constraints are added and non-null validation is performed when data is saved
    defaultValue: new Date(), // Literal default, JavaScript function, or an SQL function
    comment: 'birthday' // Field description (since 1.7+, this description is not added to the database)
  },
  sex: {
    type: Sequelize.INTEGER,
    validate: {
      isInt: true.len: 1
    }, // The validation object that is called each time the model is saved. You can use the validation functions in validator.js (see DAOValidator), or custom validation functions
    allowNull: false.// When set to false, NOT NULL (non-null) constraints are added and non-null validation is performed when data is saved
    defaultValue: 0.// Literal default, JavaScript function, or an SQL function
    comment: 'Sex, 0- male 1- female' // Field description (since 1.7+, this description is not added to the database)}, {},freezeTableName: true.// When set to true, Sequelize does not change the table name, otherwise it may adjust according to its rules
  timestamps: true.// Add createdAt and updatedAt timestamp fields to the model
})

// Create a table, default is false, true is to delete the original table, create again
User.sync({
  force: false,})module.exports = User
Copy the code
[* Automatic exposure model (choose whether to automatically expose all models as required)]
// model/index.js

/* Scan all models */
const fs = require('fs')
const files = fs.readFileSync(__dirname + '/model') // Traverse the directory
const jsFiles = files.filter(item= > {
  return item.endsWith('.js')
}, files)

module.exports = {}
for (const file of jsFiles) {
  console.log(`import model from file ${file}`)
  const name = file.substring(0, file.length - 3)
  module.exports[name] = require(__dirname + '/model/' + file)
}
Copy the code

2. Define routes

mkdir router
touch router/index.js
Copy the code
// router/index.js
const router = require('koa-router') ({prefix: '/api'
})

router.get('/'.async(ctx, next) => {
  ctx.body = 'Hello World~'
})

module.exports = router
Copy the code

Import in the entry file app.js

// Routing middleware
const router = require('./router')
// Start the service and generate the route
app.use(router.routes()).use(router.allowedMethods()) // Start the service and generate the route
Copy the code

3. Custom middleware

1) Create middleware folders
mkdir middleware
Copy the code
2) Define an error-handling middleware
touch middleware/exception.js Middleware files
touch utils/http-exception.js # define a known exception class
Copy the code
3) Define the known exception class to inherit from the Error class

Make it clear whether a known exception is an unknown exception

// utils/http-exception.js
/** * Default exception */
class HttpException extends Error {
  constructor(MSG = 'error request ', errorCode = 10000, code = 400) {super(a)this.errorCode = errorCode
    this.code = code
    this.msg = msg
  }
}

class ParameterException extends HttpException {
  constructor(msg, errorCode) {
    super(a)this.code = 400
    this.msg = msg || 'Parameter error'
    this.errorCode = errorCode || 10000}}class AuthFailed extends HttpException {
  constructor(msg, errorCode) {
    super(a)this.code = 401
    this.mag = msg || 'Authorization failed'
    this.errorCode = errorCode || 10004}}class NotFound extends HttpException {
  constructor(msg, errorCode) {
    super(a)this.code = 404
    this.msg = msg || 'Resource not found'
    this.errorCode = errorCode || 10005}}class Forbidden extends HttpException {
  constructor(msg, errorCode) {
    super(a)this.code = 403
    this.msg = msg || 'Access denied'
    this.errorCode = errorCode || 10006}}class Oversize extends HttpException {
  constructor(msg, errorCode) {
    super(a)this.code = 413
    this.msg = msg || 'Upload file is too large'
    this.errorCode = errorCode || 10007}}class InternalServerError extends HttpException {
  constructor(msg, errorCode) {
    super(a)this.code = 500
    this.msg = msg || 'Server error'
    this.errorCode = errorCode || 10008}}module.exports = {
  HttpException,
  ParameterException,
  AuthFailed,
  NotFound,
  Forbidden,
  Oversize,
  InternalServerError
}
Copy the code
4) Define exception handling middleware
// middleware/exception.js
const { HttpException } = require('.. /utils/http-exception')

// Global exception listener
const catchError = async(ctx, next) => {
  try {
    await next()
  } catch(error) {
    // There is a known exception
    const isHttpException = error instanceof HttpException
    // Development environment
    const isDev = global.config.service.enviroment === 'dev'

    // Display unknown exception information on console: in development environment, not HttpException throws exception
    if(isDev && ! isHttpException) {throw error
    }

    /** * Whether the error is known or unknown * returns: * MSG error message * error_code Error code */
    if (isHttpException) {
      ctx.body = {
        msg: error.msg,
        error_code: error.errorCode
      }
      ctx.response.status = error.code
    } else {
      ctx.body = {
        msg: 'Unknown error'.error_code: 9999
      }
      ctx.response.status = 500}}}module.exports = catchError
Copy the code
5) Import file loading introduces exception handling middleware
// Load the global exception
const errors = require('./utils/http-exception')
global.errs = errors

const app = new Koa()
// The global exception is monitored and handled by the middleware at the top of all middleware
const catchError = require('./middleware/exception')
app.use(catchError)
Copy the code

4. Define a unified API return format

1) Resjson.js defines the interface return format
// utils/resJson.js
const ResultJson =  {
  success: (params) = > {
    return {
      data: params.data || null.// The data returned
      msg: params.msg || 'Operation successful'.// A message is returned
      code: 1 // Return the interface call status code, 0- failed, 1- success}},fail: (params) = > {
    return {
      data: params.data || null.msg: params.msg || 'Operation failed'.code: 0.error_code: params.errorCode // Returns an interface exception code}}}module.exports = ResultJson
Copy the code
2) Use in middleware

Modify the exception handling middleware mentioned above

/* Error handling middleware */
const { HttpException } = require('.. /utils/http-exception')
const resJson = require('.. /utils/resJson')

/ /... Omit the above
    if (isHttpException) {
      ctx.body = resJson.fail(error)
      ctx.response.status = error.code
    } else {
      ctx.body = resJson.fail({
        msg: 'Unknown error'.error_code: 9999
      })
      ctx.response.status = 500
    }
/ /... Omit the following

Copy the code

Write an interface that returns all user information

1) Create a controller folder and define user.js to be used for operations on the User table.
mkdir controller
touch controller/User.js
Copy the code
2) Obtain the list of all users
const User = require('.. /model/User.js')
const resJson = require('.. /utils/resJson')

module.exports = {
  selectAll: async (ctx, next) => {
    await User.findAll({
      raw: true.attributes: { // Do not return the password field
        exclude: ['password'] 
      }
    }).then((res) = > {
    	// Successful return
      ctx.body = resJson.success({data: res})
    }).catch((err) = > {
    	// Fail, catch exception and output
      ctx.body = resJson.fail(err)
    })
  }
}

Copy the code
3) Declare the interface path
// router/index.js
const router = require('koa-router') ({prefix: '/api'
})
// User controller
const User = require('.. /controller/user')

// Get all users
router.get('/user/list', User.selectAll)

module.exports = router
Copy the code
4) Access interface

Access interface address: http://localhost:3002/api/user/list

Results:

{
    "data": [{"id": 1."name": "cai"."email": "[email protected]"."phone": "13234323453"."birth": "The 2019-12-13 T01: none. 000 z"."sex": 1."createdAt": "The 2019-12-13 T01: just 000 z"."updatedAt": "The 2019-12-13 T01: just 000 z"}]."msg": "Operation successful"."code": 1
}
Copy the code