- 投稿日:2019-11-02T23:48:00+09:00
express-validator を使ってみたポイント
はじめに
以前、 Node.js + Express.js で Web API を開発した際、 入力チェックに express-validator を使いました。
express-validator は validator.js がベースになっており、 validation や sanitize ができる便利なモジュールですが、提供されている API は文字列に対する検証になるため、 JSON パラメータの型を明確にチェックしたい場合には少し注意が必要でした。
今回は使用した際のポイントなんかを自分の整理がてらメモとして残します。環境
OS: macOS Mojave 10.14.6
Node.js: 10.16.3
Express.js: 4.16.1
express-validator: 6.2.0Case1(GET & 桁チェック)
引数 必須 桁 arg1 ○ arg2 min:2 max:4 arg3 max:4 arg4 min:2 case1.jsconst express = require('express'); const router = express.Router(); const {check, validationResult} = require('express-validator'); router.get('/', validateParam(), async (req, res) => { const errors = validationResult(req); if(!errors.isEmpty()) { res.status(400).end(); return; } res.status(200).end(); }); function validateParam() { return [ check('arg1') .exists({checkFalsy: true}) .isString(), check('arg2') .optional({nullable: true}) .isInt() .isLength({min: 2, max: 4}), check('arg3') .optional({nullable: true}) .isInt() .isLength({min: undefined, max: 4}), check('arg4') .optional({nullable: true}) .isInt() .isLength({min: 2, max: undefined}) ]; } module.exports = router;OKな例
?arg1=hoge ?arg1=hoge&arg2=12 ?arg1=hoge&arg2=1234 ?arg1=hoge&arg3=12 ?arg1=hoge&arg3=1 ?arg1=hoge&arg3=1234 ?arg1=hoge&arg4=12 ?arg1=hoge&arg4=1234567890NGな例
?arg1=hoge&arg2=1 ?arg1=hoge&arg2=12345 ?arg1=hoge&arg3=12345 ?arg1=hoge&arg4=1解説
GET メソッドを例に桁チェックを行います。
桁はisLength()を使用してチェックできます。
さらに、オプションで min (最小値)と max (最大値)が指定できます。
OKな例とNGな例で、オプションで指定した min と max の境界値が効いているのがわかります。Case2(POST & 型チェック)
引数 必須 型 arg1 ○ 整数 arg2 文字列の整数 arg3 真偽値 case2.jsconst express = require('express'); const router = express.Router(); const {check, validationResult} = require('express-validator'); router.post('/', validateParam(), async (req, res) => { const errors = validationResult(req); if(!errors.isEmpty()) { res.status(400).end(); return; } res.status(200).end(); }); function validateParam() { return [ check('arg1') .exists({nullable: true}) .not().isString() .isInt(), check('arg2') .optional({nullable: true}) .isString() .isInt(), check('arg3') .optional({nullable: true}) .not().isString() .not().isInt() .isBoolean() ]; } module.exports = router;OKな例
{ arg1: 1, arg2: '1' } { arg1: 1, arg3: true }NGな例
{ arg1: 1, arg2: 'a' } { arg1: 1, arg2: 1 } { arg1: 1, arg3: 'true' } { arg1: 1, arg3: 1 }解説
POST メソッドを例に型チェックを行います。
冒頭に触れている通り、 express-validator の API は文字列に対する検証なので、少し工夫をします。
arg1 は整数なので、.not().isString()と.isInt()を組み合わせて文字列ではないかつ整数という指定になります。
arg2 のように文字列の整数に限定したい場合は、.isString()と.isInt()を組み合わせて文字列かつ整数という指定になります。
arg3 は真偽値ですが、文字列の'false', 'true'や整数の0, 1が真偽値として判定されないようそれぞれ.not().isString()と.not().isInt()という指定をしています。ケースによっては上記を真偽値として判定したいという場合もあるので、その場合は適宜指定を外せばOKです。Case3(POST & 条件付き必須)
引数 必須 型 arg1 ○ 真偽値 arg2 arg1=true の場合のみ必須 文字列 case3.jsconst express = require('express'); const router = express.Router(); const {check, oneOf, validationResult} = require('express-validator'); router.post('/', validateParam(), async (req, res) => { const errors = validationResult(req); if(!errors.isEmpty()) { res.status(400).end(); return; } res.status(200).end(); }); function validateParam() { return [ check('arg1') .exists({checkNull: true}) .not().isString() .not().isInt() .isBoolean(), check('arg2') .optional({nullable: true}) .isString(), oneOf( [ check('arg1') .custom((value) => value === false), check('arg2') .exists({checkNull: true}) ] ) ]; } module.exports = router;OKな例
{ arg1: false } { arg1: true, arg2: 'hoge' }NGな例
{ arg1: true }解説
POST メソッドを例に条件付き必須チェックを行います。
条件付き必須チェックを行う場合にはoneOf()が使用できます。
oneOf()は Validation Chain の配列で、いずれか1つでも真であれば検証OK(=すべて偽だった場合に検証NG)となります。これを利用すると、 arg2 のarg1=true の場合のみ必須という条件は、arg1=false と arg2が必須 のどちらかが真という条件に置き換えることができます(もっとスマートなやり方があれば教えて下さい)。まとめ
今回は簡単なサンプルを例にとって紹介しましたが、 express-validator のようなモジュールがあると、組み合わせでたいていの要件を満たすことができてとても便利です。
Web API で入力チェックはとても大切ですが、あまり時間をかけたくないところでもあります。また、実装者が複数いれば実装の粒度も変わりやすいので、実装者に依存しにくくなるというのも大きなメリットでしょうか。今回、使用したコードはGitHubで公開しています。
https://github.com/ponko2bunbun/express-validator-sample
- 投稿日:2019-11-02T19:19:50+09:00
【DynamoDB】LastEvaluatedKeyを使ってscanリクエストで全件取得
はじめに
RDBMSを使っていると、allとかで全件取得出来て特に気にする必要がないのですが、DynamoDBでは1MBまでのデータ量の件数のみしか取得が出来ないので、知っていないとあれ全件取得出来ていない...みたいなことになり私のように痛い目にあってしまうので、自分への戒めの意味も込めて残しておきます。
情報ソース:DynamoDB でのスキャンの使用
環境
- Nodejs
実装
const AWS = require('aws-sdk'); const DynamoDB = new AWS.DynamoDB.DocumentClient({region: "ap-northeast-1"}); const tableName = 'hoges'; module.exports.hello = async (event, context) => { try { // scan用のパラメーターをセット const params = { TableName: tableName } // scanで取得したデータを格納する空の配列を定義しておく let items = [] const scan = async () => { const result = await DynamoDB.scan(params).promise() items.push(...result.Items) // scanリクエストを行なった時にLastEvaluatedKeyがあれば、再帰的にリクエストを繰り返す if (result.LastEvaluatedKey){ params.ExclusiveStartKey = result.LastEvaluatedKey await scan() } } await scan() const count = items.length for (let i = 0; i < count; i++){ console.log(`id: ${items[i]['id']}`) } console.log(`Execution result: ${count}`) } catch (err) { console.error(`[Error]: ${JSON.stringify(err)}`) return err } }
- 投稿日:2019-11-02T18:20:34+09:00
初心者がReact+FirebaseでWebアプリを作成する ~複数の入力を取得してデータベースに反映させる~②
①の続きです。
①で作ったfirebaseのデータベースに、入力された値を登録するようにします。
ReactのstateとsetStateで入力値を受け取れるようにします。
例でstateでnameに空値' 'を入れておきます。App.jsclass App extends React.Component{ constructor(props){ super(props); this.state = { name: '', } }rerurnの中にTextFieldを設けて、onChangeでgetNameメソッドを発動させsetStateを行うようにします。
App.js//メソッド定義 getName(event) { this.setState({ name: event.target.value, }); };App.js//returnの中 <TextField label="名前" value= {this.state.Name} onChange={(event)=>{this.getName(event)}}/>これでTextFieldの値がstateのnameに代入されます。
それではbuttonを作って、firebaseのDatabaseに送信してみましょう。その前にデータの送り先を決めるため、
firebaseのDatabaseから「+コレクションの開始」をクリックして、コレクションを作成します。
コレクション名はとりあえず「users」として、ドキュメントIDは自動を選択。
すると下のような画面になります。
このコレクションにnameの情報を送りましょう。
App.js//returnの中 <Button onClick={this.addData}>登録</Button>App.js//メソッドの定義 addData(){ firestore.collection('users').add({ name: this.state.name, }).then(() => { this.setState({ name: '', }); })};ここまでをまとめると次のようになります。
App.jsimport React from 'react'; import './App.css'; import firebase from "firebase/app" import "firebase/auth" import "firebase/firestore" import { firestore } from './plugins/firebase'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; class App extends React.Component{ constructor(props){ super(props); this.state = { name: '', } //ここでbindでthisを束縛しないとエラーが起きます this.getName = this.getName.bind(this); this.addData = this.addData.bind(this); } getName(event) { this.setState({ name: event.target.value, }); }; addData(){ //usersの部分がコレクション名になります firestore.collection('users').add({ name: this.state.name, }).then(() => { this.setState({ name: '', }); })}; render(){ return( <div> <TextField label="名前" value= {this.state.name} onChange={(event)=>{this.getName(event)}}/> <Button onClick={this.addData}>登録</Button> </div> ); } } export default App;すると↓のようなページができます。
「登録」をクリックしてテスト送信してみましょう。
反映できました。
同じ要領でstateにaddressを設けて、住所も同時に登録できるようにします。
getAddressメソッドでsetStateを行えるようにしましょう。App.jsimport React from 'react'; import './App.css'; import firebase from "firebase/app" import "firebase/auth" import "firebase/firestore" import { firestore } from './plugins/firebase'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; import TextareaAutosize from '@material-ui/core/TextareaAutosize'; class App extends React.Component{ constructor(props){ super(props); this.state = { name: '', address: '', } this.getName = this.getName.bind(this); this.getAddress = this.getAddress.bind(this); this.addData = this.addData.bind(this); } getName(event) { this.setState({ name: event.target.value, }); }; getAddress(event) { this.setState({ address: event.target.value, }); }; addData(){ firestore.collection('users').add({ name: this.state.name, address: this.state.address, created_at: new Date(), }).then(() => { this.setState({ name: '', address: '', }); })}; render(){ return( <div> <form> <TextField label="名前" value= {this.state.name} onChange={(event)=>{this.getName(event)}}/> <br/> <TextareaAutosize aria-label="住所" rows={6} placeholder="住所" value= {this.state.address} onChange={(event)=>{this.getAddress(event)}} />; <br/> <Button onClick={this.addData}>登録</Button> </form> </div> ); } } export default App;登録を押すと…
できました!
ちなみ
下のようにnew Date()をaddすることで、登録日時も取得できています。
App.jsfirestore.collection('users').add({ name: this.state.name, address: this.state.address, created_at: new Date(),次回はデータの呼び出しをやってみたいと思います。
- 投稿日:2019-11-02T18:20:34+09:00
初心者がReact+FirebaseでWebアプリを作成する② ~複数の入力を取得してデータベースに反映させる~
①の続きです。
①で作ったfirebaseのデータベースに、入力された値を登録するようにします。
ReactのstateとsetStateで入力値を受け取れるようにします。
例でstateでnameに空値' 'を入れておきます。App.jsclass App extends React.Component{ constructor(props){ super(props); this.state = { name: '', } }TextFieldを設けて、onChangeでgetNameメソッドを発動させsetStateを行うようにします。
App.js//メソッド定義 getName(event) { this.setState({ name: event.target.value, }); };App.js//returnの中 <TextField label="名前" value= {this.state.Name} onChange={(event)=>{this.getName(event)}}/>これでTextFieldの値がstateのnameに代入されます。
データの送り先を決めるため、
firebaseのDatabaseから「+コレクションの開始」をクリックして、コレクションを作成します。
コレクション名はとりあえず「users」として、ドキュメントIDは自動を選択。
すると下のような画面になります。
buttonを作って、このコレクションにnameの情報を送りましょう。
次のようになります。App.jsimport React from 'react'; import './App.css'; import firebase from "firebase/app" import "firebase/auth" import "firebase/firestore" import { firestore } from './plugins/firebase'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; class App extends React.Component{ constructor(props){ super(props); this.state = { name: '', } //ここでbindでthisを束縛しないとエラーが起きます this.getName = this.getName.bind(this); this.addData = this.addData.bind(this); } getName(event) { this.setState({ name: event.target.value, }); }; //データベースに登録するためのメソッド。usersの部分がコレクション名になります addData(){ firestore.collection('users').add({ name: this.state.name, }).then(() => { this.setState({ name: '', }); })}; render(){ return( <div> <TextField label="名前" value= {this.state.name} onChange={(event)=>{this.getName(event)}}/> <Button onClick={this.addData}>登録</Button> </div> ); } } export default App;すると↓のようなページができます。
「登録」をクリックしてテスト送信してみましょう。
反映できました。
同じ要領でstateにaddressを設けて、住所も同時に登録できるようにします。
getAddressメソッドでsetStateを行えるようにしましょう。App.jsimport React from 'react'; import './App.css'; import firebase from "firebase/app" import "firebase/auth" import "firebase/firestore" import { firestore } from './plugins/firebase'; import Button from '@material-ui/core/Button'; import TextField from '@material-ui/core/TextField'; import TextareaAutosize from '@material-ui/core/TextareaAutosize'; class App extends React.Component{ constructor(props){ super(props); this.state = { name: '', address: '', } this.getName = this.getName.bind(this); this.getAddress = this.getAddress.bind(this); this.addData = this.addData.bind(this); } getName(event) { this.setState({ name: event.target.value, }); }; getAddress(event) { this.setState({ address: event.target.value, }); }; //new Date()で登録日時も記録します addData(){ firestore.collection('users').add({ name: this.state.name, address: this.state.address, created_at: new Date(), }).then(() => { this.setState({ name: '', address: '', }); })}; render(){ return( <div> <form> <TextField label="名前" value= {this.state.name} onChange={(event)=>{this.getName(event)}}/> <br/> <TextareaAutosize aria-label="住所" rows={6} placeholder="住所" value= {this.state.address} onChange={(event)=>{this.getAddress(event)}} />; <br/> <Button onClick={this.addData}>登録</Button> </form> </div> ); } } export default App;登録を押すと…
できました!
次回は画像のアップロードを投稿したいと思います。




