【Node.js+Express4.x】REST APIで、エラーの内容毎に異なるステータスコードとエラーコードを出力する
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でレスポンスを返しています。
終わりに
正しい実装方法があれば、ぜひご教示ください!よろしくお願いします。