20191102のNode.jsに関する記事は4件です。

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.0

Case1(GET & 桁チェック)

引数 必須
arg1
arg2 min:2 max:4
arg3 max:4
arg4 min:2
case1.js
const 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=1234567890

NGな例

?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.js
const 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.js
const 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=falsearg2が必須どちらかが真という条件に置き換えることができます(もっとスマートなやり方があれば教えて下さい)。

まとめ

今回は簡単なサンプルを例にとって紹介しましたが、 express-validator のようなモジュールがあると、組み合わせでたいていの要件を満たすことができてとても便利です。
Web API で入力チェックはとても大切ですが、あまり時間をかけたくないところでもあります。また、実装者が複数いれば実装の粒度も変わりやすいので、実装者に依存しにくくなるというのも大きなメリットでしょうか。

今回、使用したコードはGitHubで公開しています。
https://github.com/ponko2bunbun/express-validator-sample

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がReact+FirebaseでWebアプリを作成する ~複数の入力を取得してデータベースに反映させる~②

①の続きです。

①で作ったfirebaseのデータベースに、入力された値を登録するようにします。

ReactのstateとsetStateで入力値を受け取れるようにします。
例でstateでnameに空値' 'を入れておきます。

App.js
class 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は自動を選択。
すると下のような画面になります。
firebase database collection dekita.png

このコレクションに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.js
import 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;

すると↓のようなページができます。
「登録」をクリックしてテスト送信してみましょう。
database 山田太郎.png
反映できました。
database 山田太郎が追加された.png

同じ要領でstateにaddressを設けて、住所も同時に登録できるようにします。
getAddressメソッドでsetStateを行えるようにしましょう。

App.js
import 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;

これで入力箇所が2つできました。
明訓.png

登録を押すと…

明訓完了.png

できました!

ちなみ

下のようにnew Date()をaddすることで、登録日時も取得できています。

App.js
firestore.collection('users').add({
name: this.state.name,
address: this.state.address,
created_at: new Date(),

次回はデータの呼び出しをやってみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がReact+FirebaseでWebアプリを作成する② ~複数の入力を取得してデータベースに反映させる~

①の続きです。

①で作ったfirebaseのデータベースに、入力された値を登録するようにします。

ReactのstateとsetStateで入力値を受け取れるようにします。
例でstateでnameに空値' 'を入れておきます。

App.js
class 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は自動を選択。
すると下のような画面になります。
firebase database collection dekita.png

buttonを作って、このコレクションにnameの情報を送りましょう。
次のようになります。

App.js
import 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;

すると↓のようなページができます。
「登録」をクリックしてテスト送信してみましょう。
database 山田太郎.png
反映できました。
database 山田太郎が追加された.png

同じ要領でstateにaddressを設けて、住所も同時に登録できるようにします。
getAddressメソッドでsetStateを行えるようにしましょう。

App.js
import 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;

これで入力箇所が2つできました。
明訓.png

登録を押すと…

明訓完了.png

できました!

次回は画像のアップロードを投稿したいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む