Koa 是一個新的 web 框架,由 Express 幕後的原班人馬打造, 致力於成爲 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。
Koa與Express風格相似,不一樣在於默認異步解決方案和採用洋蔥圈模型的中間件。javascript
Koa沒有綁定任何中間件,簡單的同時也缺失了不少Web程序基礎的功能,如今咱們實現這些基礎的功能:
css
實現路由功能咱們使用到三個中間件,分別是koa-router
、koa-body
、koa-parameter
:html
koa-router
來實現最基礎的路由功能,將不一樣的url分發到相應的處理函數中;koa-body
對post請求的參數進行處理,將處理結果解析到ctx.request.body
中,koa-body
也可以處理上傳文件,文件會被解析到ctx.request.files
中;koa-parameter
對傳參進行校驗,get請求會對query
進行校驗,post請求則對body
進行校驗,校驗方法基於parameter。在server/index.js
中引用中間件:java
const Koa = require('koa') const app = new Koa() app.use(require('koa-body')({ multipart: true, formidable: { maxFileSize: 200*1024*1024 // 設置上傳文件大小最大限制,默認2M } })) require('koa-parameter')(app) app.use(require('./api')) app.listen(3000)
在server/api.js
中分發請求進行相應的處理:node
const fs = require('fs') const path = require('path') const Router = require('koa-router') const api = new Router({ prefix: '/api' }) api.post('/test', ctx => { // 使用koa-parameter對參數進行校驗 ctx.verifyParams({ name: { type: "string", required: true } }) // koa-body會將參數解析到ctx.request.body ctx.body = ctx.request.body }) api.post('/upload', ctx => { // 文件在ctx.request.files中以對象的形式保存,若是多個文件的key相同,則value是一個File對象組成的數組,結構{ key: <File|File[]>value } Object.keys(ctx.request.files).forEach(key => { const file = ctx.request.files[key] const reader = fs.createReadStream(file.path) const upStream = fs.createWriteStream(path.join(__dirname, '../dist/' + file.name)) reader.pipe(upStream) }) ctx.body = 'upload success' }) module.exports = api.routes()
執行命令node ./server/index.js
,運行程序,使用postman訪問路由,訪問/api/test
時,若是沒有參數name,狀態碼爲422,提示Validation Failed
git
而咱們帶上參數name,返回結果爲咱們請求的參數
github
使用postman模擬文件上傳,調用/api/upload
接口,上傳成功顯示upload success,咱們項目中的dist文件夾也多出了上傳的文件(dist文件夾須要先建立,否則程序會報錯)。
web
爲了方便咱們調試程序,咱們使用nodemon
啓動程序,首先運行yarn run nodemon --dev
,而後在package.json
中添加命令"dev": "nodemon ./server/index.js",以後咱們啓動程序只須要運行yarn run dev
便可,若是項目進行了修改,程序自動會自動從新運行。mongodb
咱們使用koa-static
來實現靜態資源的訪問;生成頁面通常會使用koa-views
+相應的模板引擎的方式來實現,可是我準備使用atr-tempate
來生成頁面,根據官網的說明咱們使用koa-art-template
便可:數據庫
const static = require('koa-static') app.use(static(path.resolve(__dirname, '../dist'))) const render = require('koa-art-template') render(app, { root: path.join(__dirname, 'view'), extname: '.art', debug: process.env.NODE_ENV !== 'production' })
新建頁面和樣式文件,而且添加路由:
<!-- server/view/index.art --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{title}}</title> </head> <body> <div id="app"> Hello World </div> <link rel="stylesheet" href="style.css"> </body> </html>
/* dist/style.css */ #app { color: red; font-size: 24px; }
// server/routes.js const Router = require('koa-router') const page = new Router() page.get('/index',async ctx => { await ctx.render('index', { title: 'Hello' }) }) module.exports = page.routes()
數據庫用於持久化保存數據,服務端的開發每每離不開數據庫的使用。MongoDB 是一個基於分佈式文件存儲的開源數據庫系統。在node.js
中咱們能夠經過mongoose
操做MongoDB數據庫。
首先咱們參考菜鳥教程安裝好MongoDB數據庫,而後爲項目添加mongoose
。
在項目中操做數據庫,第一步先鏈接數據庫,新建dbconnect.js文件:
const mongoose = require('mongoose') const db = require('../config/db') mongoose.connect(db.dbname, {useNewUrlParser: true, useUnifiedTopology: true}, err => { if (err) { log.fatal({msg: '[Mongoose] database connect failed!', err}) } else { console.log('[Mongoose] database connect success!') } }) module.exports = mongoose
Mongoose
中全部東西都是從Schema
,Schema
相似於MySQL中的數據結構,Schema
約束了MongoDB中每一個集合的字段結構,限制程序隨意修改數據庫;Model
是根據Schema
定義的結構編譯生成的高級構造函數,Model
的實例被稱爲Document
,Model
負責從底層MongoDB數據庫中建立和讀取文檔;關於Mongoose
的其餘概念,還有Schema
、Model
、Document
三者相關的接口能夠查看Mongoose官方文檔。
在項目中新建model/User.js
:
const { Schema, model } = require('mongoose') const UserSchema = new Schema({ username: { type: String, require: true, unique: true }, password: { type: String, require: true } }) module.exports = model('User', UserSchema)
在這個文件中,咱們定義了Schema
,而後生成Model
導出使用,要操做數據庫,咱們只須要使用Model
相應的方法便可。
Passport是Node.js的身份驗證中間件。 Passport極其靈活和模塊化,能夠絕不費力地放入任何基於Express的Web應用程序中。一套全面的策略支持使用用戶名和密碼,Facebook,Twitter等進行身份驗證。
咱們準備使用jwt進行身份驗證,暫時不使用第三方受權登陸,若是想要了解第三方受權登陸或者Passport更多的信息能夠閱讀官方文檔;在項目安裝koa-passport
和jwt對應的策略passport-jwt
,新建auth.js
:
const keys = require('../config/keys') const User = require('./model/User') const JwtStrategy = require('passport-jwt').Strategy const ExtractJwt = require('passport-jwt').ExtractJwt const opts = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: keys.secretOrkey } module.exports = passport => { passport.use(new JwtStrategy(opts, (jwt_payload, done) => { User.findById(jwt_payload._id).then(user => { if (user) { done(null, user) } else { done(null, false) } }) })) }
而後在項目中使用:
const passport = require('koa-passport') require('./auth')(passport) app.use(passport.initialize())
新建接口,並使用passport驗證身份信息:
api.post('/auth', passport.authenticate('jwt', { session: false }), async ctx => { ctx.body = 'auth' })
結果提示Unauthorized,證實身份驗證中間件已經生效。
接下來咱們來實現用戶的註冊和登陸,完善身份驗證的整個流程,註冊和登陸咱們會用到bcrypt
和jsonwebtoken
,bcrypt
用於密碼的加密和比較,jsonwebtoken
用於生成token;新增login和register接口:
api.post('/login', async ctx => { ctx.verifyParams({ username: { type: "string", required: true }, password: { type: "string", required: true }, }) const { username, password } = ctx.request.body const user = await User.findOne({ username }) if (user && bcrypt.compareSync(password, user.password)) { const token = jwt.sign({ _id: user._id, username }, keys.secretOrkey, { expiresIn: 3600 }) ctx.status = 200 ctx.body = { token: 'Bearer ' + token } } else if (user) { ctx.status = 500 ctx.body = { error: '密碼錯誤' } } else { ctx.status = 500 ctx.body = { error: '用戶名不存在' } } }) api.post('/register', async ctx => { const { username, password } = ctx.request.body const users = await User.find({ username }) if (users.length > 0) { ctx.status = 500 ctx.body = { error: '用戶名已被佔用' } } else { await User.create({ username, password: bcrypt.hashSync(password, keys.salt) }).then(user => { ctx.body = user }).catch(err => { ctx.status = 500 ctx.body = { error: err } }) } })
調用這兩個接口獲取用戶登陸的token,再次訪問auth接口,狀態碼200,正常返回訪問信息。
log4js
是Node.js的日誌工具,它提供豐富的日誌功能,詳細的功能的使用能夠查看log4js 徹底講解和官方文檔,咱們如今只實現最簡單的路由訪問日誌打印功能:
const log4js = require("log4js"); const logger = log4js.getLogger(); logger.level = "info"; app.use(async (ctx, next) => { const start = new Date() await next() const ms = new Date() - start logger.info(`${ctx.method} ${ctx.url} - ${ms}ms`) })
訪問接口控制檯會打印簡單的訪問日誌
至此一個簡單koa項目的基礎功能咱們就實現好了,具體代碼實現能夠查看項目地址。