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のレスポンスはXMLとJSON形式が一般的です。(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で答えがあったんですが、どこにどう入れればいいのかが本当に分かりにくかったので誰かがこれを見て解決できたらと思います。

プログラミング未経験からでもAIスキルが身につくAidemy Premium




PythonやAIプログラミングを学ぶなら、オンライン制スクールのAidemy Premiumがおすすめです。
「機械学習・ディープラーニングに興味がある」
「AIをどのように活用するのだろう?」
「文系の私でもプログラミング学習を続けられるだろうか?」
少しでも気になることがございましたら、ぜひお気軽にAidemy Premiumの【オンライン無料相談会】にご参加いただき、お悩みをお聞かせください!