Node_review

The best Node.js -Koa restfulAPI practice 💦💦💦

MIT License

Stars
5

Koa framework foundation for node.js, a tutorial for building the restfuAPI

API Docs Path: DOCS

test this api: test

Getting started

# clone the Project
git clone https://github.com/251205668/Node_review.git

# enter the project directory
cd Node_review

# install dependency
npm install

# develop
npm run dev

# test
Listening the 3000 port

Features

  • middlewares
    • Global exception handling
    • JWT token validation
    • Token does not sense refresh
  • classic journals
    • Get the latest journals
    • Get the next issue of the journal
    • Get the last issue of the journal
    • For details of a particular issue
    • Get the thumb up information
    • Get the journals I liked
  • Book
    • Get a list of popular books
    • Get book reviews
    • Get the book thumb up information
    • A new comment
    • Get hot search keywords
    • Books retrieval
    • Get book details
  • favor
    • give a like
    • Cancel the thumb up

How It work

Basic

Write the most basic code for an API

const koa = require('koa')
const Router = require('koa-router')

const router = new Router()

const app = new koa()

router.get('/classic/latest', (ctx, next) => {
  ctx.body = { key: 'classic' }
})
// router.routes() 
app.use(router.routes())
app.listen(3000, () => {
  console.log('http3000')
})

middleware

The middleware sends functions called by HTTP, one instance can define multiple middleware, and the middleware call always returns promise

app.useregister the middlewarectxcontentsnextnext middleware

// --
app.use((ctx, next) => {
  // ctx 
  console.log('')
  next()
})
app.use((ctx, next) => {
  console.log('')
})

Passing parameters By mounting to CTX, the onion model is first guaranteed

app.use(async (ctx, next) => {
  await next()
  console.log(ctx, r)
})

app.use(async (ctx, next) => {
  ctx.r = await axios.get('www.baidu.com').data
  await next()
})

// dom

The onion model

Execute on fun1 and then execute the middleware function under fun1.You can determine whether the function is fully executed, with the middleware function as the dividing line

Simple example:

app.use(async (ctx, next) => {
  fun1()
  await next()
  funt1()
})

app.use(async (ctx, next) => {
  funt2()
})

use async,await to let promise sequentiallyCall to order: fun1()TOP ====> fun2() ====> fun1 Bottom

async await

await Features:

  • evaluation
    await promise, await function async async,**await **
app.use(async (ctx, next) => {
  console.log(1)
  const a = await next()
  console.log(2)
})

app.use(async (ctx, next) => {
  console.log(3)
})

// 1 3 2
  • Blocking threads
    A common asynchronous call: 'to the resource read file operation database send http`
//

app.use((ctx,next)=>{
    console.log(1)
    axios.get('www.baidu.com').then((res)=>{
          const a = res
    })
    console.log(a)
    console.log(2)
})

//   1 2 res

await
app.use(async(ctx,next)=>{
    console.log(1)
    const a = await axios.get('www.baidu.com')
    console.log(a)
    console.log(2)
})
//  1 res 2

Limited energy, not translated for the time being

ctx.path,ctx.method``ctx.body

app.use(async (ctx, next) => {
  if (ctx.path === '/clasic/latest' && ctx.method === 'GET') {
    ctx.body = { key: 'clasic' }
  }
})

koa-router

const router = new route()

router.get('/',(ctx,next)=>{
    ...
})

app.use(router.routes())

nodemon node nodemon app.js

** module require-directory**

const requireDirectory = reuire('require-directory')

requreDirectory(module, './api/v1', (visit: function))

function whenExportModule(obj) {
  if (obj instanceof Router) {
    app.use(obj.routes())
  }
}

core.js

const Router = require('koa-router')
const requireDirectory = require('require-dicectory')

//  Router
class InitManger {
  static initcore(app) {
    InitManager.app = app
    InitManager.InitLoadRouters()
  }
  static InitLoadRouters() {
    const path = `${process.cwd()}/api/v1`
    requireDirectory(module, path, {
      visit: whenLoadrouters,
    })
    function whenLoadrouters(obj) {
      if (obj instanceof Router) {
        InitManger.app.use(obj.routes())
      }
    }
  }
}

app.js

const IniManager = require('core.js')

InitManager.initcore(app)
app.listen(3000)

localhost:3000/v1/3/classic/latest?password=123 header:token:1111 ,body:{"key":"localhost"}

router.get('/v1/:id/classic/latest', (ctx, next) => {
  // url
  const params = ctx.params
  // query
  const query = ctx.request.query
  // token
  const query = ctx.request.header
  // 
  const body = ctx.request.body
  ctx.body = { key: 'classic' }
})
msg
code
request_url
HTTP status code 2xx 4xx 5xx
Http

200 'ok'
400 'params error'
404 'Not found'
403 'forbidden'
502 'bad gateway' 
500 ''
504 ''

execption``Error, json

  • Http-execption.js
class Httpexecption extends Errror {
  // 
  constructor(msg = '', code = 500, errorCode = 9999) {
    super()
    this.msg = msg
    this.code = code
    this.errorCode = 9999
  }
}

class Paramexecption extends Httpexecption {
  constructor(msg, code, errCode) {
    super()
    this.msg = msg || ''
    this.code = 400
    this.errorCode = 10000
  }
}
module.exports = {
  Httpexecption,
  Paramexecption,
}
  • (token)

app.js``koa-jwt,token,

{
  "msg":"",
  "code":999,
  "request":"HttpRequest.method/request.path"
}
const {HttpException} =require('Http-exception')
const exception = async(ctx,next){
  try{
    await next()
  }catch(error){
    // token 
    //Httpexception,
    if(error.status === 401){
      ctx.status = 401
      ctx.body={
        msg:"token",
        code:401,
        request:`${ctx.method}${ctx.path}`
      }
    }else{
      // 
      const isHttpException = error instanceof HttpException
      if(isHttpException){
        ctx.status = error.code
        ctx.body = {
          msg:error.msg,
          code:error.errorCode,
          request:`${ctx.method}${ctx.path}`
        }
      }else{
        //
        ctx.status = 500
        ctx.body = {
          msg:"",
          code:999,
          request:`${ctx.method}${ctx.path}`
        }
      }
    }

  }
}

Lin-validator Paramexecption,util.js


const {Linvalidator,Rule} from 'Lin-validator.js'

// 
class PositiveIntegerValidator extends Linvalidator{
    constructor(){
       super()
    //  lin-validator  
    //   
    this.id = [
      new Rule('isInt','',{min:1})
    ]
  }
    }
}
module.exports = {PositiveIntegerValidator}

** ** ( validate )

const { PositiveIntegerValidator } = require('validator.js')

router.get('/classic/:id/latest', (ctx, next) => {
  const v = new PositiveIntegerValidator().validate(ctx)
})

** ** get

const param = ctx.params
const v = new PositiveIntegerValidator().validate(ctx)
const id = v.get('param.id') // id 
// 
const id = v.get('param.id', (parsed: false))
module.exports = {
  enviorment: 'dev',
}

,,

if (global.config.enviorment === 'dev') {
  throw error
}

sql

CREATE DATABASE 
DROP DATABASE 
1.   NOT NULL
2.   DEFAULT ''
3.  UNIQUE
3.  PRIMARY KEY
create table (
 () [],
    ...
)
DROP TABLE ;
DESC 

Alter table   change     ;


Alter table   modify    ;
insert into (12...)values(1,2...)
insert into (1,2) values(1,2),(1,2);   //MYSQL
insert into  values(1,2);          //
insert into () select  from 2;     //
insert into  select  from 2;        //
delete from  where 
update  set = where 
: update user set username=7yue where id =1;
select * from 
select 1 from 
select .. from  where 
=  >  >=  <  <=  and  &&  or  not
where  between 1 and 2;     //

where  not between 1 and 2;  //

where !(  between 1 and 2 );  //
where  is null; //null
where  like '%0';  //0
where  like '0%';  //0
where  like '%0%';  //0
where  order by  [asc/desc]
select * from 1,2 where 1.=2.; //,where

select * from 1 [inner] join 2 on 1.=2.; //,join..onjoin..on,inner

sequlize()

sequelize

config.js

module.exports = {
  database: {
    //     
    dbName: 'koa',
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: 'wohenpi0918',
  },
}

sequelize API

const Sequelize = require('sequelize')
const {dbName,host,port,user,password} = require('config.js')

// :    
const sequlize = new Sequelize(dbName,user,password.{
 	// 
      dialect:'mysql',
      host,
      port,
    // sql
      logging:true,
    // 
  	  timezone:'+08:00',
      define:{
    //   updatedAt, createdAt
      timestamps:true,
    // timestamps  deletedAt 
      paranoid:true,
    //  
       createdAt:'created_at',
       updatedAt:'updated_at',
       deletedAt:'deleted_at',
    // 
    //  updatedAt  updated_at
       underscored: true,
  }
      })

   // 
   sequelize.sync({
       //  
       force: false,
   })
module.exports = {sequelize}

,

model user.js

const { sequelize } = require('db.js')
const { Sequelize, Model } = require('sequelize')

class User extends Model {}
// 
User.init(
  {
    // :  
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      //  id
      autoIncrement: true,
    },
    username: { type: Sequelize.STRING, unique: true },
    password: Sequelize.STRING,
    email: { type: Sequelize.STRING, unique: true },
    openid: {
      //  64
      type: Sequelize.STRING(64),
      unique: true,
    },
  },
  {
    sequelize,
    //   
    tableName: 'user',
  }
)

, sequelize user

sequelize API

sequelize API pormise , async``await

1.   class A extends Model{}  A.unit({},{sequelize,tableName:"name"})
2.   get(){ let title = this.getDataValue('title')..}  set(val){this.setDataValue('index',value)}

3. Validations
username:{type:Sequelize.STRING,validate:{len:[2,10] ....}}

----------
ModelAPI

1. removeAttribute([attribute]) ()

2. sync()   {force:true} ,

3. drop() 

4. getTableName() schema

5. scope() 

6. findOne   await ModelNmae.findOne({
    where:{
        'index':value
    }
})

7. findAll   await ModelName.findAll({
    where:{
        attr1:value,
        attr2:value
    }
})
 ,`$gt` `lte`  `$or`
Model.findAll({
  where: {
    attr1: {
      $gt: 50
    },
    attr2: {
      $lte: 45
    },
    attr3: {
      $in: [1,2,3]
    },
    attr4: {
      $ne: 5
    }
  }
})
// WHERE attr1 > 50 AND attr2 <= 45 AND attr3 IN (1,2,3) AND attr4 != 5


8. findById()  id

9. count()  

10. findAndCount  

11. create() 

12. max()  min()  sum()

13. upsert 

14. className.transition(async (t)=>{
    ...
})
 
 Modle.transcation(async t =>{
     await Favor.create({
         uid,art_id,type
     },{transcation:t})
     ...
 })

15. destory 

16. restore 

17   increment decrement
user.increment(['age', 'number'], {by:2}).then(function(user){
  console.log('success');
})

:

:

:

const {User} = require('user.js')

class RegisterValidator extends Linvalidator{
    constructor(){
        // 
        this.username= [
            new Rule('isLength','',{min:4,max:32})
        ]
        // 
        this.password1 =  [
            new Rule('matches',',,''/^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$/')
        ]
        // 
        this.password1 = this.password2
        this.email = [
            new Rule('isEmail','')
        ]
    }
    //  validate
    validateUserName(params){
        // params.body body
        const username = params.body.username
        // sequelize
        const user = User.finOne({
            where:{
                username:username
            }
        })
    }
    validateEmail(params){
        const email = params.body.email
        const user = USer.findOne({
            where:{
                email:email
            }
        })
    }
}
API
const Router = require('koa-router')
const router = new Router({
  // 
  prefix: '/v1/user',
})

router.post('/register', async (ctx) => {
  //   
  const v = await new RegisterValidator().validate(ctx)
  // 
  const params = {
    username: v.get('body.username'),
    password: v.get('body.password1'),
    email: v.get('body.email'),
  }
  //   .create()
  await User.create(user)
})
const bcryptjs = require('bcryptjs')
//
User.init({
  password: {
    type: Sequeize.STRING,
    set(val) {
      //  
      const sault = bcryptjs.getSaltSync(10)
      const pwd = bcryptjs.hashSync(val, sault)
      // 
      this.setDataValue('password', pwd)
    },
  },
})

,

//  ,
function isInType(val) {
  for (let key in this) {
    if (this[key] === val) {
      return true
    }
  }
  return false
}
const LoginType = {
  //    
  USER_EMAIL: 100,
  USER_PHONE: 101,
  USER_MINI: 102,
  isInType,
}

module.exports = {
  LoginType,
}

account``secret,

class Loginvalidator extends Linvalidator {
  constructor() {
    super()
    this.account = [
      new Rule('isLength', '', { min: 4, max: 128 }),
    ]
    this.secret = [
      (new Rule() = 'isOptional'),
      new Rule('isLength', '6', { min: 6 }),
    ]
  }
  validateLoginType(params) {
    const type = params.body.loginType
    if (!type) {
      throw new Error('')
    }
    if (!global.config.LoginType.isInType(type)) {
      throw new Error('type ')
    }
  }
}

module.exports = { Loginvalidator }

isOptional

,

async function emailLogin(account, secret) {
  const user = await User.vertifyEmail(account, secret)
}

,

user.js

class User extends Model{
    async function vertifyEmail(account,secret){
        const user = await User.findOne({
            where:{
                email:account
            }
        })
        if(!user){
            throw 
        }
        // 
        const corret = bcrypt.compareSync(secret,User.password)
        if(!corret){
            throw 
        }
        return user
    }
}
router.get('/token',async(ctx,next)=>{
    const v = new Loginvalidator().validate(ctx)
    const type = v.get('body.loginType')
    const account = v.get('body.account')
    const secret = v.get('body.secret')
    switch(type){
        case LoginType.USER_EMAIL:
            // 
            await emailLogin(account,secret)
            break;
        case :
        break;
    }
    // token
    ctx.body={
        token
    }
})
URL
appId
appSecret
class wxManager {
  static async openidTotoken(code) {
    // url
    const url = util.format(URL, appId, appSecret, code)
    const result = await axios.get(url)
    if (result.status !== 200) {
      throw new getOpenIDException()
    }
    if (result.data.errcode !== 0) {
      throw new getOpenIDException('openID' + result.data.errcode)
    }
    //    token
    let user = User.getOpenIdUser(result.data.openid)
    if (!user) {
      user = User.registerOpenId(result.data.openid)
    }
    let token = generate(user.id, Auth.USER)
  }
}

util.format Node.js util API,

getOpenIdUser Model user

registerOpenId openid

Auth.USER Auth class scope level level scope token,

class Auth {
  constructor(level) {
    this.level = level || 1
    Auth.USER = 8
    Auth.ADMIN = 16
    Auth.SUPER_ADMIN = 32
  }
}

** openid token **

static verifyToken(token){
    try {
       jwt.verify(token, global.config.security.secretKey)
      return true
    } catch (error) {
      return false
    }
  }

Storage token,

, token

token``jwt API

jwt.sign() //   :1.(auth) 2.secretKey() 3.

:jwt.sign(
   {uid,scope},secretKey,{expiresIn}
  )


jwt.verify() // token
//  token  
try catch
security = {
  secretKey: 'abcdefg', // 
  expiresIn: 60 * 60, //
}
const generateToken = function (uid, scope) {
  const token = jwt.sign({ uid, scope }, security.secretKey, { expiresIn })
  return token
}
  • token
async function emailLogin(account, secret) {
  // user 

  const token = generate(user.id, 2)
  return token
}
async function vertifyEmail(account, secret) {
  const user = await User.findOne({
    where: {
      email: account,
    },
  })
  if (!user) {
    throw new LoginExecption('')
  }
  if (!bcrypt.compareSync(secret, user.password)) {
    throw new LoginExecption('')
  }
  return user
}

:

  1. token header``body
  2. token

token HTTPBasicAuth header

jwt.verify() token

,

const baseAuth = require('base-auth')
class Auth {
    constructor(){}
    get m(){
        return async (ctx,next)=>{
            // basicAuthtoken  API req
            const UserToken = baseAuth(ctx.req)
            if(!USerToken || !UserToken.name){
                throw new token
            }
            try{
                var decode = jwt.verify(UserToken,secretKey)
                //  
            }catch(error){
                 if (error.name == 'TokenExpiredError') {
          errMsg = 'token'
        }
        throw new ForbidenException(errMsg)
            }
        }
        ctx.auth = {
            uid:decode.uid,
            scope:decode.scope
        }
        // 
        await next()
    }

}

new Auth().m m class get

, router.get('') new Auth().m

(BasicAuth ) (API key )

HTTP header

header:{
    Authorization:Basic base64(account:password) // base64token
}

// base64
import {Base64} from 'base64-js'
const base64 = Base64.encode(token+':')
return 'Basic'+base64
header: {
  Authorization: 
}

API key base64

// headerquerytoken
class Auth {
  get m() {
    return async (ctx, next) => {
      const UserToken = ctx.request.header.token
      if (!USerToken) {
        throw new token()
      }
      // token
      try {
        var decode = jwt.verify(UserToken, global.config.secretKey)
      } catch (error) {
        if ((error.name = 'TokenExpiredError')) {
          throw new Error('')
        }
        throw new token()
      }
    }
  }
}

:

wx.request({
  url: '',
  method: 'POST',
  header: {
    token: wx.getStorageSync('token'),
  },
  success: (res) => {
    console.log(res.data)
  },
})

  • : index 
    

sequelize

classic art flow user favor

sequelize

classic: movie sentence music

art ,

flow: :art_id,index,type

user:

favor

,

fow

router.get('/latest', new Auth().m,async(ctx)=>{
   let latest = async folw.findOne({
       // sequlize   ASC
       order:[
           ['index','DESC']
       ]
   })
   let art = async Art.getOne(latest.index)
   art.setDataValue('index',latest.index)
    ctx.body = art

})

,

Art

class Art {
  // art_id  type
  static async getOne(art_id, type) {
    const find = {
      where: {
        id: art_id,
      },
    }

    let result = null
    switch (type) {
      case 100:
        result = await movie.findOne(find)
        break
      case 200:
        result = await music.findOne(find)
        break
      case 300:
        result = await sentence.findOne(find)
        break
      case 400:
        break
      default:
        break
    }
    return result
  }
}


``

sequelize

sequelize.transaction(async (t)=>{
    ...
})
// Favor  artfav_nums

const favor =await Favor.finOne({
    where:{
        uid,art_id,type
    }
})
if(favor){
    throw new LikeException('')
}
// return
return sequelize.trancation(async t =>{
    await Favor.create({
        art_id,
        type,
        uid
    },{transcation : t})
    const art =await Art.getOne(art_id,type)
    //  by 
    await art.increment('fav_nums',{by:1,transaction:t})
})

// dislike 
 deleted_at
MOdel.class.destroy(force:false,transcation:t)

flow index index+1 art like_status index

router.get('/:index/next', new Auth().m, async (ctx) => {
  //  art_id type  
  const v = await new IndexValidator().validate(ctx)
  const index = v.get('path.index')
  const next = await flow.findOne({
    where: {
      index: index + 1,
    },
  })
  if (!next) {
    throw new NotFoundException('')
  }
  let art = await Art.getOne(next.art_id, next.type)
  const like_status = await Favor.Userlike(ctx.auth.uid, next.art_id, next.type)
  art.setDataValue('index', next.index)
  art.setDataValue('like_status', like_status)
  ctx.body = art
})

uid, art_id type favor

static async Userlike(uid, art_id, type) {
    // art
    const favor = await Favor.findOne({
      where: {
        uid,
        art_id,
        type:type
      }
    })
    //  true
    return !!favor
  }

type art_id flow like_status index

router.get('/:type/:id/detail', new Auth().m, async (ctx) => {
  const v = await new ClassicValidator().validate(ctx)
  const art_id = v.get('path.id')
  const type = parseInt(v.get('path.type'))
  const classic = await flow.scope('bh').findOne({
    where: {
      art_id,
      type: {
        [Op.not]: 400,
      },
    },
  })
  if (!classic) {
    throw new NotFoundException('')
  }
  let art = await Art.getOne(art_id, type)
  const like_status = await Favor.Userlike(ctx.auth.uid, art_id, type)
  art.setDataValue('index', classic.index)
  art.setDataValue('like_status', like_status)
  ctx.body = {
    art,
  }
})

uid favor Art

json key

const artInfoObj = {
  100: [],
  200: [],
  300: [],
}

for artInfoList

artInfoObj[artinfo.type].push(artinfo.art_id)

type

ids artInfoObj[key]

type key

for ,

obj key

undefined

[[],[],[]]

faltten

static async getFavorList(list,uid) {
    //  [[100:],[200:],[300:]]
    let FavorListObj = {
      100: [],
      200: [],
      300: []
    }
    // type ids  obj {100:[1,2,3],200:[1,2,3]..}
    list.forEach(item => {
      FavorListObj[item.type].push(item.art_id)
    })
    let ret = []
    for (let item in FavorListObj) {
      // key :item-type  keyids FavorListobj[item]
      //in
      let itemX = parseInt(item)
      if (FavorListObj[item].length === 0) {
        continue
      }
      ret.push(await Favor._getlistByType(itemX, FavorListObj[item],uid))
    }
    // loadsh 
    return flatten(ret)
  }
  static async _getlistByType(type, ids,uid) {
    const find = {
      where: {
        id: {
          [Op.in]: ids
        }
      }
    }
    let result = []
    switch (type) {
      case 100:
        result= await movie.scope('bh').findAll(find)
        break
      case 200:
        result = await music.scope('bh').findAll(find)
        break
      case 300:
        result = await sentence.scope('bh').findAll(find)
        break
      default:
        break
    }
    // result  [[],[],[]]
    result.forEach(async(item)=>{
      let like_status =await Favor.Userlike(uid,item.id,item.type)
      item.setDataValue('like_status',like_status)
    })
    return result
  }

 static async getHotBooklist(list){
    // art_id
    let ids = []
    list.forEach((book)=>{
      ids.push(book.id)
    })
  const favors=  await Favor.scope('bh').findAll({
      art_id:{
        [Op.in]:ids
      },
      type:400,
    group:['art_id'],
    //  [{art_id:1,count:},{}....]
    attributes:['art_id',[Sequelize.fn('COUNT','*'),'count']]
    })
    // 
    list.forEach((book)=>{
      Hotbook._setCount(book,favors)
    })
    return list
  }
  // favor countbook
  static _setCount(book,favors){
    let count = 0
    favors.forEach((favor)=>{
      if(book.id === favor.art_id){
        count = favor.get('count')
      }
    })
    book.setDataValue('count',count)
    return book
  }

book id fav_nums

query keywordstart count

start summary=1

encodeURI(q)

json

toJSONsequelize

this.getDataValue()

toJSON(){
    return {
        content:this.getDataValue('content')
    }
}

Model dataValues

KOA-STATIC

__dirname

 / static
const static = require('koa-static')
const path = require('path')
app.use(static(path.join(__dirname, '/static')))
// localhost static

: host: 'http://localhost:3000/'

art image

_request(url, resolve, reject, data = {}, method = 'GET', noRefetch = false) {
    wx.request({
      url: api.url,
      method: method,
      data: data,
      header: {
        Authorization: wx.getStorageSync('token');
      },
      success: (res) => {
        const code = res.statusCode
          if (code === 403) {
            if (!noRefetch) {
              _refetch(
                url,
                resolve,
                reject,
                data,
                method
              )
            }
          }
        }
    })
  }
  _refetch(...param) {
    getTokenFromServer((token) => {
      this._request(...param, true);
    });
  }

Author


Contributing

Contributions, issues and feature requests are welcome!Feel free to check issues page. You can also take a look at the contributing guide.

Show your support

Give a if this project helped you!

Thanks

Thanks for the explanation of node. js course given by the teacher ``, whose rigor of the course and exception handling of the project are all worthy of learning, which enabled me to acquire a lot of knowledge of Node and make my understanding of javascript to a higher level.Thanks!!

Here are the coruse of the project :course

License

Copyright 2020 . This project is MIT licensed.