Use Vue3 and NodeJS to build h5 mobile single page and back-end RESTful project template

Use Vue3 and NodeJS to build h5 mobile single page and back-end RESTful project template

1. Project introduction

This project uses

Vue3
Build
h5
Mobile single page, use
NodeJS
based on
MVC
Build
RESTful
Frame template.

The front end is based on

Vue3 + webpack4 + sass + vant ui
of
rem
Adaptation plan, build mobile terminal template.

The backend is based on

koa + sequelize + mysql
Build
restful api
Template.

Node
Version requirements:
Vue CLI 4.x
need
Node.js v8.9
Or higher (recommended
v10
the above). Used in this example
Node.js v10.17.0

The source code has been submitted to github, so please try to use Star.

2. Unified development specifications

The first thing to do before code development is to use

VsCode
of
ESLint + stylelint
Unified format
JS
with
CSS
Code.

ESLint

VScode
Need to install
ESLint
Plug-in, after the installation is complete, if we want the extension to work, we need to do it first
ESLint
The installation and configuration.
EsLint
It can be installed locally and globally. Here, a part of the current project build system is used for partial installation:

npm install eslint --save-dev duplicated code

After the installation is complete, configure in the project root directory

.eslintrc.js
file:

module .exports = { //Specify the environment to be enabled env : { browser : true , es6 : true , node : true }, //Set language options parserOptions : { ecmaVersion : 6 }, //Enable recommended rules extends : 'eslint:recommended' , rules : { //Except when comparing with null literals, always enforce absolute equality eqeqeq : [ 'error' , 'always' , { null : 'ignore' }], } } Copy code

When using

rules
When adding custom rules, the first value of each rule represents the error level displayed after the rule is detected:

  1. off
    or
    0
    Will close the rule
  2. warn
    or
    1
    Treat the rule as a warning
  3. error
    or
    2
    Treat the rule as an error

More complete rules can be accessed:

eslint.cn/docs/rules/

eslint.vuejs.org/user-guide/...

Finally at

Vscode
of
setting.json
Enable
ESLint
:

//VSCode will verify the automatic formatting code according to the rules of the .eslintrc.js configuration file under your current project "editor.codeActionsOnSave" : { "source.fixAll" : true } Copy code

If in

Vscode
installed
vetur
Plug-in, in use
Vue3
Times
vue/no-multiple-template-root
mistake,
vetur
You will be prompted that there cannot be more than one root element. The solution is to open
setting
Configure, cancel check:

//F1>Preferences:Open Settings (JSON) "vetur.validation.template" : false copy the code

There is another problem

Vue
The project is placed in a subdirectory,
VsCode
Will go to the top-level directory to find
eslint-plugin-vue
, The meeting
Cannot find module'eslint-plugin-vue'
error. The solution is in
setting
Customize in configuration
ESLint
Work list:

//F1>Preferences:Open Settings (JSON) "eslint.workingDirectories" : [ "./vue3-template" ] Copy code

stylelint

VScode
Need in
stylelint
Plug-in, and then install it locally in the project:

npm install --save-dev stylelint stylelint- config-standard stylelint-order duplicated code

stylelint
Is the running tool,
stylelint-config-standard
Yes
stylelint
The recommended configuration.
stylelint-order
Yes
CSS
Attribute sorting plug-in (first write the positioning, then the box model, then write the content area style, and finally write
CSS3
Related attributes).

Configure under the project after installation

.stylelintrc.json
The configuration file:

{ //Enable the default recommended rule "extends" : "stylelint-config-standard" //The priority of rules is greater than extends. If you want to modify the default rules of the plugin, you can add rules "rules" : { //Use single quotes to specify the string Still double quotes "string-quotes" : "double" } } Copy code

3. the front end

Front end use

Vue-cli4
To initialize, using
@vue/cli 4.5.13
version:

vue -V @vue/cli 4.5 .13 npm get registry //here I did not use Taobao source HTTPS : //registry.npmjs.org/ copy the code

Here are the initial settings for my project:

Sass
Recommended to use
dart-sass
,Do not use
node-sass
.

For details, please refer to: panjiachen.github.io/vue-element...

Multi-environment configuration

Generally, a project will have the following 3 environments:

  • Development environment
    development
  • test environment
    stage
  • Production Environment
    production

in

package.json
inner
scripts
Configure three environments, as follows:

"scripts" : { "serve" : "vue-cli-service serve" , "build" : "vue-cli-service build" , "stage" : "vue-cli-service build --mode staging" , "preview " : "vue-cli-service build --report" , "lint" : "vue-cli-service lint" } Copy code

Create configuration files for different environments in the project root directory:

  • .env.development
NODE_ENV = 'development' = VUE_APP_BASE_API '/API-Development' duplicated code
  • .env.staging
NODE_ENV = 'staging' = VUE_APP_BASE_API '/API-the staging' duplicated code
  • .env.production
NODE_ENV = 'production' = VUE_APP_BASE_API '/API-Production' duplicated code

vue-cli
Officially integrated
webpack-bundle-analyzer
Plug-in analysis
webpack
Build the product. Just need to execute
npm run preview
You can build, package, and generate
report.html
Help analyze the contents of the package. After opening the file, it is as follows:

rem adaptation

because

Vant
Used by default
px
As a style unit, use if needed
rem
Unit, the official recommendation is to use the following two tools:

  • postcss-pxtorem
    Is a
    PostCSS
    Plug-in, used to
    px
    Units are converted to
    rem
    unit
  • lib-flexible
    Used to set
    rem
    Reference value
//install npm install vant@next -S npm install amfe-flexible -S npm install postcss -D @ pxtorem the install postcss-NPM 5.1 .1 -D duplicated code

Install the latest version

postcss 8.3.0
with
postcss-pxtorem 6.0.0
Not compatible, need to use
postcss-pxtorem@5.1.1
version.

in

main.js
Introduced in the file
lib-flexible
:

Import 'amfe-flexible' duplicated code

Then create a new one in the root directory

postcss.config.js
file:

module .exports = { plugins : { //The version of the postcss-pxtorem plugin needs to be >= 5.0.0'postcss-pxtorem' : { rootValue ( {file} ) { //The rootValue of the vant component uses 37.5 return file.indexOf( 'vant ' ) !==- 1 ? 37.5 : 75 }, propList : [ '*' ] } } } Copy code

Currently

postcss-pxtorem
The default version is 6.0.0, which is not compatible with vue-cli4. and so
postcss-pxtorem
The best version is down to 5.1.1.

Vant UI
Set up
rootValue: 37.5
, We used
lib-flexible
Fit, so
iPhone6
under
1rem
equal
37.5
px.

because

Vant
Used by default
px
As a style unit, if we say
Vant
The component has an element width
375px
. Set via plugin
rootValue
Yes
37.5
, Convert
rem
for
10rem
. If the plugin
rootValue
To
75
, Convert
rem
for
5rem
. But the actual
font-size
still is
37.5px
. and so
Vant
actual
css
Pixels are
187.5px
, The overall size has doubled.

So you can add one to judge whether it is

vant
File if it is
vant
File
rootValue: 37.5
As a benchmark. If the UI design draft is
750px
, We don t need to get the design draft and calculate directly.
1:1
To write
css
Style again.

vant on-demand loading

It is recommended to use the on-demand introduction of components and installation of official documents

babel-plugin-import
Plug-in:

Babel Plugin- the install-NPM Import -D duplicated code

Then configure in the root directory

babel.config.js
:

module .exports = { presets : [ '@vue/cli-plugin-babel/preset' ], plugins : [ [ 'import' , { libraryName : 'vant' , libraryDirectory : 'es' , style : true } ] ] } Copy code

allowable

src/plugins/vant.js
Under unified management components, write the work of introducing components into a js file separately, as follows:

Import {the Button, Tabbar, TabBarItem} from 'Vant' export default function ( app ) { app.use(Button).use(Tabbar).use(TabbarItem) } Copy code

Then in

main.js
Just import it in the file:

Import {createApp} from 'VUE' Import useVant from '@/plugins/vant' const App = createApp (the App) //register vant assembly useVant(app) Copy code

Vuex state management

Vuex
Use modular management to write a counter
demo
. The following directory structure:

store modules counter.js index.js Copy code

index.js
as follows:

Import {createstore} from 'vuex' const modulesFiles = require .context( './modules' , true , /\.js$/ ) Automatic assembly module// const modules modulesFiles.keys = (). The reduce ( ( modules, the ModulePath ) => { const moduleName = modulePath.replace ( /^\.\/(.*)\.\w+$/ , '$. 1 ' ) const value = modulesFiles(modulePath) modules[moduleName] = value.default return modules }, {}) export default createStore({ modules }) Copy code

counter.js
as follows:

const state = function () { return { count : 0 } } const mutations = { increment ( state, count ) { state.count++ } } const actions = { incrementAsync ( {commit} ) { return new Promise ( ( resolve, reject ) => { setTimeout ( () => { commit( 'increment' ) resolve() }, 1000 ) }) } } const getters = { evenOrOdd ( state ) { return state.count% 2 === 0 ? 'even' : 'odd' } } export default { namespaced : true , state, getters, actions, mutations } Copy code

in

main.js
Introduced in the file:

Import {createApp} from 'VUE' Import Store from './store' app.use(store) Copy code

How to use:

< template > < div > {{ count }} is {{ evenOrOdd }}. </div > < button @ click = "incrementAsync" > Increment async </button > </template > < script > import {computed} from ' VUE @/Runtime-Core ' Import {useStore} from ' vuex ' Export default { name : ' the App ' , Setup ( ) { const store = useStore() return { count : computed( () => store.state.counter.count), evenOrOdd : computed( () => store.getters[ 'counter/evenOrOdd' ]), incrementAsync : ( ) => store.dispatch( 'counter/incrementAsync' ) } } } </script > copy code

Vue-Router routing

This template uses the hash mode for development and also uses modularization for management.

router modules tabbar.js index.js Copy code

index.js
as follows:

Import {createRouter, createWebHashHistory} from 'VUE-Router' Import the tabBar from './modules/tabBar' //static route Export const constantRoutes = [the tabBar] //Dynamic routing export const asyncRoutes = [] const router = createRouter({ //The new history configuration replaces mode history : createWebHashHistory(), routes : constantRoutes //The object x returned by scrollBehavior in Vue3 is renamed to left, and y is renamed to top //scrollBehavior: () => ({ top: 0 }) }) Export default Router copy the code

Define bottom navigation

modules/tabBar.js
, And then create a new one in the corresponding directory
layout
,
Home
,
my
Components:

import Layout from '@/layout' export default { path : '/' , component : Layout, meta : { title : 'Home' , keepAlive : true }, redirect : '/home' , children : [ { path : 'home' , name : 'home' , component : () => import ( '@/views/home' ), meta : { title : 'Home' , keepAlive : true } }, { path : 'my' , name : 'my' , component : () => import ( '@/views/my' ), meta : { title : ' ' , keepAlive : true } } ] } Copy code

in

main.js
Introduced in the file:

Import {createApp} from 'VUE' Import Router from './router' app.use(router) Copy code

axios package

installation

axios
:

npm install axios -S copy the code

in

src/utils/request.js
Encapsulation
axios
:

Import Axios from 'Axios' Import Store from ' @/Store ' const servie = axios.create ({ the baseURL : process.env.VUE_APP_BASE_API, timeout : 10000 , headers : { ' the Type-the Content ' : ' file application/JSON; charset = UTF-8' } }) //Add request interceptor servie.interceptors.request.use( config => { if (store.getters.accessToken) { config.headers.accessToken = '' } return config }, error => { console .log( 'err:' + error) return Promise .reject(error) }) //Add response interceptor servie.interceptors.response.use( response => { const res = response.data if (res.code !== 200 ) { return Promise .reject(res.msg || 'error' ) } else { return res } }, error => { console .log( 'err:' + error) return Promise .reject(error) }) Export default servie copy the code

Then in

src/api
The unified management interface under the folder is just fine, as follows:

//common.js export function getInfo ( data ) { return request({ url : '/info' , method : 'get' , data }) } Copy code

Vue.config.js configuration

const path = require ( 'path' ) const resolve = dir => path.join(__dirname, dir) module .exports = { publicPath : '/' , outputDir : 'dist' , //The directory of production environment build files assetsDir : 'static' , //The directory of static resources (js, css, img, fonts) of outputDir lintOnSave : false , productionSourceMap : false , //If you don't need the source map of the production environment, you can set it to false to speed up the construction of the production environment. // devServer: { disableHostCheck: true, proxy: { ///api '/api': { target: 'http://xx.xxx.com', // API changeOrigin: true, // true, host target ws: true, // webSocket // , target/api , pathRewrite: { '^/api': '' } } }, hot: true, // true port: 8999, // open: true, // // overlay: { warnings: true, errors: true } }, chainWebpack: config => { // alias config.resolve.alias .set('@', resolve('src')) config.plugin('preload').tap(() => [ { rel: 'preload', fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/], include: 'initial' } ]) config.plugins.delete('prefetch') config.when(process.env.NODE_ENV !== 'development', config => { config .plugin('ScriptExtHtmlWebpackPlugin') .after('html') .use('script-ext-html-webpack-plugin', [ { // runtime inline: /runtime\..*\.js$/ } ]) .end() config.optimization.splitChunks({ chunks: 'all', cacheGroups: { //cacheGroups test test commons: { name: 'chunk-commons', test: resolve('src/components'), minChunks: 3, // priority: 5, // reuseExistingChunk: true // chunk true chunk }, node_vendors : { name : 'chunk-libs' , chunks : 'initial' , //Only package third-party tests that are initially dependent on : /[\\/]node_modules[\\/]/ , priority: 10 }, vantUI : { name : 'chunk-vantUI' , //Separately unpack vantUI. Priority : 20 , //The number is large and the weight is high. When multiple cacheGroups are met, the high weight test is assigned : /[\\/]node_modules [\\/]_?vant(.*)/ } } }) config.optimization.runtimeChunk( 'single' ) }) } } Copy code

4. Node backend

I wanted to use it at first

egg.js
To develop, but right
node.js
It is not very friendly for me to understand the whole project. So I read a lot of articles and added a handwriting based on
koa
of
MVC
The back-end template, writing code on this basis, feels quite easy to use at present.

issues that need resolving

Build one manually by yourself

NodeJS
Back-end templates, there are many issues to consider, here I list what I thought of:

  • Quick commissioning
  • Error handling
  • Logging
  • File directory hierarchy
  • Data legality verification
  • Distinguish between development environment and production environment

....

Then we will solve these problems step by step.

Project directory structure

First list the basic directory structure of the project to get a general understanding:

bin //project startup directory www //configuration item logs //log file src //source code config //public configuration file index.js config-development.js //development environment config-production.js //production environment config-staging.js //test environment controllers //routing layer userController.js db //Database connection configuration index.js init-db.js // lib // WXBizDataCrypt.js // middlewares // errorHandle.js // getTokenData.js // token myLogger.js // models // index.js // models user.js //user services // index.js // services userService.js //user service layer utils //tool library response.js //custom response exception.js //custom exception .eslintrc.js //eslint configuration app.js //application entry ecosystem.config.js //pm2 configuration file logs.config.js //log configuration package-lock.json package.json //project dependencies Copy code

Introduction to nodemon and cross-env

nodemon
Used to monitor
node.js
Any changes in the application will automatically restart the service.

cross-env
Used to process
windows
and other
unix
The system is inconsistent in the way of setting environment variables.

The following dependencies need to be installed:

npm install cross-env -D npm install nodemon -D Copy code

The following will be in

package.json
Used in
nodemon
with
cross-env
.

Node development and debugging

--inspect
Command can start debugging mode.

Then in

Chrome
In the address bar of the browser, enter
chrome://inspect
After pressing Enter, you can see the interface below.

in

Target
Section, click
inspect
Link, you can enter debugging.

The second method is to open "Developer Tools", there is a

Node
Click on the green mark to enter debugging.

For more details, please refer to this article .

PM2 and development environment configuration

Because this project is based on

koa2
Yes, so the environment configuration needs to be installed first:

npm install koa -S copy the code

1. Create a new www file

in

bin
Create under the folder
www
Executable files are used to run, shut down, and restart services.

#!/usr/bin/env node /** * Module dependencies. */ var app = require ( '../app' ) var http = require ( 'http' ) var port = normalizePort(process.env.PORT || '3000' ) /** * Create HTTP server. */ var server = http.createServer(app.callback()) /** * Listen on provided port, on all network interfaces. */ server.listen(port) server.on('error', onError) server.on('listening', onListening) /** * Normalize a port into a number, string, or false. */ function normalizePort(val) { var port = parseInt(val, 10) if (isNaN(port)) { //named pipe return val } if (port >= 0) { //port number return port } return false } /** * Event listener for HTTP server "error" event. */ function onError(error) { if (error.syscall !== 'listen') { throw error } var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port //handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges') process.exit(1) } } /** * Event listener for HTTP server "listening" event. */ function onListening () { var addr = server.address() var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port console .log( 'Listening on' + bind) } Copy code

here it is

koa-generator
The program startup code can be used directly by simply modifying it according to our specific situation. Then create
app.js
, Import
koa2
:

const Koa = require ( 'koa' ) const app = new Koa() app.use( async (ctx, next) => { ctx.body = 'Hello World' }) Module1 .exports = App duplicated code

2. Install global PM2

$ npm install pm2@latest -g # or The Yarn $ , Ltd. Free Join the Add PM2 copy the code

After the global installation is complete, in

package.json
of
script
Configuration and
pm2
Default read configuration file
ecosystem.config.js
Configure for multi-environment operation.

//ecosystem.config.js module .exports = { //application list, pm2 can manage multiple applications apps : [ { name: 'www', // script: './bin/www', // ignore_watch: ['node_modules', 'logs'], // args: '', // instances: 1, // cluster fork autorestart: true, watch: true, // max_memory_restart: '1G', // // env_staging: { 'PORT': 8002, 'NODE_ENV': 'staging' }, // env_production: { 'PORT': 80, 'NODE_ENV': 'production' } } ] }

3. package.json

"scripts": { "dev": "cross-env NODE_ENV=development PORT=8001 nodemon --inspect bin/www", "stage": "cross-env pm2 start ecosystem.config.js --env staging", "prod": "cross-env pm2 start ecosystem.config.js --env production", "logs": "pm2 logs", "stop": "pm2 stop ecosystem.config.js" }

npm run dev // npm run stage // npm run prod // npm run logs // pm2 npm run stop // pm2

http

//exception.js /** * http */ class HttpException extends Error { constructor(customError = {}) { super() const defaultError = { message: ' ', state: 500, errorCode: 10000 } const { message, status, errorCode } = Object.assign(defaultError, customError) this.message = message this.status = status this.errorCode = errorCode } } /** * request params * example: throw new ParamsException({ message: ' ', status: 400,errorCode: 10001 }) */ class ParamsException extends Error { constructor(customError = {}) { super() const defaultError = { message: ' ', status: 400, errorCode: 10001 } const { message, status, errorCode } = Object.assign(defaultError, customError) this.message = message // this.status = status //http status code 2xx 4xx 5xx this.errorCode = errorCode // 10001 } } module.exports = { HttpException, ParamsException }

response

response

/** * * @param {*} data * @param {*} code * @param {*} msg * @returns {} */ const responseFormat = (data = {}, msg = 'success',code = 200) => { return { code, msg, data } } module.exports = { responseFormat }

koa

1. koa-bodyparser koa-static koa2-cors

npm install koa-bodyparser -S npm install koa-static -S npm install koa2-cors -S
const static = require('koa-static') const bodyparser = require('koa-bodyparser') const path = require('path') const cors = require('koa2-cors') //post app.use(bodyparser({ enableTypes: ['json', 'form', 'text', 'xml'] })) // app.use(static(path.join(__dirname, './src/public'))) // app.use(cors())

2. logger

log4js

npm install log4js -S

logs.config.js

/** * */ const log4js = require('log4js') const path = require('path') let developmentLogConfig = {} // if (process.env.NODE_ENV !== 'production') { developmentLogConfig = { STDOUT: { type: 'stdout' } } } // const fileAllName = path.join(__dirname, './logs/all.log') const fileErrorName = path.join(__dirname, './logs/error.log') log4js.configure({ /** * cluster ,pm2 true, * pm2: process.env.NODE_ENV === 'production' */ appenders: { ...developmentLogConfig, FILE_ALL: { type: 'datefile', //log4js all.log all.2021-06-03.log filename: fileAllName, backups: 10, // 10 maxLogSize: 10485760, // 10M daysToKeep: 10, // 10 , 0 keepFileExt: true // }, FILE_ERROR: { type: 'datefile', filename: fileErrorName, daysToKeep: 30, keepFileExt: true } }, categories: { default: { appenders: process.env.NODE_ENV !== 'production' ? ['STDOUT', 'FILE_ALL'] : ['FILE_ALL'], level: 'debug' }, error: { appenders: ['FILE_ERROR'], level: 'error' } } }) const defaultLogger = log4js.getLogger() const errorLogger = log4js.getLogger('error') // log module.exports = { defaultLogger, errorLogger }

middlewares
myLogger.js
app.js

// src/middlewares/myLogger.js const { defaultLogger } = require('../../logs.config') module.exports = function() { return async(ctx, next) => { const start = new Date() await next() const ms = new Date() - start const logText = `${ctx.method} ${ctx.status} ${ctx.url} ${ms}ms` defaultLogger.info(logText) } }

app.js

const myLogger = require('./src/middlewares/myLogger') // app.use(myLogger())

3. jwt

jsonwebtoken
token
koa-jwt
jwt

npm install jsonwebtoken -S npm install koa-jwt -S
const jwt = require('jsonwebtoken') // const config = { // jwt PRIVATE_KEY: 'private_key', // JWT_EXPIRED: 60 * 60 } // token const token = jwt.sign({ data: 'testToken', exp: config.JWT_EXPIRED }, config.PRIVATE_KEY) ctx.body = { token }

koa-jwt
token

//app.js const koaJwt = require('koa-jwt') app.use(koaJwt({ secret: 'private_key' }).unless({ // login register path: [ /^\/user\/login/, /^\/user\/register/ ] }))

4.

//errorHandles.js const { errorLogger } = require('../../logs.config') module.exports = function() { return async(ctx, next) => { try { await next() } catch (err) { errorLogger.error(err.stack) // ctx.status = err.statusCode || err.status || 500 ctx.body = { msg: err.message || ' ', errorCode: err.errorCode || err.statusCode || err.status || 500 } } } }
// app.js const Koa = require('koa') const app = new Koa() const errorHandler = require('./src/middlewares/errorHandler.js') // jwt app.use(errorHandler())

joi
hapijs
joi

// npm install joi -S

demo
API joi

const Joi = require('joi') const { ParamsException } = require('../utils/exception') router.get('/vali', async ctx => { const schema = Joi.object({ username: Joi.string().min(1).max(30).required(), password: Joi.string().trim().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')), }) //allowUnknown: true const { error } = schema.validate({ username: 'bob', age: 10 }, { allowUnknown: true }) if (error) { // throw new ParamsException({ message: error.details[0].message, state: 400 }) } ctx.body = {msg:'success'} })

ParamsException

controllers

koa-router
koa-router
:

npm install koa-router -S

controllers
userController.js
,

const Router = require('koa-router') const router = new Router() router.prefix('/user') router.get('/getInfo', ctx => { ctx.body = { data: 'getInfo' } }) module.exports = router

app.js
controllers

//app.js const path = require('path') const fs = require('fs') const Koa = require('koa') const app = new Koa() /* loader router */ const jsFiles = fs.readdirSync(path.join(__dirname, './src/controllers')).filter(file => file.indexOf('.') !== 0 && file.endsWith('.js') !== -1) jsFiles.forEach(file => { const mapping = require(path.join(__dirname, './src/controllers', file)) app.use(mapping.routes(), mapping.allowedMethods()) })

services models

models

services
models
mysql
Node
ORM Sequelize

npm install mysql2 -S npm install sequelize -S

//config-development.js const config = { myEnv: 'development', mysql: { dialect: 'mysql', host: 'xxx.xx.xx.x', port: 3306, database: 'template_db', username: 'root', // password: '123456', // // max: 20, min: 10, // idle: 10000, // acquire: 30000 } } module.exports = config

sequelize
model
sequelize.define
model
Sequelize
API
db
index.js
Model
:

const { v4: uuidv4 } = require('uuid') const { mysql } = require('../config') const { Sequelize, DataTypes } = require('sequelize') const { dialect, host, port, database, username, password, max, min, idle, acquire } = mysql // const sequelize = new Sequelize({ dialect, host, port, database, username, password, pool: { max, min, idle, acquire }, define: { freezeTableName: true, // underscored: true // }, query: { raw: true } }) /* * uuid * @returns uuid */ function generateId() { return uuidv4().replace(/-/g, '') } /* * model * model createAt,updateAt * uuid , id */ function defineModel(name, attributes, options = {}) { const attrs = { ...attributes, id: { type: DataTypes.STRING(50), primaryKey: true }, //createAt updateAt BIGINT , createAt: { type: DataTypes.BIGINT }, updateAt: { type: DataTypes.BIGINT } } //console.log(`model->${name} is create`) return sequelize.define(name, attrs, { ...options, timestamps: false, hooks: { //create hook, id,createAt,updateAt beforeCreate(instance, options) { if (!instance.id) { instance.id = generateId() } const now = Date.now() instance.createAt = now instance.updateAt = now }, //update hook, updateAt beforeUpdate(instance) { instance.updateAt = Date.now() } } }) } const mysqlDB = { defineModel, // sync() { return new Promise((resolve, reject) => { if (process.env.NODE_ENV !== 'production') { sequelize.sync({ force: true }).then(() => { console.log(' ') resolve() }).catch(err => { reject(err) }) } else { reject(' sync()') } }) } } module.exports = { DataTypes, mysqlDB, sequelize }

node init-db.js

const { sync } = require('../models') /** * */ sync().then(() => { console.log('init db ok!') process.exit(0) }).catch((e) => { console.log('failed with: ' + e) process.exit(0) })

defineModel
createAt
updateAt
Model
models
model
user.js

const { mysqlDB, DataTypes } = require('../db') module.exports = mysqlDB.defineModel('user', { username: { type: DataTypes.STRING(255), allowNull: false, comment: ' ' }, password: { type: DataTypes.STRING(255), allowNull: false, comment: ' ' } }, { tableName: 'user' // } )

models
index.js
model

//models/index.js const fs = require('fs') const path = require('path') const { sequelize, mysqlDB } = require('../db') const jsFiles = fs.readdirSync(__dirname).filter((file) => { return file.endsWith('.js') && (file !== 'index.js') && (file.indexOf('.') !== 0) }) const models = {} jsFiles.forEach((file) => { const name = file.substring(0, file.length - 3) models[name] = require(path.join(__dirname, file)) }) models.sequelize = sequelize // models.sync = async() => { return await mysqlDB.sync() } module.exports = models

const { user } = require('../models/index')
model

services

services
models
index.js
service

//services/index.js const fs = require('fs') const path = require('path') const jsFiles = fs.readdirSync(__dirname).filter(file => { return file.endsWith('.js') && (file !== 'index.js') && (file.indexOf('.') !== 0) }) const service = {} jsFiles.forEach((file) => { const name = file.substring(0, file.length - 3) service[name] = require(path.join(__dirname, file)) }) module.exports = service

const { userService } = require('../services')
service
userService.js
:

const { user, sequelize } = require('../models') module.exports = { // async login({ username, password }) { return await user.findOne({ where: { username, password }}) }, // async editUser(user) { // `individualHooks:true` `beforeUpdate` `hooks` const result = await sequelize.transaction(async(t) => { const res = await user.update(user, { where: { id: user.id }, transaction: t, individualHooks: true }) return res }) return result }, // async addUser({ username, password }) { // const result = await sequelize.transaction(async(t) => { const result = await user.create({ username, password }, { transaction: t }) return result }) return result } }

SQL
sequelize.transaction

sequelize

sequelize

const sequelize = new Sequelize({ // ... logging: process.env.NODE_ENV === 'production' ? sqlLogger : console.log }) function sqlLogger(msg) { defaultLogger.info(msg) }

Vue3
Node

github