[转载]Node.js框架之express与koa对比分析

转载自Node.js框架之express与koa对比分析


摘要: 提到Node.js开发,不得不提目前炙手可热的2大框架express和koa。Express诞生已有时日,是一个简洁而灵活的web开发框架,使用简单而功能强大。Koa相对更为年轻,是Express框架原班人马基于ES6新特性重新开发的敏捷开发框架,现在可谓风头正劲,大有赶超Express之势。 ...

提到Node.js开发,不得不提目前炙手可热的2大框架express和koa。Express诞生已有时日,是一个简洁而灵活的web开发框架,使用简单而功能强大。Koa相对更为年轻,是Express框架原班人马基于ES6新特性重新开发的敏捷开发框架,现在可谓风头正劲,大有赶超Express之势。

Express和koa都是服务端的开发框架,服务端开发的重点是对HTTP Request和HTTP Response两个对象的封装和处理,应用的生命周期维护以及视图的处理等。 以下将主要通过这些方面,对两者进行一个对比介绍,看看到底有什么区别。

Express主要基于Connect中间件框架,功能丰富,随取随用,并且框架自身封装了大量便利的功能,比如路由、视图处理等等。而koa主要基于co中间件框架,框架自身并没集成太多功能,大部分功能需要用户自行require中间件去解决,但是由于其基于ES6 generator特性的中间件机制,解决了长期诟病的“callback hell”和麻烦的错误处理的问题,大受开发者欢迎。

Express和koa初印象

先来一个Hello World,各自认识一下吧

//Express
var express = require('express')
var app = express()  //创建一个APP实例

//建一个项目根目录的get请求路由,回调方法中直接输出字符串Hello World!
app.get('/', function (req, res) {
    res.send('Hello World!')
});

//监听端口,启动服务
app.listen(3000);

//Koa
var koa = require('koa');
var route = require('koa-route');  //koa默认没有集成route功能,引入中间件

var app = koa();  //创建一个APP实例

//建一个项目根目录的get请求路由,回调方法中直接输出字符串Hello World!,就是挂载一个中间件
app.use(route.get('/', function *(){
    this.body = 'Hello World';
}));

//监听端口,启动服务
app.listen(3000);

可以看出来,两者创建一个基础的Web服务都非常简单,可以说几行代码就解决了问题。两者的写法也基本相同,最大的区别是路由处理Express是自身集成的,而koa需要引入中间件。以下是Koa官方文档对于两者特性的一个对比:

重要功能对比介绍

Feature Koa Express Connect
Middleware Kernel
Routing
Sending Files
JSONP

通过后续的比较,大家其实可以看出,虽然koa看上去比express少集成了很多功能,但是使用起来其实基本一致,因为中间件非常丰富全面,需要什么require进来就行了(不一定要像express那样先帮你require好),使用起来反而更加灵活。

应用生命周期和上下文

我们在项目过程中,经常需要用到在整个应用生命周期中共享的配置和数据对象,比如服务URL、是否启用某个功能特性、接口配置、当前登录用户数据等等。属于比较基础的功能,两者都非常方便,koa的application context感觉使用起来更方便一点。

//Express
//共享配置,express提供了很多便利的方法
app.set('enableCache', true)
app.get('enableCache')//true

app.disable('cache')
app.disabled('cache')//true

app.enable('cache')
app.enabled('cache')//true

//应用共享数据:app.locals
app.locals.user = {name:"Samoay", id:1234};

请求 HTTP Request

服务器端需要进行什么处理,怎么处理以及处理的参数都依赖客户端发送的请求,两个框架都封装了HTTP Request对象,便于对这一部分进行处理。以下主要举例说明下对请求参数的处理,其它例如头信息、Cookie等请参考官方文档。两者除了写法上稍有区别,没太大区别。GET参数都可以直接通过Request对象获取,POST参数都需要引入中间件先parse,再取值。

// Express
// 获取QueryString参数
// GET /shoes?order=desc&shoe[color]=blue
req.query.order
// => "desc"

req.query.shoe.color
// => "blue"

// 通过路由获取Restful风格的URL参数
app.get('/user/:id?', function userIdHandler(req, res) {
    console.log(req.params.id);
    res.send('GET');
})

//获取POST数据:需要body-parser中间件
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/', function (req, res) {
    console.log(req.body);
    res.json(req.body);
})

// 获取QueryString参数
// GET /?action=delete&id=1234
this.request.query
// => { action: 'delete', id: '1234' }

// 通过路由获取Restful风格的URL参数
var route = require('koa-route');
app.use(route.get('/post/:id', function *(id){
    console.log(id);
    // => 1234
}));

// 获取POST数据:需要co-body中间件
// Content-Type: application/x-www-form-urlencoded
// title=Test&content=This+is+a+test+post
var parse = require('co-body');
app.use(route.post('/post/new', function *(){
    var post = yield parse(this.request);//this
    console.log(post);
    // => { title: 'Test', content: 'This is a test post' }
}));

路由Route

收到客户端的请求,服务需要通过识别请求的方法(HTTP Method: GET, POST, PUT...)和请求的具体路径(path)来进行不同的处理。这部分功能就是路由(Route)需要做的事情,说白了就是请求的分发,分发到不同的回调方法去处理。

// Express
// app.all表示对所有的路径和请求方式都要经过这些回调方法的处理,可以逗号方式传入多个
app.all('*', authentication, loadUser);
// 也可以多次调用
app.all('*', requireAuthentication)
app.all('*', loadUser);
// 也可以针对某具体路径下面的所有请求
app.all('/api/*', requireAuthentication);

// app.get GET方式的请求
app.get('/user/:id', function(req, res) {
    res.send('user ' + req.params.id);
});

// app.post  POST方式的请求
app.post('/user/create', function(req, res) {
    res.send('create new user');
});

这里需要说明2个问题,首先是app.get,在应用生命周期中也有一个app.get方法,用于获取项目配置。Express内部就是公用的一个方法,如果传入的只有1个参数就获取配置,2个参数就作为路由处理。其次是app.use('', cb)* 与app.all('', cb)* 的区别,前者是中间件方式,调用是有顺序的,不一定会执行到;后者是路由方式,肯定会执行到。


// Koa
// 和Express不同,koa需要先引入route中间件
var route = require('koa-route');

//引入中间件之后支持的写法差不多,只是路径传入route,然后把route作为中间件挂载到app
app.use(route.get('/', list));
app.use(route.get('/post/new', add));
app.use(route.get('/post/:id', show));
app.use(route.post('/post', create));

//链式写法
var router = require('koa-router')();

router.get('/', list)
      .get('/post/new', add)
      .get('/post/:id', show)
      .post('/post', create);

app.use(router.routes())
   .use(router.allowedMethods());

视图Views

Express框架自身集成了视图功能,提供了consolidate.js功能,可以是有几乎所有Javascript模板引擎,并提供了视图设置的便利方法。Koa需要引入co-views中间件,co-views也是基于consolidate.js,支持能力一样强大。

// Express
// 这只模板路径和默认的模板后缀
app.set('views', __dirname + '/tpls');
app.set('view engine', 'html');

//默认,express根据template的后缀自动选择模板
//引擎渲染,支持jade和ejs。如果不使用默认扩展名
app.engine(ext, callback)

app.engine('html', require('ejs').renderFile);

//如果模板引擎不支持(path, options, callback)
var engines = require('consolidate');
app.engine('html', engines.handlebars);
app.engine('tpl', engines.underscore);

app.get('list', function(res, req){
    res.render('list', {data});
});

//Koa
//需要引入co-views中间件
var views = require('co-views');

var render = views('tpls', {
    map: { html: 'swig' },//html后缀使用引擎
    default: "jade"//render不提供后缀名时
});

var userInfo = {
    name: 'tobi',
    species: 'ferret'
};

var html;
html = render('user', { user: userInfo });
html = render('user.jade', { user: userInfo });
html = render('user.ejs', { user: userInfo });

返回 HTTP Response

获取完请求参数、处理好了具体的请求、视图也准备就绪,下面就该返回给客户端了,那就是HTTP Response对象了。这部分也属于框架的基础部分,各种都做了封装实现,显著的区别是koa直接将输出绑定到了ctx.body属性上,另外输出JSON或JSONP需要引入中间件。

// Express
//输出普通的html
res.render('tplName', {data});

//输出JSON
res.jsonp({ user: 'Samoay' });
// => { "user": "Samoay" }

//输出JSONP   ?callback=foo
res.jsonp({ user: 'Samoay' });
// => foo({ "user": "Samoay" });

//res.send([body]);
res.send(new Buffer('whoop'));
res.send({ some: 'json' });
res.send('<p>some html</p>');

//设定HTTP Status状态码
res.status(200);

//koa直接set ctx的status和body
app.use(route.get('/post/update/:id', function *(id){
    this.status = 404;
    this.body = 'Page Not Found';
}));

var views = require('co-views');
var render = views('tpls', {
    default: "jade"//render不提供后缀名时
});
app.use(route.get('/post/:id', function *(id){
    var post = getPost(id);
    this.status = 200;//by default, optional
    this.body = yield render('user', post);
}));

//JSON
var json = require('koa-json');
app.use(route.get('/post/:id', function *(id){
    this.body = {id:1234, title:"Test post", content:"..."};
}));

中间件Middleware

对比了主要的几个框架功能方面的使用,其实区别最大,使用方式最不同的地方是在中间件的处理上。Express由于是在ES6特性之前的,中间件的基础原理还是callback方式的;而koa得益于generator特性和co框架(co会把所有generator的返回封装成为Promise对象),使得中间件的编写更加优雅。

// req 用于获取请求信息, ServerRequest 的实例
// res 用于响应处理结果, ServerResponse 的实例
// next() 函数用于将当前控制权转交给下一步处理,
//        如果给 next() 传递一个参数时,表示出错信息
var x = function (req, res, next) {

    // 对req和res进行必要的处理

    // 进入下一个中间件
    return next();

    // 传递错误信息到下一个中间件
    return next(err);

    // 直接输出,不再进入后面的中间件
    return res.send('show page');
};

// koa 一切都在ctx对象上+generator
app.use(function *(){
    this; // is the Context

    this.request; // is a koa Request
    this.response; // is a koa Response

    this.req;// is node js request
    this.res;// is node js response

    //不再进入后面的中间件, 回溯upstream
    return;
});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 1.简书 koa是由Express原班人马打造,致力于成为一个更小、更富有表现力、更健壮的Web框架。使用koa编...
    不去解释阅读 2,643评论 0 11
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,266评论 25 707
  • 看到标题,也许您会觉得奇怪,redux跟Koa以及Express并不是同一类别的框架,干嘛要拿来做类比。尽管,例如...
    Perkin_阅读 1,710评论 0 4
  • #玩卡不卡·每日一抽# 每一位都可以通过这张卡片觉察自己: 1、直觉他叫什么名字?皮皮 2、他几岁了? 5岁 3、...
    天音思语阅读 278评论 0 0
  • 爱情碰到不容易,所以有的人恋爱了一次就是一生,从此不离不弃。 爱情不容易碰到,所以有的人孤老了一生,看透了世间红尘...
    导演张升志阅读 163评论 0 0