Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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のインストール

$ npm i koa@2

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が出る頃には出そうですね:smiley:

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数判らず 

freee
スモールビジネスのバックオフィス業務をテクノロジーで自動化し、日本のスモールビジネスを元気にする
http://www.freee.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away