Aidemy Tech Blog

機械学習・ディープラーニング関連技術の活用事例や実装方法をまとめる、株式会社アイデミーの技術ブログです。

Expressで使うJoiによるデータバリデーションに「"context" must be an object」が出た時の解決方法

 

準備

Expressが提供するアプリケーション生成プログラムツールexpress-generatorというものがあります。

初めての方はこちらをさっと読んでみてください。

http://qiita.com/janus_wel/items/207672dc29e22fa2c343

http://expressjs.com/ja/starter/generator.html

express-generatorはExpress Web Appを作る時に必要なRoute、Controller、素材、View等や最低限のソースコードを自動生成してくれるすぐれものです。しかも

./node_modules/.bin/express ../app

コマンド一発で準備されます。実行するとこんな構造ができます。(ViewエンジンはデフォルトでJadeが指定されてます)


├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.jade
    ├── index.jade
    └── layout.jade

ここで

npm start

って実行するだけでもうAppサーバーが立ち上がります。いいですね。さらに、この構造はWeb APIを作るにも便利です。

Web AppとAPIの違いはやはりレスポンスですね。普段ウェブサイトにアクセスするとHTMLページデータが返されます。このページデータを読んでCSSや画像を読み込んだりしてますね。APIのレスポンスはXMLJSON形式が一般的です。(XMLの時代は終わったのでJSON基準で話します)。HTMLの変わりにJSONを返すようにすればAPIサーバーに変わります。

 

ちなみにJSONの送受信だけであれば、/views/publicもいらないので捨てていいです。捨てる場合、app.jsにあるrequire等削除するよう気をつけてください。

 

序章は以上で

Joiバリデーション

フロントだけではNG

はい、入力のバリデーションはブラウザ側だけではダメです。ブラウザのバリデーションはHTML弄れば素通りできるので意味がないです。cURLツールなどを使えばバリデーションがそもそもない状態です。こんな状態でSQLインジェクション文字列を受け取ってサニタイズしないで処理したら危ないですね。

ということで、express-validationjoiを用いて各種入力の確認を出来るようにしましょう。

npm i --save express-validation joi

で一気に追加できます。

 

サーバーへデータを渡す方法は

  • params
  • body
  • query
  • headers

があります。今回はbodyを主に書きたいと思います。

 

ではまずバリデーションを以下の通りにしたいと思います。


const testobj = {
	body: Joi.object({
		abc: Joi.string().required(),
		num: Joi.number().required(),
	}).required(),
};

そしてもともとある/route/index.jsのGETを書き換えます。


var express = require('express');
const validate = require('express-validation');
const Joi = require('joi');
var router = express.Router();

const testobj = {
	body: Joi.array().items(Joi.object({
		abc: Joi.string().required(),
		num: Joi.number().required(),
	})).required(),
};

/* GET home page. */
router.post('/', validate(testobj), function(req, res, next) {
  res.json(req.body);
});

module.exports = router;

 

通常複数の項目を一気に送信するのでObjectとして囲み、送信します。


// POST bodyの中身
{
  abc: "def",
  num: 123
}

Joiのちょっとした変なところ(?)

ですがオブジェクトの配列を送りたい場合もありますね。

以下のデータを送ったとします。

[
	{
		abc: "def",
		num: 123
	},
	{
		abc: "def",
		num: 123
	}
]

この場合だとexpress-validation/Joiが以下のようなエラーを吐き出します。


<!DOCTYPE html><html><head><title></title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><h1>&quot;context&quot; must be an object</h1><h2></h2><pre>Error: &quot;context&quot; must be an object
    at checkOptions (C:\Users\dummy\Desktop\test2\app\node_modules\joi\lib\types\any\index.js:89:19)
    at _validateWithOptions (C:\Users\dummy\Desktop\test2\app\node_modules\joi\lib\types\any\index.js:646:18)
    at root.validate (C:\Users\dummy\Desktop\test2\app\node_modules\joi\lib\index.js:121:23)
    at validate (C:\Users\dummy\Desktop\test2\app\node_modules\express-validation\lib\index.js:75:7)
    at C:\Users\dummy\Desktop\test2\app\node_modules\express-validation\lib\index.js:42:24
    at Array.forEach (native)
    at C:\Users\dummy\Desktop\test2\app\node_modules\express-validation\lib\index.js:39:55
    at Layer.handle [as handle_request] (C:\Users\dummy\Desktop\test2\app\node_modules\express\lib\router\layer.js:95:5)
    at next (C:\Users\dummy\Desktop\test2\app\node_modules\express\lib\router\route.js:137:13)
    at Route.dispatch (C:\Users\dummy\Desktop\test2\app\node_modules\express\lib\router\route.js:112:3)</pre></body></html>

ここでエラースタックに書いてある

"context" must be an object

にご注目ください。JoiはデフォルトでObjectを想定しているので却下されます。

 

この問題に結構長い間解決できずに悩んでいたのですが、

実はバリデーションに一行だけ、


const testobj = {
	options: { contextRequest: true }, // <-----これ
	body: Joi.array().items(Joi.object({
		abc: Joi.string().required(),
		num: Joi.number().required(),
	})).required(),
};

を追加すれば、問題なく送信できます。

 

https://github.com/AndrewKeig/express-validation/issues/36

GitHubにQ&Aで答えがあったんですが、どこにどう入れればいいのかが本当に分かりにくかったので誰かがこれを見て解決できたらと思います。