先日からSequelize を使用して、単純に ORM で作成したモデルでの DB アクセス と,Express を絡めた RESTAPI の作成 を行いました。 今まで作成したものは、データのバリデーションができていないので DB に(内部で挿入される日付を除く項目が)空のレコードを量産できるようになっていました。 今回はモデルにデータのバリデーションの追加とエラーハンドリングをしてみたいと思います。
では本編
目次
参考資料
実行環境
バリデーションを組み込もう 現状 User
モデル
models\user.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 "use strict" ;module .exports = (sequelize, DataTypes ) => { const User = sequelize.define ( "User" , { name : DataTypes .STRING , age : DataTypes .INTEGER , }, {} ); User .associate = function (models ) { }; return User ; };
モデルを呼び出しレコードを追加するコード
src\index.js 1 2 3 4 5 const models = require ("../models" );models.User .create ().then ((User ) => { console .log ("Created" , JSON .stringify (User )); });
以上をnode src\index.js
で実行すると、Created: {"id":73,"updatedAt":"2019-10-19T02:02:05.957Z","createdAt":"2019-10-19T02:02:05.957Z"}
と表示が出ますね。 せっかくname
とage
を持っているのに、それらを持たないレコードが作れてしまいます。
それではバリデーションを組み込みます。問題の単純化のためカラムname
のみを対象にします。
とりあえず、空で入力されることを防止してみよう User
モデルを以下の通り書き換えます。
models\user.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 "use strict" ;module .exports = (sequelize, DataTypes ) => { const User = sequelize.define ( "User" , { name : { type : DataTypes .STRING , allowNull : false , }, age : DataTypes .INTEGER , }, {} ); User .associate = function (models ) { }; return User ; };
name オブジェクトの内容を 書き換えました。
models\user.js抜粋 1 2 3 4 5 6 name : DataTypes .STRING ,name :{ type :DataTypes .STRING , allowNull : false , },
以上の書き換えを行い、node src\index.js
で実行します。 おそらく何も表示されないで終了します。
コンソールから DB にアクセスしてテーブルの内容を確認もできますが、呼び出し側でエラーハンドリングしてみましょう。
エラーハンドリングしよう promise 対応しているので、.catch
節を追加しましょう。 以下のように書き換えします。
src\index.js 1 2 3 4 5 6 7 8 9 10 const models = require ("../models" );models.User .create () .then ((User ) => { console .log ("Created:" , JSON .stringify (User )); }) .catch ((error ) => { console .log ("ERROR処理" ); console .error (error); });
node src\index.js
で実行します。 「ERROR 処理」から始まる以下の表示が出ると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ERROR処理 { SequelizeValidationError: notNull Violation: User.name cannot be null -----中略----- at processImmediate [as _immediateCallback] (timers.js:745:5) name: 'SequelizeValidationError', errors: [ ValidationErrorItem { message: 'User.name cannot be null', type: 'notNull Violation', path: 'name', value: null, origin: 'CORE', instance: [Object], validatorKey: 'is_null', validatorName: null, validatorArgs: [] } ] }
適切に?エラー処理されたことがわかります。
空で入力されたときのメッセージを変更してみよう 標準で設定されているエラーメッセージ'User.name cannot be null',
を変更することができます。
User
モデルを以下の通り書き換えます。
models\user.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 "use strict" ;module .exports = (sequelize, DataTypes ) => { const User = sequelize.define ( "User" , { name : { type : DataTypes .STRING , allowNull : true , validate : { notNull : { msg : 'Please your "Name"' , }, }, }, age : DataTypes .INTEGER , }, {} ); User .associate = function (models ) { }; return User ; };
node src\index.js
で実行します。 「ERROR 処理」から始まる以下の表示が出ると思います。
1 2 3 4 5 6 7 8 9 10 11 errors: [ ValidationErrorItem { message: 'Please your "Name"', type: 'notNull Violation', path: 'name', value: null, origin: 'CORE', instance: [Object], validatorKey: 'is_null', validatorName: null, validatorArgs: [] } ] }
message の中身が指定した'Please your "Name"'
に書き換わっています。
別のバリデーションを試そう Sequelize - API Reference - Model definition の「Per-attribute validations」の項を見ると、30 種ほど、バリデーション項目が紹介されています。 試しに、name 要素を対象に名前で使用しそうなバリデーションを試してみましょう。
登録できない文字列としてXXXX
は使えない
文字数は、4 文字以上 8 文字以下
以上 2 つを設定してみます。User
モデルを以下の通り書き換えます。
models\user.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 "use strict" ;module .exports = (sequelize, DataTypes ) => { const User = sequelize.define ( "User" , { name : { type : DataTypes .STRING , allowNull : true , validate : { notNull : { msg : 'Please your "Name"' , }, notIn : [["XXXX" ]], len : [4 , 8 ], }, }, age : DataTypes .INTEGER , }, {} ); User .associate = function (models ) { }; return User ; };
呼び出し側を以下のように書き換えておきます。 それぞれ入力できないXXXX
、文字数が足りないYY
、文字数が多すぎるZZZZZZZZZZZZZZZ
を登録してみます。
src\index.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const models = require ("../models" );models.User .create ({ name : "XXXX" }) .then ((User ) => { console .log ("Created:" , JSON .stringify (User )); }) .catch ((error ) => { console .log ("ERROR処理" ); console .error (error); }); models.User .create ({ name : "YY" }) .then ((User ) => { console .log ("Created:" , JSON .stringify (User )); }) .catch ((error ) => { console .log ("ERROR処理" ); console .error (error); }); models.User .create ({ name : "ZZZZZZZZZZZZZZZ" }) .then ((User ) => { console .log ("Created:" , JSON .stringify (User )); }) .catch ((error ) => { console .log ("ERROR処理" ); console .error (error); });
3 種類登録するので上記でもいいですが以下のほうが、スマートな気がします。 エラーメッセージの量も多いので、message
だけ出力するように変えます。
src\index.js 1 2 3 4 5 6 7 8 9 10 ["XXXX" , "YY" , "ZZZZZZZZZZZZZZZ" ].map (async (arg) => { await models.User .create ({ name : arg }) .then ((User ) => { console .log ("Created:" , JSON .stringify (User )); }) .catch ((error ) => { console .log ("ERROR処理" ); console .error (error.message ); }); });
node src\index.js
で実行します。 「ERROR 処理」から始まる以下の表示が出ると思います。
1 2 3 4 5 6 ERROR処理 Validation error: Validation notIn on name failed ERROR処理 Validation error: Validation len on name failed ERROR処理 Validation error: Validation len on name failed
それぞれエラー処理されました。 文字数が「足りない」、「多すぎる」の場合、len で設定するとエラーメッセージは同じとなります。 対応するために、バリデーションを独自に定義してみます。
バリデーションを独自に定義しよう 以下のようにisEven
でバリデーションを定義してみました。 Sequelize が提供するバリデーションには min,max がありますが、only allow values
と記載があるとおり文字列には効かないものでした。
models\user.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 "use strict" ;module .exports = (sequelize, DataTypes ) => { const User = sequelize.define ( "User" , { name : { type : DataTypes .STRING , allowNull : false , validate : { notNull : { msg : 'Please your "Name"' , }, notIn : [["XXXX" ]], isEven (value, min = 4 , max = 8 ) { if (value.length < min) { throw new Error ("Validation isEven[minlength] on name failed" ); } if (value.length > max) { throw new Error ("Validation isEven[maxlength] on name failed" ); } }, }, }, age : DataTypes .INTEGER , }, {} ); User .associate = function (models ) { }; return User ; };
node src\index.js
で実行します。 以下のように文字数が足りないもの、多いもののエラーメッセージを切り分けることができました。
1 2 3 4 5 6 ERROR処理 Validation error: Validation notIn on name failed ERROR処理 Validation error: Validation isEven[minlength] on name failed ERROR処理 Validation error: Validation isEven[maxlength] on name failed
複合的なバリデーション 複数の要素へのバリデーションが行えます。 ここではここまで放置していた age 要素と複合した要素へのバリデーションを入れてみます。 追加する設定は以下の通り、
age 要素
複合要素
name がAAAAA
,age が18
以下を満たすことを禁止
上記 3 っつのバリデーションを追加して user.js を以下のように書き換えます。
models\user.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 "use strict" ;module .exports = (sequelize, DataTypes ) => { const User = sequelize.define ( "User" , { name : { type : DataTypes .STRING , allowNull : false , validate : { notNull : { msg : 'Please your "Name"' , }, notIn : [["XXXX" ]], isEven (value, min = 4 , max = 8 ) { if (value.length < min) { throw new Error ("Validation isEven[minlength] on name failed" ); } if (value.length > max) { throw new Error ("Validation isEven[maxlength] on name failed" ); } }, }, }, age : { type : DataTypes .INTEGER , allowNull : false , validate : { isInt : true , }, }, }, { validate : { compositeValidate ( ) { if (this .name == "AAAAA" && this .age <= 18 ) { throw new Error ( "Validation compositeValidate() on name and age failed" ); } }, }, } ); User .associate = function (models ) { }; return User ; };
呼び出し側も書き換えます。
src\index.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const models = require ("../models" );[ { name : "AAAAA" , age : "16" }, { name : "AAAAA" , age : "19" }, { name : "BBBBB" , age : "16" }, ].map (async (arg) => { await models.User .create (arg) .then ((User ) => { console .log ("Created:" , JSON .stringify (User )); }) .catch ((error ) => { console .log ("ERROR処理" ); console .error (error.message ); }); });
node src\index.js
で実行します。 以下のように name がAAAAA
,age が18
以下を満たす入力をブロックできました。 name がAAAAA
,age が16
でも、二つ目の条件を満たさない場合登録できます。
1 2 3 4 5 6 ERROR処理 Validation error: Validation compositeValidate() on name failed and age Executing (default): INSERT INTO `Users` (`id`,`name`,`age`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?); Executing (default): INSERT INTO `Users` (`id`,`name`,`age`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?); Created: {"id":81,"name":"AAAAA","age":"19","updatedAt":"2019-10-19T10:43:45.766Z","createdAt":"2019-10-19T10:43:45.766Z"} Created: {"id":82,"name":"BBBBB","age":"16","updatedAt":"2019-10-19T10:43:45.766Z","createdAt":"2019-10-19T10:43:45.766Z"}
今回は、Sequelize で作成したモデルにバリデーションを仕込み、エラーハンドリングしてみました。 前回 Express と Sequelize で、RESTAPI を作ってみましたが、空のデータを作ることが防止されていませんでした。 次回は、今回のバリデーションを反映して RESTAPI を完成させてみたいと思います。
ではでは。