PCがあれば何でもできる!

へっぽこアラサープログラマーが、覚えたての知識を得意げにお届けします

【Node.js+Express4.x】REST APIで、エラーの内容毎に異なるステータスコードとエラーコードを出力する

f:id:m_doi2:20160303170941p:plain

Node.jsでREST APIを作っていて、エラー時のレスポンスをどうしようかなぁ、とごにょごにょ考えていました。

できれば、

  • エラー内容毎に、レスポンスのステータスコードを切り分けたい
  • クライアント側でエラーの種類を判別するために、独自のエラーコードも出力したい
  • エラー内容がなんとなくわかるメッセージは出力したい
  • エラー内容毎に、ログに出力するか否かも切り分けたい(システム的なエラーでない限りログに出てほしくないので)

の4点はクリアしたいなと。

これって一般的な方法はあるんでしょうか?

調べてわからなかったので、自前で実装してみました。

ソースコードはこちらにアップしてます。
https://github.com/m-doi2/express-error-code

簡単に説明

環境

  • node v5.6.0
  • express v4.13.4

ディレクトリ構成

app.js
errors
  ├─── abstracts
  │      └── abstract-errors.js // 自作エラーの抽象クラス
  │
  ├─── originals
  │      └── ・・・ // 自作エラークラス
  │
  └─── expand-error.js
       // Errorクラスを拡張し、ステータスコード、エラーコード、
       // コンソール出力の有無のプロパティを追加する関数 

自作エラー作成のための抽象クラス

// ./errors/abstracts/abstract-errors.js
'use strict';

/**
 * オリジナルのErrorオブジェクトの抽象化クラス
 * @constructor
 * @param {string} [message] エラーメッセージ
 * @public
 */
class AbstractError extends Error {
  constructor(message) {
    super(message);
    // 自身のクラス名をエラー名に指定
    this.name = this.constructor.name;
  }
}

module.exports = AbstractError;

Errorオブジェクトは、nameプロパティにエラー名を持っています。
例えばJavaScript標準のRangeErrorだと、RangeErrorという文字列が入っています。
このnameプロパティに、this.constructor.nameで自身のクラス名を入れることによって、子クラスが毎回エラー名を定義しなくて良くなります。

自作エラークラスのサンプル

// ./errors/originals/original1-error.js
'use strict';

const AbstractError = require('../abstracts/abstract-error');

class Original1Error extends AbstractError {
  constructor() {
    super('オリジナル1のエラーです');
  }
}

module.exports = Original1Error;

エラーメッセージが毎回変わるものではない場合、メッセージの引数はなしにして、superに固定で入れたほうが良いと思います。

ついでに、404のエラーも作成

// ./errors/originals/route-not-found-error.js
'use strict';

const AbstractError = require('../abstracts/abstract-error');

class RouteNotFoundError extends AbstractError {
  constructor() {
    super('ページが見つかりませんでした');
  }
}

module.exports = RouteNotFoundError;

Error拡張用の関数を用意

// ./errors/expand-error.js
'use strict';

const AbstractError = require('./abstracts/abstract-error');

// [statusCode, errorCode, outputLog]
const definitions = {
  // 40X
  RouteNotFoundError: [404, '4001', false],

  // 標準エラー
  Error:              [500, '5000', false],
  SyntaxError:        [500, '5001', true],
  RangeError:         [500, '5002', true],

  // 自作のエラー
  Original1Error:     [500, '5601', false],
  Original2Error:     [500, '5602', true],

  // 不明
  Unknown:            [500, '5000', true],
};

/**
 * Errorオブジェクトを拡張し、以下のプロパティを追加する
 *  - {number} [statusCode] HTTPのステータスコード
 *  - {string} [errorCode] エラー識別用のエラーコード
 *  - {boolean} [outputLog] エラー内容をログに出力するか
 *
 * @param {Object} [err] 拡張するエラーオブジェクト
 * @return {Object} 拡張後のエラーオブジェクト
 * @public
 */
function expandError(err) {
  let def = definitions[err.name] || definitions['Unknown'];
  err.statusCode = def[0];
  err.errorCode  = def[1];
  err.outputLog  = def[2];
  return err;
}

module.exports = expandError;

definitionsにエラー毎の値を設定してきます。
配列の1つ目がステータスコード、2つ目がエラーコード、3つ目がログ出力の有無となっています。
後は、expandError関数にErrorオブジェクトを突っ込むだけです。

使い方

// app.js
'use strict';

const express = require('express');
const app = express();
const bodyParser = require('body-parser');

const expandError = require('./errors/expand-error');
const RouteNotFoundError = require('./errors/originals/route-not-found-error');
const Original1Error = require('./errors/originals/original1-error');
const Original2Error = require('./errors/originals/original2-error');

// POSTをjsonで受ける
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());


// エラー発生用Routes(throwされてもnextに送っても動作可)
app.get('/error_any', function(req, res, next){
  // 任意のエラー
  throw new Error('メッセージ');
});
app.get('/error_basic', function(req, res, next){
  // 標準のエラー
  throw new RangeError('メッセージ');
});
app.get('/error_original1', function(req, res, next){
  // 自作のエラー
  next(new Original1Error());
});
app.get('/error_original2', function(req, res, next){
  // 自作のエラー
  next(new Original2Error());
});


// 404
app.use(function(req, res, next) {
  // Routeに一致しない場合は、404用のエラー出力
  next(new RouteNotFoundError());
});
// エラー
app.use(function(err, req, res, next) {
  // 既存エラーにコードを付加するためにエラー拡張関数を呼び出す
  expandError(err);

  // コンソールにエラー内容出力
  if (err.outputLog) {
    console.error(err);
    console.error(err.stack);
  }

  // RESTでエラー出力
  res.status(err.statusCode).json({
    statusCode: err.statusCode,
    errorCode: err.errorCode,
    message: err.message,
  });
});


// HTTPサーバ起動
let server = app.listen(8080, function() {
  console.log(`Started on port ${server.address().port}`);
});

404では、自作したRouteNotFoundErrorをnextに送るだけにし、実際には最後のエラー処理ミドルウェアで処理しています。

エラー処理ミドルウェアでは、expandError関数でエラーオブジェクトを拡張後、ログ出力の必要性があれば出力、最後にRESTでレスポンスを返しています。

終わりに

正しい実装方法があれば、ぜひご教示ください!よろしくお願いします。