koa2纺腊,以前沒(méi)有接觸過(guò),只知道是express的原班人馬開(kāi)發(fā)的誓沸,在一些方面優(yōu)于express拜隧,又經(jīng)歷了一次從koa到koa2的升級(jí)趁仙,應(yīng)該說(shuō)是比較成熟的了。
根據(jù)現(xiàn)在的技術(shù)實(shí)現(xiàn)方案薇组,現(xiàn)在大部分的web服務(wù)基本都是前后端分離模式的律胀,所以koa2的讓web應(yīng)用開(kāi)發(fā)和api的使用更加簡(jiǎn)便優(yōu)勢(shì)貌矿,更值得我們?nèi)W(xué)習(xí)逛漫。
本文只簡(jiǎn)單介紹將環(huán)境搭建起來(lái),可以讓初次學(xué)習(xí)koa或者web應(yīng)用的同學(xué)能夠更快的看到學(xué)習(xí)成果克握,增加學(xué)習(xí)的自信心枷踏,其中涉及到的很多的知識(shí)和技術(shù)點(diǎn)旭蠕,我們后續(xù)補(bǔ)充,不在本文做過(guò)多的闡述
本文的目的就是做知識(shí)的普及佑稠,文中可能會(huì)有些代碼是從網(wǎng)絡(luò)中復(fù)制的旗芬,但是都是親手調(diào)試可運(yùn)行疮丛,保證代碼的正常運(yùn)行的
搭建環(huán)境
- 安裝node
這個(gè)不用想也不用考慮漱办,koa項(xiàng)目是基于node的娩井,上來(lái)什么都別說(shuō)似袁,先安裝node昙衅。但這里有個(gè)細(xì)節(jié)需要注意,node需要最低版本為7.6.0著瓶,主要是為了一些es6的語(yǔ)法支持材原。
brew install node
我這里是在mac環(huán)境上安裝的node季眷,由于本人習(xí)慣使用homebrew安裝子刮,如果不是mac環(huán)境的同學(xué),也可以直接到官網(wǎng)下載安裝包安裝葵孤。這個(gè)步驟比較簡(jiǎn)單橱赠,一般情況下看下官網(wǎng)的文檔介紹,可以直接搞定吓著。
node安裝完成后送挑,要查看下node的版本惕耕,有兩個(gè)目的诫肠,一是查看node是否安裝成功,再就是檢查是否滿足koa的版本需求谚殊。
node -v
npm -v
npm集成到node中了蛤铜,只要成功的安裝了node围肥,npm也會(huì)被成功安裝。
- 安裝koa
我們學(xué)習(xí)和應(yīng)用的是koa項(xiàng)目置尔,就先安裝上koa吧榜轿。
npm install koa --save - 安裝koa2項(xiàng)目生成器并創(chuàng)建項(xiàng)目
npm install koa-generator -g
koa2 bbs
這里的koa-generator朵锣,并不是官方的項(xiàng)目生成器猪勇,而是狼叔-桑世龍為我們貢獻(xiàn)的財(cái)富,在這里助析,我們感謝狼叔外冀。
koa2 bbs掀泳,這行代碼創(chuàng)建了基于koa2的項(xiàng)目员舵,并生成基本的項(xiàng)目架構(gòu)∽矗基本的項(xiàng)目架構(gòu)如下:
- 安裝依賴
項(xiàng)目也創(chuàng)建完了措近,但是只是完成了一個(gè)基本的骨架瞭郑,還沒(méi)有血肉,接下來(lái)就需要安裝項(xiàng)目的依賴了
cd bbs
npm install
這個(gè)步驟可能消耗的時(shí)間有點(diǎn)長(zhǎng)擒权,這個(gè)和網(wǎng)絡(luò)環(huán)境有關(guān)菜拓,由于網(wǎng)絡(luò)的原因笛厦,還有可能某些依賴會(huì)安裝失敗裳凸,這個(gè)時(shí)候姨谷,我們可以通過(guò)修改npm鏡像來(lái)減少由于網(wǎng)絡(luò)環(huán)境導(dǎo)致安裝依賴失敗的問(wèn)題。
具體可參考淘寶NPM鏡像瞎颗。
- 啟動(dòng)服務(wù)
npm start
npm可以啟動(dòng)服務(wù)哼拔,但不支持熱更新瓣颅,我們這里只是測(cè)試項(xiàng)目能否正常啟動(dòng)宫补,執(zhí)行,熱更新的內(nèi)容健民,我們后續(xù)再講荞雏。
服務(wù)正常啟動(dòng)之后平酿,瀏覽器輸入:localhost:3000,如果頁(yè)面中展示了如"Hello Koa 2"的內(nèi)容蜈彼,說(shuō)明項(xiàng)目搭建成功。
搭建項(xiàng)目
這里的搭建項(xiàng)目棍辕,不是前面的使用koa2初始化一個(gè)項(xiàng)目楚昭,而是把前面通過(guò)koa2初始化的項(xiàng)目補(bǔ)充營(yíng)養(yǎng)抚太,讓它豐滿起來(lái)昔案。
- 安裝sequelize
暫且不要關(guān)心sequelize是什么踏揣,以后會(huì)有專題細(xì)講,只需要了解一點(diǎn)就可以了:Sequelize是一個(gè)基于promise的nodejs ORM又谋,目前支持Postgres彰亥、mysql铃辖、SQLite和Microsoft SQL Server娇斩。它具有強(qiáng)大的事務(wù)支持犬第,關(guān)聯(lián)關(guān)系,讀取和復(fù)制等功能丰介。
npm install sequelize --save
- 安裝mysql哮幢、mysql2
項(xiàng)目需要mysql的數(shù)據(jù)庫(kù)支持
npm install mysql mysql2 --save
- 配置Sequelize的數(shù)據(jù)庫(kù)鏈接
在項(xiàng)目的根目錄下創(chuàng)建一個(gè)config目錄橙垢,config目錄中創(chuàng)建db.js,該文件主要用來(lái)創(chuàng)建mysql的數(shù)據(jù)庫(kù)鏈接的嗽元。
const Sequelize = require('sequelize');
const sequelize = new Sequelize('dbname','dbusername','password',{
host:'localhost',
dialect:'mysql',
operatorsAliases:false,
dialectOptions:{
//字符集
charset:'utf8mb4',
collate:'utf8mb4_unicode_ci',
supportBigNumbers: true,
bigNumberStrings: true
},
pool:{
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
timezone: '+08:00' //東八時(shí)區(qū)
});
module.exports = {
sequelize
};
這些代碼可以直接使用剂癌,只需要將代碼中實(shí)例化Sequelie對(duì)象語(yǔ)句中的dbname更改為你的數(shù)據(jù)庫(kù)名佩谷,dbusername更改為你的數(shù)據(jù)庫(kù)用戶名琳要,passoword更改為你的數(shù)據(jù)庫(kù)密碼秤茅,其中數(shù)據(jù)庫(kù)名和數(shù)據(jù)庫(kù)用戶名不能為空框喳,密碼可以為空五垮,為空時(shí)則為空的字符串就可以了。
const sequelize = new Sequelize('bbs','root','',{
……
-
創(chuàng)建schema润绎、modules莉撇、controllers
3個(gè)目錄下分別創(chuàng)建article.js棍郎。
schema:數(shù)據(jù)表模型實(shí)例
modules:實(shí)體模型
controllers:控制器
目錄結(jié)構(gòu) - schema數(shù)據(jù)表模型
在schema目錄下新建一個(gè)article.js文件涂佃,該文件的主要作用就是建立與數(shù)據(jù)表的對(duì)應(yīng)關(guān)系,也可以理解為代碼的建表汽抚。
首先來(lái)分析表結(jié)構(gòu):
字段 | 說(shuō)明 | 是否必填 |
---|---|---|
id | 文章自增ID殊橙,主鍵 | 否,自動(dòng)填的 |
title | 文章標(biāo)題 | 是 |
author | 作者 | 是 |
content | 文章內(nèi)容 | 是 |
category | 文章分類 | 是 |
分析表結(jié)構(gòu)季研,主要是為了用來(lái)建表的誉察,表結(jié)構(gòu)持偏,是我們根據(jù)實(shí)體的關(guān)系抽象出來(lái)的實(shí)體關(guān)系鸿秆,根據(jù)這些關(guān)系建立表,將實(shí)體的關(guān)系存儲(chǔ)到表中卿叽。建表有兩種方式:一是使用mysql數(shù)據(jù)庫(kù)的命令行工具或者UI工具建表考婴,再就是使用我們前面介紹的Sequelize沥阱,讓程序去創(chuàng)建表。
使用mysql工具建表方式:
DROP TABLE IF EXISTS `article`;
CREATE TABLE `article` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`author` varchar(255) NOT NULL,
`content` varchar(255) NOT NULL,
`category` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
這是使用sql語(yǔ)句創(chuàng)建表的方式,我們看另一種借助Sequelize建表方式(Sequelize不光是能建表蛮寂,還能做數(shù)據(jù)庫(kù)的管理)易茬;
我們剛才在schema目錄下的article.js用來(lái)創(chuàng)建數(shù)據(jù)表模型的,也可以理解為創(chuàng)建一張數(shù)據(jù)表骄恶,代碼如下:
const moment = require("moment");
module.exports = function(sequelize,DataTypes){
return sequelize.define('article',{
id:{
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: true,
autoIncrement: true
},
//文章標(biāo)題
title:{
type: DataTypes.STRING,
allowNull: false,
field: 'title'
},
//作者
author:{
type: DataTypes.STRING,
allowNull: false,
field: 'author'
},
//內(nèi)容
content:{
type: DataTypes.STRING,
allowNull: false,
field:'content'
},
//文章分類
category:{
type: DataTypes.STRING,
allowNull: false,
field: 'category'
},
// 創(chuàng)建時(shí)間
createdAt:{
type: DataTypes.DATE
},
// 更新時(shí)間
updatedAt:{
type: DataTypes.DATE
}
}
},{
/**
* 如果為true僧鲁,則表示名稱和model相同寞秃,即user
* 如果為fasle春寿,mysql創(chuàng)建的表名稱會(huì)是復(fù)數(shù)忽孽,即users
* 如果指定的表名稱本身就是復(fù)數(shù)兄一,則形式不變
*/
freezeTableName: true
});
}
- 模型應(yīng)用出革、使用
在項(xiàng)目中modules目錄下創(chuàng)建article.js文件蹋盆,為文章表,該文件為文章的實(shí)例楞抡。
// 引入mysql的配置文件
const db = require('../config/db');
// 引入sequelize對(duì)象
const Sequelize = db.sequelize;
// 引入數(shù)據(jù)表模型
const Article = Sequelize.import('../schema/article');
Article.sync({force: false}); //自動(dòng)創(chuàng)建表
class ArticleModel {
/**
* 創(chuàng)建文章模型
* @param data
* @returns {Promise<*>}
*/
static async createArticle(data){
return await Article.create({
title: data.title, //標(biāo)題
author: data.author, //作者
content: data.content, //文章內(nèi)容
category: data.category //文章分類
});
}
/**
* 查詢文章的詳情
* @param id 文章ID
* @returns {Promise<Model>}
*/
static async getArticleDetail(id){
return await Article.findOne({
where:{
id
}
});
}
}
module.exports = ArticleModel;
- controller 控制器
控制器的主要作用為功能的處理,項(xiàng)目中controller目錄下創(chuàng)建article.js竞慢,代碼如下:
const ArticleModel = require("../modules/article");
class articleController {
/**
* 創(chuàng)建文章
* @param ctx
* @returns {Promise.<void>}
*/
static async create(ctx){
//接收客服端
let req = ctx.request.body;
if(req.title && req.author && req.content && req.category){
try{
//創(chuàng)建文章模型
const ret = await ArticleModel.createArticle(req);
//使用剛剛創(chuàng)建的文章ID查詢文章詳情筹煮,且返回文章詳情信息
const data = await ArticleModel.getArticleDetail(ret.id);
ctx.response.status = 200;
ctx.body = {
code: 200,
msg: '創(chuàng)建文章成功',
data
}
}catch(err){
ctx.response.status = 412;
ctx.body = {
code: 412,
msg: '創(chuàng)建文章失敗',
data: err
}
}
}else {
ctx.response.status = 416;
ctx.body = {
code: 200,
msg: '參數(shù)不齊全'
}
}
}
/**
* 獲取文章詳情
* @param ctx
* @returns {Promise.<void>}
*/
static async detail(ctx){
let id = ctx.params.id;
if(id){
try{
// 查詢文章詳情模型
let data = await ArticleModel.getArticleDetail(id);
ctx.response.status = 200;
ctx.body = {
code: 200,
msg: '查詢成功',
data
}
}catch(err){
ctx.response.status = 412;
ctx.body = {
code: 412,
msg: '查詢失敗',
data
}
}
}else {
ctx.response.status = 416;
ctx.body = {
code: 416,
msg: '文章ID必須傳'
}
}
}
}
module.exports = articleController;
- 路由
路由本冲,也可以簡(jiǎn)單理解為路徑劫扒,主要是作為請(qǐng)求的url沟饥,請(qǐng)求的路徑來(lái)處理一些請(qǐng)求贤旷,返回?cái)?shù)據(jù)。一般情況下性昭,基于node的項(xiàng)目,路由都是在一個(gè)叫做routes的目錄下面萧求。
const Router = require('koa-router');
const ArtileController = require('../controllers/article');
const router = new Router({
prefix: '/api/v1'
});
/**
* 文章接口
*/
//創(chuàng)建文章
router.post('/article/create',ArtileController.create);
//獲取文章詳情
router.get('/article/:id',ArtileController.detail)
module.exports = router
- 啟動(dòng)服務(wù)
這里主要測(cè)試服務(wù)能否正常啟動(dòng)夸政,能否正常運(yùn)行榴徐。
npm start
如果啟動(dòng)過(guò)程中出現(xiàn)下面的結(jié)果坑资,說(shuō)明服務(wù)啟動(dòng)成功
? bbs npm start
> bbs@0.1.0 start /usr/local/var/www/koa/bbs
> node bin/www
koa deprecated Support for generators will be removed in v3. See the documentation for examples of how to convert old middleware https://github.com/koajs/koa/blob/master/docs/migration.md app.js:20:5
Ignoring invalid configuration option passed to Connection: collate. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration options to a Connection
Ignoring invalid configuration option passed to Connection: collate. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration options to a Connection
Executing (default): CREATE TABLE IF NOT EXISTS `article` (`id` INTEGER auto_increment , `title` VARCHAR(255) NOT NULL, `author` VARCHAR(255) NOT NULL, `content` VARCHAR(255) NOT NULL, `category` VARCHAR(255) NOT NULL, `createdAt` DATETIME, `updatedAt` DATETIME, PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `article`
接下來(lái)仿便,就可以測(cè)試接口了嗽仪。
我使用的測(cè)試接口工具為postman柒莉,以前postman好像是作為瀏覽器的一個(gè)插件存在的兢孝,現(xiàn)在不作為瀏覽器的插件了,直接做成了應(yīng)用了哨颂,可以直接從postmna官網(wǎng)下載相种。 官網(wǎng)地址https://www.getpostman.com/,然后我們根據(jù)我們的系統(tǒng)平臺(tái)來(lái)選擇合適的版本下載就可以了寝并。
- 解決跨域
跨域是web開(kāi)發(fā)中不可避免的一個(gè)必須要解決的問(wèn)題了衬潦《频海跨域問(wèn)題,主要是要解決服務(wù)器端的通信問(wèn)題驾锰。在node的開(kāi)發(fā)中椭豫,只需要實(shí)現(xiàn)一個(gè)CORS標(biāo)準(zhǔn)就可以了赏酥。
npm install koa-cors --save
然后在根目錄下的app.js加入koa-cors的引用:
const cors = require('koa-cors')
app.use(cors()) //使用cors
然后重新啟動(dòng)服務(wù)裸扶。到這里為止低淡,還不能支持熱更新又固,現(xiàn)在主要測(cè)試功能猪杭,暫且先這么手動(dòng)重啟吧餐塘,后面會(huì)有專題描述熱更新服務(wù)。
- 測(cè)試接口
前面我下載并且安裝好了postman皂吮,工具有了戒傻,代碼層面也準(zhǔn)備就緒税手,解決了跨域問(wèn)題,下面就可以測(cè)試接口是否可用了需纳。
測(cè)試接口芦倒,我們先看下我們的路由管理文件routes目錄下的index.js文件。
//創(chuàng)建文章
router.post('/article/create',ArtileController.create);
//獲取文章詳情
router.get('/article/:id',ArtileController.detail)
這里有2個(gè)文章相關(guān)的接口兵扬,一個(gè)是創(chuàng)建新文章,一個(gè)是獲取文章詳情口蝠。我們先來(lái)測(cè)試創(chuàng)建新的文章器钟。
看路由,創(chuàng)建新文章需要的請(qǐng)求方式為post妙蔗,url為/article/create傲霸,那我們就根據(jù)這些信息使用postman進(jìn)行測(cè)試吧。
這是我創(chuàng)建新文章的接口測(cè)試眉反,然后看測(cè)試結(jié)果:
運(yùn)行結(jié)果告訴我們創(chuàng)建新文章的接口已經(jīng)測(cè)試成功了昙啄,說(shuō)明這個(gè)接口可以走通了。如果我們現(xiàn)在是前后端分離的開(kāi)發(fā)模式禁漓,我們需要提供api的話跟衅,那么這個(gè)api就可以提供給前端同學(xué)使用了。
我們也可以從數(shù)據(jù)庫(kù)中驗(yàn)證一下執(zhí)行結(jié)果的正確性:
數(shù)據(jù)也成功插入到數(shù)據(jù)庫(kù)中了播歼,我們也更加驗(yàn)證了我們接口的正確性和可執(zhí)行性了,這個(gè)接口到此結(jié)束掰读。
然后看下一個(gè)獲取文章詳情的接口秘狞。需要使用get的請(qǐng)求方式,請(qǐng)求的URL為/article/:id蹈集。
總結(jié)
到現(xiàn)在為止烁试,我們基本已經(jīng)完成了從0開(kāi)始搭建一個(gè)koa項(xiàng)目,并完成一些簡(jiǎn)單的api接口的實(shí)現(xiàn)拢肆,當(dāng)然了减响,只是最基本的實(shí)現(xiàn),里面還有很多可優(yōu)化的空間郭怪,對(duì)于一個(gè)作為入門案例來(lái)說(shuō)支示,已經(jīng)夠了。對(duì)于有深入了解或?qū)W習(xí)的朋友鄙才,希望我們功能學(xué)習(xí)颂鸿,共同提高。