The best Node.js -Koa restfulAPI practice 💦💦💦
MIT License
Koa framework foundation for node.js, a tutorial for building the restfuAPI
API Docs Path: DOCS
test this api: test
# 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
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')
})
The middleware sends functions called by HTTP, one instance can define multiple middleware, and the middleware call always returns promise
app.use
register the middlewarectx
contentsnext
next 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
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
await Features:
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
//
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
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,
}
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
}
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
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}
,
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 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
}
})
}
}
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``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
}
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
}
:
header``body
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
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
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)
toJSON
sequelize
this.getDataValue()
toJSON(){
return {
content:this.getDataValue('content')
}
}
Model dataValues
__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);
});
}
Contributions, issues and feature requests are welcome!Feel free to check issues page. You can also take a look at the contributing guide.
Give a if this project helped you!
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
Copyright 2020 . This project is MIT licensed.