Koa v1からv2へのアップデート


Koa v1.2.1で書かれたアプリケーションをKoa v2.0.0にアップデートしたのでそのログ的なものです。

v1からの大きな変更点として、middlewareのインターフェースがgenerator functionからasync/awaitへと変わっています。

環境の用意

Koa v2ではasync/awaitが使える環境が必要となります。現状Node.jsでasync/awaitを使うためには主に

  • Node.js v7系を使い --harmony-async-await オプションをつけて起動
  • Babelでトランスパイル&polyfill

という二種類の方法がありますが、ここでは後者のBabelを使う方法で進めていきます。

pluginとpolyfillのインストール
$ npm install babel-polyfill babel-plugin-transform-async-to-generator

既にクライアント側でBabelを使用していて、presetやpluginsの設定を分けたい場合はenvを設定します。

.babelrc
{
  "env": {
    "client": {
      "presets": [ "es2015", "react" ],
      "plugins": [
        "add-module-exports",
        "transform-class-properties",
        "transform-object-rest-spread",
        "transform-export-extensions"
      ]
    },
    "server": {
      "presets": [ "es2015" ],
      "plugins": [
        "transform-async-to-generator"
      ]
    }
  }
}

BABEL_ENVを指定した上でbabel-nodeを使って動かします。

scriptsに追加
"start": "BABEL_ENV=server babel-node index.js"

なおパフォーマンスなどの問題から、productionではbabel-nodeではなくフロントのコード同様トランスパイルしたものを使用することが推奨されています。

babel/example-node-server

production用にトランスパイル
"build": "BABEL_ENV=server babel index.js --out-file bundle.js",
"start:production": "node bundle.js"

Koa v2のインストール

v2ではクラスコンストラクタを返すようになったので対応します。

before
import koa from 'koa';
const app = koa();
after
import Koa from 'koa';
const app = new Koa();

migration

generator -> async/await or Promise

generator functionをasync/awaitに書き換えていきます。

before
app.use(function *(next) {
  try {
    yield next;
  } catch (err) {
    if (401 == err.status) {
      this.status = 401;
      this.set('WWW-Authenticate', 'Basic');
      this.body = 'authorization required';
    } else {
      this.throw(err);
    }
  }
});
async/await
app.use(async (ctx, next) => {
  try {
    await next;
  } catch (err) {
    if (401 == err.status) {
      ctx.status = 401;
      ctx.set('WWW-Authenticate', 'Basic');
      ctx.body = 'authorization required';
    } else {
      ctx.throw(err);
    }
  }
});

あるいはPromiseで書くこともできます。

promise
app.use((ctx, next) => {
  next().catch((err) => {
    if (401 == err.status) {
      ctx.status = 401;
      ctx.set('WWW-Authenticate', 'Basic');
      ctx.body = 'authorization required';
    } else {
      ctx.throw(err);
    }
  });
});

koa-convert

未対応のmiddlewareは、koa-convertを使ってgenerator functionをPromiseを使用したものに変換することができます。

before
import logger from 'koa-logger';
import serve from 'koa-static';

app.use(logger());
app.use(serve('.'));
after
import logger from 'koa-logger';
import serve from 'koa-static';
import convert from 'koa-convert';

app.use(convert(logger()));
app.use(convert(serve('.')));

さいごに

とりあえずconvertで囲ってしまえばどうにかなるのは楽でいいですね。

async/awaitのためだけにサーバーサイドでBabelを使用するのは、ビルドプロセスも複雑になるし正直あまり筋のいい方法とは言えないのでNode.js v7入れられるならそちらの方が良いでしょう。

ちなみにKoa v2の正式リリースはNode.js上でstableなasync/awaitが実装されたらとのことなので、Node.js v8が出る頃には出そうですね

FYI: フレームワークDL数比較

Koaよりhapi,restifyの方が多いようです。。。1

last month(2016/12/17)
express 7,942,528
restify 258,329
hapi 246,358
Koa 156,030
sails 67,201
Trails 10,423

Node.js Advent Calendar 2016 17日目の記事でした。


  1. Meteorはnpmで入らないためDL数判らず