20190302のNode.jsに関する記事は8件です。

NERM (Node.js + Express + React + Material-UI) ベースなWebアプリ開発用のシンプルなビルド環境 2019年3月版

Node.js + Express で Web アプリを開発するのはもう一般的だと思いますが、ちょっと凝ったアプリを作成しようとすると、フロントエンドの UI で何を使おうか悩みますね。

個人的には React + Material-UI あたりが使いやすいと思います。(勝手に)名付けて NERM (Node.js + Express + React + Material-UI) 環境!で、この環境用にシンプルなビルド環境を用意してみました。

2017年に投稿した browserify+babelifyでReactアプリをトランスコンパイルできるシンプルなビルド環境を用意する が進化した2019年版です。Node.js ベースのWebアプリを気軽に作成したい場合に、スケルトンとして使っていただければ、嬉しいです。

準備

node と npm は利用できるよう準備しておいてください。私はWindows環境なので nodist を使っています。

まずは作業用のディレクトリ(フォルダ)を作成します。今回は "rtk-nerm" としました。そして src, public のサブディレクトリを作成しておきます。

シンプルな React + Material-UI サンプル

今回も非常にシンプルなサンプルを用意しましょう。

src ディレクトリに App.jsx ファイルを用意します。内容は以下をコピペしてください。

src/App.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import Button from '@material-ui/core/Button';

function App() {
  return (
    <Button variant="contained" color="primary">
      Hello World
    </Button>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('react-root')
);

また public ディレクトリに index.html ファイルを用意します。内容は以下をコピペしてください。

public/index.html
<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
  <title>rtk-nerm</title>
</head>
<body>
  <div id="react-root"></div>
  <script type="text/javascript" src="./App.js"></script>
</body>
</html>

サンプルはこれで準備できましたので、いよいよ環境の構築を始めましょう。

トランスコンパイル環境の構築

作業ディレクトリに package.json ファイルを用意します。内容は以下をコピペしてください。

package.json
{
  "name": "rtk-nerm",
  "version": "1.0.0",
  "description": "Simple transcode environment for NERM (Node.js + Express + React + Material-UI) application.",
  "scripts": {
    "build": "browserify -t [ babelify --presets [ @babel/preset-env @babel/preset-react ] ] src/App.jsx -o public/App.js",
    "start": "node Server.js"
  },
  "engines": { "node": ">= 8" },
  "author": "yamachan / Toshio Yamashita",
  "license": "MIT"
}

※ scripts 要素以外は自由に変更してok

そして以下のコマンドで必要なパッケージを導入します。

npm install --save express
npm install --save react react-dom @material-ui/core @material-ui/icons
npm install --save-dev browserify babelify
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react

ちなみに私は以下のようなバージョンのモジュールが導入されました。

package.json
  "dependencies": {
    "@material-ui/core": "^3.9.2",
    "@material-ui/icons": "^3.0.2",
    "express": "^4.16.4",
    "react": "^16.8.3",
    "react-dom": "^16.8.3"
  },
  "devDependencies": {
    "@babel/core": "^7.3.4",
    "@babel/preset-env": "^7.3.4",
    "@babel/preset-react": "^7.0.0",
    "babelify": "^10.0.0",
    "browserify": "^16.2.3"
  }

以下のコマンドを実行して public/App.js ファイルが生成されれば ok です。

npm run build

Node.js + Express サーバーを起動

プロジェクトのルートディレクトリに Server.js ファイルを用意し、内容は以下をコピペしてください。

Server.js
const express = require("express");
const app = express();

app.use(express.static('public'));

let port = process.env.PORT || 3000;
app.listen(port);

以下のコマンドで Server.js コードを実行し、ローカルで Web サーバーを起動します。

npm start

ブラウザで http://localhost:3000/ にアクセスすれば、Material-UI を使った React ページが表示されます。

image.png

さあアプリ開発を開始しましょう

Server.js にはバックエンドのロジックを実装しましょう。こちらを更新した時には npm start で起動するWeb サーバーを再起動(終了して再実行)することを忘れずに。

src/App.jsx にはフロントエンドの UI を実装し、npm run build で public/App.js を更新しましょう。

それぞれ拡張し、自分なりの Web アプリケーションを作成してみてください。

IBM Cloud の Node.js 環境で動かしてみる

ついでにプロジェクトをIBM Cloud (Cloud Foundry) の Node.js 環境で動かしてみましょう。

IBM Cloudアカウントをお持ちでない方は、こちらからすぐに使える無料アカウント登録をしてください。

プロジェクトのルートディレクトリに .cfignore ファイルを用意し、内容は以下をコピペしてください。このファイルが無くても動作しますが、用意したほうが push 動作が軽くなります。

.cfignore
node_modules
src
README.md
.gitignore

さあ、IBM Cloud にログインして、アプリを push しましょう。(アプリ名 rtk-nerm はユニークなものに変更する必要があります)

アプリ名はWebサイトを公開した時のURLの一部になります。IBM Cloudの場合、https://<アプリ名>.mybluemix.netとなります。

cf login
cf push -m 128M rtk-nerm

【追記】package.json に start コマンドを記載したので -c "node Server.js" オプションは不要になりました

IBM Cloud ダッシュボードでアプリの開始を確認します。
image.png
「アプリURLにアクセス」リンクから、実際の表示を確認します。
image.png

関連ファイルのダウンロード

rtk-nerm というGitHubリポジトリに今回のファイルを置いておきましたので、面倒な方はそちらからダウンロード/clone してお使いください。

Enjoy!

Node.js でロジックを実装し、Express + React + Material-UI ベースで開発したWebアプリをビルドするための、できるだけシンプルな環境を構築してみました。これを出発点として自分なりのアプリやビルド環境を構築する助けになれば嬉しいです。

ではまた!

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

Firebase CloudFunctions, Realtime DatabaseでREST WebAPIを作る

前置き

外部からhttpsで接続出来るテスト用のWebAPIをとりあえず作成したいことがありました。固定のjsonを返却するだけのWebAPIを作成すれば良かったのですが、興味本位でRealtime Databaseも使って簡単なCRUD機能を提供するWebAPIを作りました。というよりもちょっと寄道して作ってしまいました:sweat_smile:
「ソースコードだけ見られれば十分」と思われる方々もいらっしゃると思いますが、ひととおり筆者が行った手順も含めて記事にしました。ソースコードはGitHubに公開しています。
※検証した実際のURLを掲載していますが削除する予定です。ご了承ください。

なぜFirebaseを選んだか

jsonファイルをホスティングすれば良いのですが「せっかくならCRUDくらい付いていると嬉しい」という欲が出てしまいました。そのため、ホスティング案を除外して無料で動的なプログラムが動作する環境として、すぐ浮かんだ3つを検討しました。

  1. Heroku
    スキルセットの観点からRailsを利用することになりますが、APIモードでプロジェクトを作成するにしても、少し大掛かりな印象がありました。ほんの少しCRUD出来れば満足なので今回は採用を見送りました。

  2. AWS
    API Gateway+Lambda+DynamoDBを利用することも検討しました。公式のサンプルにCloudFormationのymlもあり構築も楽できそうでした。一番興味はありましたが、API Gatewayの無料枠は12ヵ月の期限付きなので見送りました。他のサービスでも途中で規約が変わることがありますが現状で判断しました。

  3. Firebase
    モバイル開発でも利用していて上記2つのサービスよりも馴染みがあったことと、自動的に課金されない明示的な無料枠の安心感も手伝って採用することにしました。CloudFunctionやRealtime Databaseに興味があるわけではありませんでした・・・:dizzy_face:

消去法で選ばれてしまったFirebaseでありますが、気を取り直して前向きに作業を進めてます:grin::grin:

事前準備

  1. node.jsをインストール

    $ node -v
    v10.15.2
    
  2. npmをインストール

    $ npm -version
    6.8.0
    
  3. Firebase CLIをインストール

    npm install -g firebase-tools
    
    1. ログイン
      以下のコマンドを実行します。

      $ firebase login
      
    2. そのままEnterを押下

      ? Allow Firebase to collect anonymous CLI usage and error reporting information?
      (Y/n) 
      
    3. ブラウザが起動するのでログイン
      01.png

    4. 諸々承認
      ブラウザでは以下のような表示になっています。
      02.png
      コンソール上では以下のように表示されています。

      Waiting for authentication...
      
      ✔  Success! Logged in as devnokiyo@example.com
      

プロジェクトを作成する

プロジェクトを作成したいパスに移動して以下のコマンドを実行します。
対話式で質疑についても例として記載します。ご参考まで。

$ cd repos
$ firebase init functions
? Select a default Firebase project for this directory: [create a new project]
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
? Do you want to install dependencies with npm now? Yes
  1. Firebaseコンソールでプロジェクト作成 03.png
  2. Firebase CLIでプロジェクトを追加
    対話式で質疑についても例として記載します。ご参考まで。

    $ firebase use --add
    ? Which project do you want to add? hoge-rest
    ? What alias do you want to use for this project? (e.g. staging) hoge-rest
    

Realtime Databaseを準備する

  1. Realtime Databaseを作成
    Cloud Firestoreという新しいデータベースも用意されていますが、従来のRealtime Databaseを利用することにします。
    ※スクリーンショットを撮直したのでプロジェクト名が異なっています。 100.png
  2. セキュリティルールを設定

    今回は「ロックモードで開始」を設定します。
    スクリーンショット 2019-03-02 23.53.35.png

  3. テストデータをインポート
    データ取得用のファンクションの動作確認をするためテストデータを用意します。Firebaseコンソールの画像の赤い部分をクリックして「JSONをインポート」を選択します。
    04.png
    以下をインポートします。

    seed.json
    {
      "users": [
        {
          "user_id": "A0001",
          "user_name": "chiyo",
          "age": 9
        },
        {
          "user_id": "A0002",
          "user_name": "eru",
          "age": 5
        },
        {
          "user_id": "A0003",
          "user_name": "otome",
          "age": 13
        }
      ]
    }
    

    インポートするとデータが可視化されます。
    05.png

  4. インデックスを作成
    user_idをキーに検索するファンクションを作成するのでインデックスを作成しておきます。FirebaseコンソールのDatabaseでルールを選択します。以下を追加します。

    "users": {
      ".indexOn": ["user_id"]
    }
    

    Firebaseコンソール上では以下のようになります。
    06.png

ファンクションを作成してデプロイする

ようやく本題に入ります。ファンクションのソースコードはGitHubに公開しています。宜しければご覧ください。

  1. プロジェクトを作成したディレクトリに移動
    簡単に構成を確認します。ご察しと思われますがindex.jsにプログラムを書きます。

    ├── firebase.json
    └── functions
        ├── index.js
        ├── node_modules
        ├── package-lock.json
        └── package.json
    
  2. Usersを全件取得するファンクションを作成
    まずは最低限のnpmパッケージのみを利用して動作させてみます。

    index.js
    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    
    admin.initializeApp();
    exports.users = functions.https.onRequest((req, res) => {
      const ref = admin.database().ref('users');
      ref.once("value", function(data) {
        res.send(data);
      });
    });
    
  3. デプロイ
    functionsディレクトリに移動して、デプロイコマンドを実行します。

    $ cd functions
    $ ls
    index.js        package-lock.json
    node_modules        package.json
    $ firebase deploy --only functions
        :
        :
    === Deploying to 'hoge-rest'...
    
    i  deploying functions
    Running command: npm --prefix "$RESOURCE_DIR" run lint
        :
        :
    ✔  Deploy complete!
    
    Please note that it can take up to 30 seconds for your updated functions to propagate.
    Project Console: https://console.firebase.google.com/project/hoge-rest/overview
    

    上記のメッセージどおりデプロイに30秒ほど掛かります:hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand:
    余談ですが、筆者は「修正が反映されていない」と思ったことが何度もありました:sweat:

  4. ファンクションを実行

    1. トリガーを確認
      FirebaseコンソールのFunctions ダッシュボードより該当のファンクションのトリガーを確認します。 07.png
    2. ブラウザでURLにアクセス
      jsonでUsersの全件が返却されていることを確認します。 08.png

expressを導入してファンクションを書替える

WebAPIを機能追加するにあたりルーティングを体系的に記述したいのでexpressを導入します。

  1. npmパッケージをインストール

    functionsディレクトリに移動してnpmパッケージをインストールします。

    $ npm install express --save
    $ npm install body-parser --save
    
  2. ファンクションをexpress版に書換え

    index.js
    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    const express = require('express');
    const bodyParser = require('body-parser');
    
    const app = express();
    app.use(bodyParser.json());
    
    admin.initializeApp();
    
    app.get('/', (req, res) => {
      const ref = admin.database().ref('users');
      ref.once('value').then(data => {
        return res.send(data);
      })
      .catch(error => {
        res.status(404).send('No data available.');
      });
    });
    
    exports.users = functions.https.onRequest(app);
    
  3. ファンクションを実行
    先ほどの手順でファンクションをデプロイ・実行して問題ないことを確認します。

その他、CRUDのファンクションを作成

ここまででUserを全件を取得するファンクションを作成しました。続いて以下を作成します。
ここからはソースコードを掲載して簡単に動作確認する程度します。
必要に応じてカスタマイズしてください:robot:

  • 特定のUserを1件取得
  • Userを作成
  • Userを更新
  • Userを削除

特定のUserを1件取得

ソースコード

index.js
app.get('/:user_id', (req, res) => {
  const ref = admin.database().ref('users');
  ref.orderByChild('user_id').equalTo(req.params.user_id)
    .once('value').then(data => {
      return res.send(data);
    })
    .catch(error => {
      res.status(404).send('No data available.');
    });
});

確認

User_IdがA0001のユーザーのみ取得します。
09.png

Userを作成

ソースコード

index.js
app.post('/', (req, res) => {
  const ref = admin.database().ref('users');
  ref.push({                                  // ユニークキーも自動生成する
    user_id: req.body.user_id,
    user_name: req.body.user_name,
    age: Number(req.body.age)                 // NaNは考慮しないことにする
  }).then(data => {
    return res.status(201).send(data);
  })
  .catch(error => {
    res.status(400).send(error);      
  });
});

確認

新しいユーザー「Kurobee」を追加します。
余談ですがREST WebAPIの確認にはChromeアプリのAdvanced REST clientを気に入っています。
10.png

Userを取得して確認します。
11.png

Userを更新

ソースコード

index.js
app.put('/:id', (req, res) => {
  const ref = admin.database().ref('users');
  const updates = {};
  updates[req.params.id] = {
    user_id: req.body.user_id,
    user_name: req.body.user_name,
    age: Number(req.body.age)                 // NaNは考慮しないことにする
  };
  ref.update(updates).then(data => {
    return res.send(data);
  })
  .catch(error => {
    res.status(400).send(error);      
  });
});

確認

ユーザー「Kurobee」の年齢を「3」から「4」に更新します。
12.png

Userを取得して確認します。
13.png

Userを削除

ソースコード

index.js
app.delete('/:id', (req, res) => {
  const ref = admin.database().ref(`users/${req.params.id}`);
  ref.remove().then(data => {
    return res.send(data);
  })
  .catch(error => {
    res.status(400).send(error);      
  });
});

確認

ユーザー「Kurobee」を削除します。
14.png

Userを取得して確認します。
15.png

終わりに

冒頭でもお伝えしましたとおりjsonだけ返却するWebAPIがあれば十分でしたが、つい作成してしまいました。「FaaSやBaaSはサーバーレスで簡単」という触込みが多いですが、それぞれそれなりに独特のものがあると思っています。NoSQLにあまり触れていない筆者はRealtime Databaseに少し苦戦しましたが、簡易的なCRUD WebAPIにはなっていると思いますので、今後活用していくつもりです。

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

Firebase Cloud Functions, Realtime DatabaseでREST WebAPIを作る

前置き

外部からhttpsで接続出来るテスト用のWebAPIをとりあえず作成したいことがありました。固定のjsonを返却するだけのWebAPIを作成すれば良かったのですが、興味本位でRealtime Databaseも使って簡単なCRUD機能を提供するWebAPIを作りました。というよりもちょっと寄道して作ってしまいました:sweat_smile:
「ソースコードだけ見られれば十分」と思われる方々もいらっしゃると思いますが、ひととおり筆者が行った手順も含めて記事にしました。ソースコードはGitHubに公開しています。
※検証した実際のURLを掲載していますが削除する予定です。ご了承ください。

なぜFirebaseを選んだか

jsonファイルをホスティングすれば良いのですが「せっかくならCRUDくらい付いていると嬉しい」という欲が出てしまいました。そのため、ホスティング案を除外して無料で動的なプログラムが動作する環境として、すぐ浮かんだ3つを検討しました。

  1. Heroku
    スキルセットの観点からRailsを利用することになりますが、APIモードでプロジェクトを作成するにしても、少し大掛かりな印象がありました。ほんの少しCRUD出来れば満足なので今回は採用を見送りました。

  2. AWS
    API Gateway+Lambda+DynamoDBを利用することも検討しました。公式のサンプルにCloudFormationのymlもあり構築も楽できそうでした。一番興味はありましたが、API Gatewayの無料枠は12ヵ月の期限付きなので見送りました。他のサービスでも途中で規約が変わることがありますが現状で判断しました。

  3. Firebase
    モバイル開発でも利用していて上記2つのサービスよりも馴染みがあったことと、自動的に課金されない明示的な無料枠の安心感も手伝って採用することにしました。Cloud FunctionsやRealtime Databaseに興味があるわけではありませんでした・・・:dizzy_face:

消去法で選ばれてしまったFirebaseでありますが、気を取り直して前向きに作業を進めてます:grin::grin:

事前準備

  1. node.jsをインストール

    $ node -v
    v10.15.2
    
  2. npmをインストール

    $ npm -version
    6.8.0
    
  3. Firebase CLIをインストール

    npm install -g firebase-tools
    
    1. ログイン
      以下のコマンドを実行します。

      $ firebase login
      
    2. そのままEnterを押下

      ? Allow Firebase to collect anonymous CLI usage and error reporting information?
      (Y/n) 
      
    3. ブラウザが起動するのでログイン
      01.png

    4. 諸々承認
      ブラウザでは以下のような表示になっています。
      02.png
      コンソール上では以下のように表示されています。

      Waiting for authentication...
      
      ✔  Success! Logged in as devnokiyo@example.com
      

プロジェクトを作成する

プロジェクトを作成したいパスに移動して以下のコマンドを実行します。
対話式で質疑についても例として記載します。ご参考まで。

$ cd repos
$ firebase init functions
? Select a default Firebase project for this directory: [create a new project]
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
? Do you want to install dependencies with npm now? Yes
  1. Firebaseコンソールでプロジェクト作成 03.png
  2. Firebase CLIでプロジェクトを追加
    対話式で質疑についても例として記載します。ご参考まで。

    $ firebase use --add
    ? Which project do you want to add? hoge-rest
    ? What alias do you want to use for this project? (e.g. staging) hoge-rest
    

Realtime Databaseを準備する

  1. Realtime Databaseを作成
    Cloud Firestoreという新しいデータベースも用意されていますが、従来のRealtime Databaseを利用することにします。
    ※スクリーンショットを撮直したのでプロジェクト名が異なっています。 100.png
  2. セキュリティルールを設定

    今回は「ロックモードで開始」を設定します。
    スクリーンショット 2019-03-02 23.53.35.png

  3. テストデータをインポート
    データ取得用のファンクションの動作確認をするためテストデータを用意します。Firebaseコンソールの画像の赤い部分をクリックして「JSONをインポート」を選択します。
    04.png
    以下をインポートします。

    seed.json
    {
      "users": [
        {
          "user_id": "A0001",
          "user_name": "chiyo",
          "age": 9
        },
        {
          "user_id": "A0002",
          "user_name": "eru",
          "age": 5
        },
        {
          "user_id": "A0003",
          "user_name": "otome",
          "age": 13
        }
      ]
    }
    

    インポートするとデータが可視化されます。
    05.png

  4. インデックスを作成
    user_idをキーに検索するファンクションを作成するのでインデックスを作成しておきます。FirebaseコンソールのDatabaseでルールを選択します。以下を追加します。

    "users": {
      ".indexOn": ["user_id"]
    }
    

    Firebaseコンソール上では以下のようになります。
    06.png

ファンクションを作成してデプロイする

ようやく本題に入ります。ファンクションのソースコードはGitHubに公開しています。宜しければご覧ください。

  1. プロジェクトを作成したディレクトリに移動
    簡単に構成を確認します。ご察しと思われますがindex.jsにプログラムを書きます。

    ├── firebase.json
    └── functions
        ├── index.js
        ├── node_modules
        ├── package-lock.json
        └── package.json
    
  2. Usersを全件取得するファンクションを作成
    まずは最低限のnpmパッケージのみを利用して動作させてみます。

    index.js
    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    
    admin.initializeApp();
    exports.users = functions.https.onRequest((req, res) => {
      const ref = admin.database().ref('users');
      ref.once("value", function(data) {
        res.send(data);
      });
    });
    
  3. デプロイ
    functionsディレクトリに移動して、デプロイコマンドを実行します。

    $ cd functions
    $ ls
    index.js        package-lock.json
    node_modules        package.json
    $ firebase deploy --only functions
        :
        :
    === Deploying to 'hoge-rest'...
    
    i  deploying functions
    Running command: npm --prefix "$RESOURCE_DIR" run lint
        :
        :
    ✔  Deploy complete!
    
    Please note that it can take up to 30 seconds for your updated functions to propagate.
    Project Console: https://console.firebase.google.com/project/hoge-rest/overview
    

    上記のメッセージどおりデプロイに30秒ほど掛かります:hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand:
    余談ですが、筆者は「修正が反映されていない」と思ったことが何度もありました:sweat:

  4. ファンクションを実行

    1. トリガーを確認
      FirebaseコンソールのFunctions ダッシュボードより該当のファンクションのトリガーを確認します。 07.png
    2. ブラウザでURLにアクセス
      jsonでUsersの全件が返却されていることを確認します。 08.png

expressを導入してファンクションを書替える

WebAPIを機能追加するにあたりルーティングを体系的に記述したいのでexpressを導入します。

  1. npmパッケージをインストール

    functionsディレクトリに移動してnpmパッケージをインストールします。

    $ npm install express --save
    $ npm install body-parser --save
    
  2. ファンクションをexpress版に書換え

    index.js
    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    const express = require('express');
    const bodyParser = require('body-parser');
    
    const app = express();
    app.use(bodyParser.json());
    
    admin.initializeApp();
    
    app.get('/', (req, res) => {
      const ref = admin.database().ref('users');
      ref.once('value').then(data => {
        return res.send(data);
      })
      .catch(error => {
        res.status(404).send('No data available.');
      });
    });
    
    exports.users = functions.https.onRequest(app);
    
  3. ファンクションを実行
    先ほどの手順でファンクションをデプロイ・実行して問題ないことを確認します。

その他、CRUDのファンクションを作成

ここまででUserを全件を取得するファンクションを作成しました。続いて以下を作成します。
ここからはソースコードを掲載して簡単に動作確認する程度します。
必要に応じてカスタマイズしてください:robot:

  • 特定のUserを1件取得
  • Userを作成
  • Userを更新
  • Userを削除

特定のUserを1件取得

ソースコード

index.js
app.get('/:user_id', (req, res) => {
  const ref = admin.database().ref('users');
  ref.orderByChild('user_id').equalTo(req.params.user_id)
    .once('value').then(data => {
      return res.send(data);
    })
    .catch(error => {
      res.status(404).send('No data available.');
    });
});

確認

User_IdがA0001のユーザーのみ取得します。
09.png

Userを作成

ソースコード

index.js
app.post('/', (req, res) => {
  const ref = admin.database().ref('users');
  ref.push({                                  // ユニークキーも自動生成する
    user_id: req.body.user_id,
    user_name: req.body.user_name,
    age: Number(req.body.age)                 // NaNは考慮しないことにする
  }).then(data => {
    return res.status(201).send(data);
  })
  .catch(error => {
    res.status(400).send(error);      
  });
});

確認

新しいユーザー「Kurobee」を追加します。
余談ですがREST WebAPIの確認にはChromeアプリのAdvanced REST clientを気に入っています。
10.png

Userを取得して確認します。
11.png

Userを更新

ソースコード

index.js
app.put('/:id', (req, res) => {
  const ref = admin.database().ref('users');
  const updates = {};
  updates[req.params.id] = {
    user_id: req.body.user_id,
    user_name: req.body.user_name,
    age: Number(req.body.age)                 // NaNは考慮しないことにする
  };
  ref.update(updates).then(data => {
    return res.send(data);
  })
  .catch(error => {
    res.status(400).send(error);      
  });
});

確認

ユーザー「Kurobee」の年齢を「3」から「4」に更新します。
12.png

Userを取得して確認します。
13.png

Userを削除

ソースコード

index.js
app.delete('/:id', (req, res) => {
  const ref = admin.database().ref(`users/${req.params.id}`);
  ref.remove().then(data => {
    return res.send(data);
  })
  .catch(error => {
    res.status(400).send(error);      
  });
});

確認

ユーザー「Kurobee」を削除します。
14.png

Userを取得して確認します。
15.png

終わりに

冒頭でもお伝えしましたとおりjsonだけ返却するWebAPIがあれば十分でしたが、つい作成してしまいました。「FaaSやBaaSはサーバーレスで簡単」という触込みが多いですが、それぞれそれなりに独特のものがあると思っています。NoSQLにあまり触れていない筆者はRealtime Databaseに少し苦戦しましたが、簡易的なCRUD WebAPIにはなっていると思いますので、今後活用していくつもりです。

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

Firebase Cloud Functions, Realtime DatabaseでCRUD REST WebAPIを作る

前置き

外部からhttpsで接続出来るテスト用のWebAPIをとりあえず作成したいことがありました。固定のjsonを返却するだけのWebAPIを作成すれば良かったのですが、興味本位でRealtime Databaseも使って簡単なCRUD機能を提供するWebAPIを作りました。というよりもちょっと寄道して作ってしまいました:sweat_smile:
「ソースコードだけ見られれば十分」と思われる方々もいらっしゃると思いますが、ひととおり筆者が行った手順も含めて記事にしました。ソースコードはGitHubに公開しています。
※検証した実際のURLを掲載していますが削除する予定です。ご了承ください。

なぜFirebaseを選んだか

jsonファイルをホスティングすれば良いのですが「せっかくならCRUDくらい付いていると嬉しい」という欲が出てしまいました。そのため、ホスティング案を除外して無料で動的なプログラムが動作する環境として、すぐ浮かんだ3つを検討しました。

  1. Heroku
    スキルセットの観点からRailsを利用することになりますが、APIモードでプロジェクトを作成するにしても、少し大掛かりな印象がありました。ほんの少しCRUD出来れば満足なので今回は採用を見送りました。

  2. AWS
    API Gateway+Lambda+DynamoDBを利用することも検討しました。公式のサンプルにCloudFormationCloudFormation のymlもあり構築も楽できそうでした。一番興味はありましたが、API Gatewayの無料枠は12ヵ月の期限付きなので見送りました。他のサービスでも途中で規約が変わることがありますが現状で判断しました。

  3. Firebase
    モバイル開発でも利用していて上記2つのサービスよりも馴染みがあったことと、自動的に課金されない明示的な無料枠の安心感も手伝って採用することにしました。Cloud FunctionsやRealtime Databaseに興味があるわけではありませんでした・・・:dizzy_face:

消去法で選ばれてしまったFirebaseでありますが、気を取り直して前向きに作業を進めてます:grin::grin:

事前準備

  1. node.jsをインストール

    $ node -v
    v10.15.2
    
  2. npmをインストール

    $ npm -version
    6.8.0
    
  3. Firebase CLIをインストール

    npm install -g firebase-tools
    
    1. ログイン
      以下のコマンドを実行します。

      $ firebase login
      
    2. そのままEnterを押下

      ? Allow Firebase to collect anonymous CLI usage and error reporting information?
      (Y/n) 
      
    3. ブラウザが起動するのでログイン
      01.png

    4. 諸々承認
      ブラウザでは以下のような表示になっています。
      02.png
      コンソール上では以下のように表示されています。

      Waiting for authentication...
      
      ✔  Success! Logged in as devnokiyo@example.com
      

プロジェクトを作成する

プロジェクトを作成したいパスに移動して以下のコマンドを実行します。
対話式で質疑についても例として記載します。ご参考まで。

$ cd repos
$ firebase init functions
? Select a default Firebase project for this directory: [create a new project]
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
? Do you want to install dependencies with npm now? Yes
  1. Firebaseコンソールでプロジェクト作成 03.png
  2. Firebase CLIでプロジェクトを追加
    対話式で質疑についても例として記載します。ご参考まで。

    $ firebase use --add
    ? Which project do you want to add? hoge-rest
    ? What alias do you want to use for this project? (e.g. staging) hoge-rest
    

Realtime Databaseを準備する

  1. Realtime Databaseを作成
    Cloud Firestoreという新しいデータベースも用意されていますが、従来のRealtime Databaseを利用することにします。
    ※スクリーンショットを撮直したのでプロジェクト名が異なっています。 100.png
  2. セキュリティルールを設定

    今回は「ロックモードで開始」を設定します。
    スクリーンショット 2019-03-02 23.53.35.png

  3. テストデータをインポート
    データ取得用のファンクションの動作確認をするためテストデータを用意します。Firebaseコンソールの画像の赤い部分をクリックして「JSONをインポート」を選択します。
    04.png
    以下をインポートします。

    seed.json
    {
      "users": [
        {
          "user_id": "A0001",
          "user_name": "chiyo",
          "age": 9
        },
        {
          "user_id": "A0002",
          "user_name": "eru",
          "age": 5
        },
        {
          "user_id": "A0003",
          "user_name": "otome",
          "age": 13
        }
      ]
    }
    

    インポートするとデータが可視化されます。
    05.png

  4. インデックスを作成
    user_idをキーに検索するファンクションを作成するのでインデックスを作成しておきます。FirebaseコンソールのDatabaseでルールを選択します。以下を追加します。

    "users": {
      ".indexOn": ["user_id"]
    }
    

    Firebaseコンソール上では以下のようになります。
    06.png

ファンクションを作成してデプロイする

ようやく本題に入ります。ファンクションのソースコードはGitHubに公開しています。宜しければご覧ください。

  1. プロジェクトを作成したディレクトリに移動
    簡単に構成を確認します。ご察しと思われますがindex.jsにプログラムを書きます。

    ├── firebase.json
    └── functions
        ├── index.js
        ├── node_modules
        ├── package-lock.json
        └── package.json
    
  2. Usersを全件取得するファンクションを作成
    まずは最低限のnpmパッケージのみを利用して動作させてみます。

    index.js
    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    
    admin.initializeApp();
    exports.users = functions.https.onRequest((req, res) => {
      const ref = admin.database().ref('users');
      ref.once("value", function(data) {
        res.send(data);
      });
    });
    
  3. デプロイ
    functionsディレクトリに移動して、デプロイコマンドを実行します。

    $ cd functions
    $ ls
    index.js        package-lock.json
    node_modules        package.json
    $ firebase deploy --only functions
        :
        :
    === Deploying to 'hoge-rest'...
    
    i  deploying functions
    Running command: npm --prefix "$RESOURCE_DIR" run lint
        :
        :
    ✔  Deploy complete!
    
    Please note that it can take up to 30 seconds for your updated functions to propagate.
    Project Console: https://console.firebase.google.com/project/hoge-rest/overview
    

    上記のメッセージどおりデプロイに30秒ほど掛かります:hourglass_flowing_sand::hourglass_flowing_sand::hourglass_flowing_sand:
    余談ですが、筆者は「修正が反映されていない」と思ったことが何度もありました:sweat:

  4. ファンクションを実行

    1. トリガーを確認
      FirebaseコンソールのFunctions ダッシュボードより該当のファンクションのトリガーを確認します。 07.png
    2. ブラウザでURLにアクセス
      jsonでUsersの全件が返却されていることを確認します。 08.png

expressを導入してファンクションを書替える

WebAPIを機能追加するにあたりルーティングを体系的に記述したいのでexpressを導入します。

  1. npmパッケージをインストール

    functionsディレクトリに移動してnpmパッケージをインストールします。

    $ npm install express --save
    $ npm install body-parser --save
    
  2. ファンクションをexpress版に書換え

    index.js
    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    const express = require('express');
    const bodyParser = require('body-parser');
    
    const app = express();
    app.use(bodyParser.json());
    
    admin.initializeApp();
    
    app.get('/', (req, res) => {
      const ref = admin.database().ref('users');
      ref.once('value').then(data => {
        return res.send(data);
      })
      .catch(error => {
        res.status(404).send('No data available.');
      });
    });
    
    exports.users = functions.https.onRequest(app);
    
  3. ファンクションを実行
    先ほどの手順でファンクションをデプロイ・実行して問題ないことを確認します。

その他、CRUDのファンクションを作成

ここまででUserを全件を取得するファンクションを作成しました。続いて以下を作成します。
ここからはソースコードを掲載して簡単に動作確認する程度します。
必要に応じてカスタマイズしてください:robot:

  • 特定のUserを1件取得
  • Userを作成
  • Userを更新
  • Userを削除

特定のUserを1件取得

ソースコード

index.js
app.get('/:user_id', (req, res) => {
  const ref = admin.database().ref('users');
  ref.orderByChild('user_id').equalTo(req.params.user_id)
    .once('value').then(data => {
      return res.send(data);
    })
    .catch(error => {
      res.status(404).send('No data available.');
    });
});

確認

User_IdがA0001のユーザーのみ取得します。
09.png

Userを作成

ソースコード

index.js
app.post('/', (req, res) => {
  const ref = admin.database().ref('users');
  ref.push({                                  // ユニークキーも自動生成する
    user_id: req.body.user_id,
    user_name: req.body.user_name,
    age: Number(req.body.age)                 // NaNは考慮しないことにする
  }).then(data => {
    return res.status(201).send(data);
  })
  .catch(error => {
    res.status(400).send(error);      
  });
});

確認

新しいユーザー「Kurobee」を追加します。
余談ですがREST WebAPIの確認にはChromeアプリのAdvanced REST clientを気に入っています。
10.png

Userを取得して確認します。
11.png

Userを更新

ソースコード

index.js
app.put('/:id', (req, res) => {
  const ref = admin.database().ref('users');
  const updates = {};
  updates[req.params.id] = {
    user_id: req.body.user_id,
    user_name: req.body.user_name,
    age: Number(req.body.age)                 // NaNは考慮しないことにする
  };
  ref.update(updates).then(data => {
    return res.send(data);
  })
  .catch(error => {
    res.status(400).send(error);      
  });
});

確認

ユーザー「Kurobee」の年齢を「3」から「4」に更新します。
12.png

Userを取得して確認します。
13.png

Userを削除

ソースコード

index.js
app.delete('/:id', (req, res) => {
  const ref = admin.database().ref(`users/${req.params.id}`);
  ref.remove().then(data => {
    return res.send(data);
  })
  .catch(error => {
    res.status(400).send(error);      
  });
});

確認

ユーザー「Kurobee」を削除します。
14.png

Userを取得して確認します。
15.png

終わりに

冒頭でもお伝えしましたとおりjsonだけ返却するWebAPIがあれば十分でしたが、つい作成してしまいました。「FaaSやBaaSはサーバーレスで簡単」という触込みが多いですが、それぞれそれなりに独特のものがあると思っています。NoSQLにあまり触れていない筆者はRealtime Databaseに少し苦戦しましたが、簡易的なCRUD WebAPIにはなっていると思いますので、今後活用していくつもりです。

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

Puppeteerのセットアップから使い方まで〜ブラウザ操作の自動化〜

はじめに

Puppeteerを使ってみたら、とても簡単にブラウザ操作が自動化できたことに感動しました。
セットアップの方法とよく使うPuppeteerのAPIについてまとめましたので、これから使ってみようかなと思っている方の参考になれば嬉しいです。

Puppeteerとは

Puppeteer

読み方は「ぱぺてぃあ」。日本語だと人形使いという意味です。
Chromeブラウザを操作できるNodeのライブラリで、Chrome DevToolsのチームが開発を行っています。

Puppetterの特徴

ブラウザ操作ができるツールとしては、Selenium Webdriverが有名です。
Selenium Webdriverとの違いは、Puppeteerはヘッドレスブラウザを使うことができるので、高速に動作させることができます。
また、PuppeteerはChromeのブラウザしか操作ができません。

使ってみる

セットアップ

Node.jsが動く環境が必要です。

$ npm i puppeteer

サクッと試したいだけならPuppeteerがWebツールを提供してくれているので、そちらを使うと便利です。

Try Puppeteer

テストを書く

簡単なテストシナリオを書いてみます。
下記はGoogleのトップページに遷移して、画面のスクリーンショットを撮るテストシナリオです。

test.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://www.google.com');
  await page.screenshot({path: 'screenshot.png'});

  await browser.close();
})();

ベースはこんな感じです。
Browserのインスタンスを作成した後にPuppeteerのAPIを使って、ページ内のリンクをクリックして別ページに遷移したり、フォームを操作したり、ブラウザを操作していきます。

テストを実行する

下記コマンドを実行します。

$ node test.js

よく使うAPI

指定したURLにアクセスする

page.goto('https://www.google.com/');

クリック

page.click('input[type="submit"]');

クリックする要素の指定には、CSSセレクタを使用します。指定したセレクタが存在しない場合は、エラーになります。

$ node test.js
UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement at ElementHandle._clickablePoint

フォーム操作

<form method="post" action="/update">
  <label for="name">名前</label>
  <input type="text" name="name" id="name">
  <label for="email">メールアドレス</label>
  <input type="text" name="email" id="email">
  <label for="gender">性別</label>
  <select id="gender" name="gender">
    <option value="m">男性</option>
    <option value="f">女性</option>
  </select>
  <label for="inquire">問い合わせの内容</label>
  <input type="radio" name="inquire" value="1" id="inquire_1"> 商品に関する問い合わせ
  <input type="radio" name="inquire" value="2" id="inquire_2"> その他
  <input type="submit" value="送信">
</form>

例えば上記のフォームを操作するAPIは以下のとおりです。

// テキストフィールドに値を設定する
page.type('input[name="name"]', 'kanoe');
page.type('input[name="email"]', 'kanoe@xxxx.xxx');
// プルダウンから選択する
page.select('#gender', 'f');
// ラジオボタンを選択する
page.click('#inquire_1');

デバイス切り替え

Puppeteerには既にデバイスのリストを定義しているので、そちらを使って切り替えると便利です。
定義されているデバイスは下記コードから確認できます。
https://github.com/GoogleChrome/puppeteer/blob/master/DeviceDescriptors.js

const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
// iPhone6を指定してみる
const iPhone = devices['iPhone 6'];

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.emulate(iPhone);
  await page.goto('https://www.ozmall.co.jp/');
  await page.screenshot({path: 'example.png'});
  await browser.close();
})();

デバイスを指定する方法以外にも、page.setUserAgentを使うことでUserAgentを直接指定することも可能です。

page.setUserAgent('Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30');

待機

用途としては、JavaScriptのeventが起きるまで待機するために使うことが多いAPIです。

// imgタグが見つかるまで待機、見つかったらconsoleにログを出力する
page.waitFor('img')
    .then(() => console.log('gazou attayo!'));
// 1s待機
page.waitFor(1000)

画面のキャプチャ

// fullPageのオプションを指定すると、フルページでスクリーンショットが撮れる
page.screenshot({path: 'example.png', fullPage: true});

特定の要素に対して操作する

特定の1要素だけ取得して操作する場合は、page.$evalを使います。

// itemクラスを持つ最初の要素のテキストを取得する
const tab = await page.$eval('.item', el => el.textContent);

ページ内のセレクタを全て取得したい場合は、page.$$evalを使う必要があります。

// ページ内の複数のセレクタの内、特定の要素だけをクリックする
page.$$eval('#nav > .item', tabs => {
  tabs.filter(tab => tab.textContent === 'Contact')[0].click();
});

感想

UIテストを書くとき、Selenium Webdriver + 何かしらのテストフレームワークでテストをかくことが多いと思います。
わたしも、以前Selenium WebdriverとCodeceptionを使ってE2Eテストを構築したことがあるのですが、ブラウザのドライバの仕様に引っ張られて苦労することが多々ありました。
PuppeteerはChrome開発チームが開発していることもあって、ストレスなくブラウザ操作が実現できるのがとてもよかったです。あと環境構築もとても簡単なので、ちょっと試したい時にすぐ使えるのは便利だなーと思いました。

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

Google App Engineにmercury-parserをデプロイしてみる

発端

  • Mercury Web Parser API2019/4/15 でシャットダウンするよ」とメールが来た。
  • Chrome拡張で便利に使っていたので、なくなると困る。
  • なのでとりあえずGoogle App Engineにデプロイする事にした。

作業環境

mercury-parser リポジトリの取得

  • 適当なフォルダでGitHubのリポジトリからクローンする。
git clone https://github.com/postlight/mercury-parser.git

app.yaml app.js .gcloudignore package.json を用意する

  • クローンしたフォルダに app.yaml と app.js と .gcloudignore を追加する。
app.yaml
runtime: nodejs8
app.js
const express = require('express');
const Mercury = require('./dist/mercury');

const app = express();

app.get('/', (req, res) => {
  const url = req.query.url;
  if (!url || url === undefined) {
    res.status(200).end('');
    return;
  }
  let timeoutID;
  Mercury.parse(url)
    .then(function(result) {
      res.status(200).send(result).end();
      clearTimeout(timeoutID);
    })
    .catch(
      (timeoutID = setTimeout(function() {
        res.status(200).end('');
      }, 20000))
    );
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`App listening on port ${PORT}`);
  console.log('Press Ctrl+C to quit.');
});
.gcloudignore
.gcloudignore
.git
.gitignore
node_modules/
.circleci/
.github/
fixtures/
scripts/
src/
.agignore
.babelrc
.eslintignore
.eslintrc
.gitattributes
.nvmrc
.prettierignore
.prettierrc
.remarkrc
CHANGELOG.md
cli.js
CODE_OF_CONDUCT.md
CONTRIBUTING.md
karma.conf.js
LICENSE-APACHE
LICENSE-MIT
preview
README.md
RELEASE.md
rollup.config.js
rollup.config.web.js
score-move
yarn.lock
  • package.jsonは元からあるファイルを改変する。
    • scriptsにstartを追加。
    • enginesの書式をGoogle App Engine方式に変更。
    • dependenciesにexpressを追加。
package.json
  "scripts": {
    "start": "node app.js",  追加
   中略 
  },
   中略 
  "engines": {
    "node": "8.x.x"  変更
  },
   中略 
  "dependencies": {
    "express": "^4.16.4",  追加
   中略 
  },

ここまで実施済みなのが このリポジトリの「feat-gae-deploy」ブランチ

ローカルで実行してみる

ローカルで軽く試してみる場合は、mercury-parserをクローンしたフォルダをコマンドプロンプトで開き、↓を実行すると8080番ポートでサーバーが起動する。

npm install
npm start
  • npm installは最初の1回目か package.json で dependencies などを変更した時のみ実行する。
  • http://localhost:8080/?url=<パースしたいURL> で動作を確認出来る。

Google App Engineにデプロイしてみる

手順

  1. mercury-parserをクローンしたフォルダをコマンドプロンプトで開き、gcloud app create を実行する。
  2. gcloud app create は最初の1回のみ実行する。

<プロジェクトID>は作成したGoogle App Engineの プロジェクトのID
  プロジェクトの名前 とは別なので注意。

gcloud app create --project=<プロジェクトID>

↑で、デプロイするリージョンを聞かれるので、好きな場所を指定する。

gcloud app deploy --project=<プロジェクトID>

続けるか聞かれるので、続けるならEnterキーを押す。
ファイルのアップロードとサービスの起動が実行される。



デプロイ完了!

動作を確認してみる

アップロードした app.js は
https://<プロジェクトID>.appspot.com/?url=<パースしたいURL>
でパースした結果をJSONで返してくるので、適当なURLで試してみる。

せっかくなので Custom Parser(extractor) を作ってみる

うまくパースされないページなどがあると自分で Custom Parser(extractor) を作ってみたくなるもの。

  • mercury-parserをクローンしたフォルダで yarn generate-parser を実行し、URLを入力すると、 Custom Parser(extractor) のひな形を作成する事が出来る。
  • yarnnpm install -g yarn でインストール出来る。
  • yarn watch:test <入力したURLのドメイン名> でテストが通るようになったら、 yarn test_builddist/mercury.js をビルドしてみる。
  • yarn test_build && npm start でローカル実行して試すも良し、 yarn test_build && gcloud app deploy --project=<プロジェクトID> でデプロイするも良し。

ついでに

  • package.json の scripts によく使うコマンドを追加すると yarn で呼び出せて便利。

    • "deploy": "gcloud app deploy --project=<プロジェクトID>",yarn deploy とか。
    • "log": "gcloud app logs tail -s default --project=<プロジェクトID>",yarn log とか。
  • デプロイしたプロジェクトが呼び出されまくって無料枠を超えてしまうなど怖い事もありますので、
    必要に応じて使用量の制限はしておきましょう。
    使用量.png

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

Alexa-hostedスキルを使用してAlexaスキルを作ってみる

AlexaスキルでGo
改め、Alexa-hostedスキルを使用してAlexaスキルを作ってみる
もともとはGo言語のwebhookを作って・・って考えてたのですが、途中から急に方向性かわったのでタイトルも変わりました。なので最初の方はタイトルにそぐわないです。すいません。
GO言語のはまた今度やります。


少し前にGo言語のハンズオンに参加してきました。

【増枠】Go言語 初心者向けハンズオン #3

前半が講義、後半がハンズオンというあまりない形式の勉強会でした。
後半のハンズオンが資料と時間を与えられてもくもくする形式で緊張感があってなかなか楽しめました。
楽しんだ結果、go言語もそこそこ掴めた気がするので、手段が目的のシリーズをやってみます。

 何作ります?

RustとLambdaでなんか作る 後編

と一緒です。google homeアプリの移植ですね。

アプリ名:リクガメの食べもの

リクガメにあげてもいい餌を教えてくれるアプリ。

俺: リクガメの食べ物につないで
Home: はい、リクガメの食べ物です
俺: ピーマンあげていい?
Home: はい。ピーマンはあげていい食べ物です。カルシウムは少ないです。(終了)

まずはAlexa上で。

Alexaコンソールでのスキル作成については一度書いてますね。
一日かけて魚へんクイズスキルを作ってみる話

キャプチャは省き、手順だけ記します。

  1. Alexaコンソールログイン
  2. スキルの作成
  3. スキル名:リクガメの食べ物

・・で、日本語、カスタムスキルと選択して進めますが・・お?なんだこれ?

76.png

Alexaがホスト?ベータ?
Alexa-hostedスキルとやらが増えてました。
google homeでfirebase functionを使って開発するイメージに近いんでしょうか。
nodejsだし。

Goで作るつもりで進めてたんだけど、気になったのでいきなり方向転換です。

Alexa-hostedスキルでスキルを作ってみる

詳細はこちら
Alexa-hostedスキルを使用してスキルをエンドツーエンドで作成する

よくわかんないけどとりあえず進めてみましょう。

77.png

インテント

  • askIntent 質問を受け止めて返事をします
  • finishIntent  「終わる」とか「終了」って言われた時にバイバイします

スロット

  • foodSlot  「小松菜」とか食べ物の群です。Dialog flowのEntitiyからraw modeで表示してペロっとコピりました

設定結果

おおよそこんな感じになってます。
さて、次はバックエンドだ。

{
    "interactionModel": {
        "languageModel": {
            "invocationName": "リクガメの食べ物",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "HelloWorldIntent",
                    "slots": [],
                    "samples": [
                        "hello",
                        "how are you",
                        "say hi world",
                        "say hi",
                        "hi",
                        "say hello world",
                        "say hello"
                    ]
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                },
                {
                    "name": "askIntent",
                    "slots": [
                        {
                            "name": "foodSlot",
                            "type": "foodSlot",
                            "samples": [
                                "{foodSlot}"
                            ]
                        }
                    ],
                    "samples": [
                        "{foodSlot} は良い?",
                        "{foodSlot}"
                    ]
                },
                {
                    "name": "finishIntent",
                    "slots": [],
                    "samples": [
                        "exit",
                        "close",
                        "ばいばい",
                        "またね",
                        "しゅうりょう",
                        "終了",
                        "おわる",
                        "終わる",
                        "止める",
                        "やめる",
                        "終わり",
                        "おわり"
                    ]
                }
            ],
            "types": [
                {
                    "name": "foodSlot",
                    "values": [
                        {
                            "id": "小松菜",
                            "name": {
                                "value": "小松菜",
                                "synonyms": [
                                    "こまつな",
                                    "コマツナ",
                                    "小松な",
                                    "小松菜"
                                ]
                            }
                        },
                        {
                            "id": "水菜",
                            "name": {
                                "value": "水菜",
                                "synonyms": [
                                    "みずな",
                                    "ミズナ",
                                    "水な"
                                ]
                            }
                        }
                    ]
                }
            ]
        },
        "dialog": {
            "intents": [
                {
                    "name": "askIntent",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "foodSlot",
                            "type": "foodSlot",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.191432920376.1362887948561"
                            }
                        }
                    ]
                }
            ],
            "delegationStrategy": "ALWAYS"
        },
        "prompts": [
            {
                "id": "Elicit.Slot.191432920376.1362887948561",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "野菜や植物の名前を言ってください"
                    },
                    {
                        "type": "PlainText",
                        "value": "どの食べ物について知りたいですか"
                    },
                    {
                        "type": "PlainText",
                        "value": "食べさせたい食べ物の名前を言ってください"
                    }
                ]
            }
        ]
    }
}

バックエンド部分開発

コードエディタというのが使えるようになっております。

78.png

package.json もありますね。ここでrequiredすればモジュールも普通に使えるんでしょうか。

そんなわけでなんとなく作りました。
dialog flow上のエディタで作る場合と違って、複数ファイルにまたがって作れますね。

79.png

SaveしたらDeployです。
なんか左下のLogs: Amazon CloudWatch ってリンクからCloudWatchのログが見れます。

テスト

80.png

そんなわけでなんとなくできました。
テレビ見つつ、ブログ書きつつで2時間くらいかかりました。

この後はリリースって流れなんですが、GO言語で作り直すかも。

code

一応あげてみてます。
https://github.com/ikegam1/alexa-kamefood-nodejs-hosted

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

VueとFirebase Authenticationを使用してGoogleログインを試す

1.Firebaseでプロジェクトを作成

  • firebaseコンソールにログインしてプロジェクトを作成する
  • 作成したプロジェクトのAuthentication->ユーザーを選択し、ログインを行うテストユーザーを作成する
  • ログイン方法を選択し、メール/パスワードとGoogleのステータスを有効にする
  • ウェブ設定を選択すると、コードが表示される(後で使用する)

2.ローカルに開発環境を作成

  • 下記のコマンドを実行する
npm i vue -g
vue create <プロジェクト名>
? Please pick a preset: (Use arrow keys)
> default (babel, eslint)
cd <プロジェクト名>
vue add router
npm i vue bootstrap-vue -s
npm i firebase -s

3.ソースコード

  • ライブラリとFirebaseの設定
プロジェクト名/src/main.js
import Vue from 'vue'
import VueHead from 'vue-head'
import App from './App.vue'
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import firebase from 'firebase/app'
import 'firebase/auth'
import router from './router'

Vue.use(BootstrapVue)
Vue.use(VueHead)

Vue.config.productionTip = false

// 1.Firebase側の設定のウェブ設定で表示したコードをコピペする
const firebaseConfig = {
    apiKey: "***",
    authDomain: "***",
    databaseURL: "***",
    projectId: "***",
    storageBucket: "***",
    messagingSenderId: "***"
};

firebase.initializeApp(firebaseConfig);

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
  • ルーターを作成
プロジェクト名/src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import Login from './views/Login.vue'
import Success from './views/Success.vue'
import firebase from 'firebase/app'

Vue.use(Router)

const router =  new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'login',
      component: Login
    },
    {
      path: '/success',
      name: 'success',
      component: Success
    },
    {
      path: '*',
      name: 'login',
      component: Login
    }
  ]
})

// 未認証の場合はログイン画面へ
router.beforeResolve((to, from, next) => {
  console.log(to)
  if (to.path == '/') {
    next()
  } else {
    firebase.auth().onAuthStateChanged(user => {
      if (user) {
        console.log('認証中')
        next()
      } else {
        console.log('未認証')
        next({path: '/'})
      }
    })
  }
})

export default router
  • ログイン画面の作成
プロジェクト名/src/views/Login.vue
<template>
  <div class="container">
    <div class="row">
      <div class="col-md-12">
        <h2>ログイン画面</h2>
        <div class="mt-2"><b-form-input v-model="email" type="text" placeholder="メールアドレス" /></div>
        <div class="mt-2"><b-form-input v-model="password" type="text" placeholder="パスワード" /></div>
        <div class="mt-2"><b-button block variant="primary" @click="emailLogin">ログイン</b-button></div>
        <div class="mt-2"><b-button block variant="primary" @click="googleLogin">Google ログイン</b-button></div>
        <div class="mt-2"><b-alert v-model="showError" dismissible variant="danger">{{ errorMessage }}</b-alert></div>
      </div>
    </div>
  </div>
</template>
<style>
.mt-2 {
  margin-top: 2px;
}
</style>

<script>
import firebase from 'firebase/app'
import router from '../router'

export default {
  name: 'login',
  data() {
    return {
      email: '',
      password: '',
      errorMessage: '',
      showError: false
    }
  },
  methods: {
    emailLogin() {
      firebase.auth().signInWithEmailAndPassword(this.email, this.password).then(result => {
        console.log(result)
        router.push('/success')
      }).catch(error => {
        console.log(error)
        this.errorMessage = error.message
        this.showError = true
      })
    },
    googleLogin() {
      const provider = new firebase.auth.GoogleAuthProvider()

      firebase.auth().signInWithPopup(provider).then(result => {
        console.log(result.user)
        router.push('/success')
      }).catch(error => {
        console.log(error)
        this.errorMessage = error.message
        this.showError = true
      })
    }
  }
}
</script>
  • ログイン成功画面の作成
プロジェクト名/src/views/Success.vue
<template>
  <div class="container">
    <div class="row">
      <div class="col-md-12">
        <h2>ログイン成功</h2>
        <div class="mt-4"><b-button block variant="primary" @click="logout">ログアウト</b-button></div>
      </div>
    </div>
  </div>
</template>

<style>
.mt-4 {
  margin-top: 4px;
}
</style>

<script>
import firebase from 'firebase/app'
import router from '../router'

export default {
  name: 'success',
  methods: {
    logout() {
      firebase.auth().signOut().then(function() {
        router.push('/')
      }).catch(error => {
        console.log(error.message)
        router.push('/')
      })
    }
  }
}
</script>

4.ログイン画面の確認

  • サーバを起動する
npm run serve
  • ログイン画面へアクセスし、メアド/パスかGoogleログインでログインを行う
    image.png

  • ログインに成功するとログイン画面が表示される

  • ログインせずにアクセスしようとしても、ログイン画面に飛ばされる
    image.png

5.Unexpected console statement (no-console)が発生した場合の対処

  • rulesに["no-console": "off"]を追加する
プロジェクト名/package.json
  "eslintConfig": {
    "rules": { "no-console": "off" }
  },

6.その他

Googleログインのリダイレクトも試したが、リダイレクトが終わる前に完了処理が実行されてしまう。。。
対処方法がわからないため、断念。。。

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