- 投稿日:2019-03-02T21:19:05+09:00
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.jsximport 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 buildNode.js + Express サーバーを起動
プロジェクトのルートディレクトリに Server.js ファイルを用意し、内容は以下をコピペしてください。
Server.jsconst 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 ページが表示されます。
さあアプリ開発を開始しましょう
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 動作が軽くなります。
.cfignorenode_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 ダッシュボードでアプリの開始を確認します。
「アプリURLにアクセス」リンクから、実際の表示を確認します。
関連ファイルのダウンロード
rtk-nerm というGitHubリポジトリに今回のファイルを置いておきましたので、面倒な方はそちらからダウンロード/clone してお使いください。
Enjoy!
Node.js でロジックを実装し、Express + React + Material-UI ベースで開発したWebアプリをビルドするための、できるだけシンプルな環境を構築してみました。これを出発点として自分なりのアプリやビルド環境を構築する助けになれば嬉しいです。
ではまた!
- 投稿日:2019-03-02T19:57:35+09:00
Firebase CloudFunctions, Realtime DatabaseでREST WebAPIを作る
前置き
外部からhttpsで接続出来るテスト用のWebAPIをとりあえず作成したいことがありました。固定のjsonを返却するだけのWebAPIを作成すれば良かったのですが、興味本位でRealtime Databaseも使って簡単なCRUD機能を提供するWebAPIを作りました。というよりもちょっと寄道して作ってしまいました
「ソースコードだけ見られれば十分」と思われる方々もいらっしゃると思いますが、ひととおり筆者が行った手順も含めて記事にしました。ソースコードはGitHubに公開しています。
※検証した実際のURLを掲載していますが削除する予定です。ご了承ください。なぜFirebaseを選んだか
jsonファイルをホスティングすれば良いのですが「せっかくならCRUDくらい付いていると嬉しい」という欲が出てしまいました。そのため、ホスティング案を除外して無料で動的なプログラムが動作する環境として、すぐ浮かんだ3つを検討しました。
Heroku
スキルセットの観点からRailsを利用することになりますが、APIモードでプロジェクトを作成するにしても、少し大掛かりな印象がありました。ほんの少しCRUD出来れば満足なので今回は採用を見送りました。AWS
API Gateway+Lambda+DynamoDBを利用することも検討しました。公式のサンプルにCloudFormationのymlもあり構築も楽できそうでした。一番興味はありましたが、API Gatewayの無料枠は12ヵ月の期限付きなので見送りました。他のサービスでも途中で規約が変わることがありますが現状で判断しました。Firebase
モバイル開発でも利用していて上記2つのサービスよりも馴染みがあったことと、自動的に課金されない明示的な無料枠の安心感も手伝って採用することにしました。CloudFunctionやRealtime Databaseに興味があるわけではありませんでした・・・消去法で選ばれてしまったFirebaseでありますが、気を取り直して前向きに作業を進めてます
事前準備
node.jsをインストール
$ node -v v10.15.2npmをインストール
$ npm -version 6.8.0Firebase CLIをインストール
npm install -g firebase-toolsプロジェクトを作成する
プロジェクトを作成したいパスに移動して以下のコマンドを実行します。
対話式で質疑についても例として記載します。ご参考まで。$ 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
- Firebaseコンソールでプロジェクト作成
![]()
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-restRealtime Databaseを準備する
- Realtime Databaseを作成
Cloud Firestoreという新しいデータベースも用意されていますが、従来のRealtime Databaseを利用することにします。
※スクリーンショットを撮直したのでプロジェクト名が異なっています。![]()
テストデータをインポート
データ取得用のファンクションの動作確認をするためテストデータを用意します。Firebaseコンソールの画像の赤い部分をクリックして「JSONをインポート」を選択します。
以下をインポートします。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 } ] }インデックスを作成
user_idをキーに検索するファンクションを作成するのでインデックスを作成しておきます。FirebaseコンソールのDatabaseでルールを選択します。以下を追加します。"users": { ".indexOn": ["user_id"] }ファンクションを作成してデプロイする
ようやく本題に入ります。ファンクションのソースコードはGitHubに公開しています。宜しければご覧ください。
プロジェクトを作成したディレクトリに移動
簡単に構成を確認します。ご察しと思われますがindex.jsにプログラムを書きます。├── firebase.json └── functions ├── index.js ├── node_modules ├── package-lock.json └── package.jsonUsersを全件取得するファンクションを作成
まずは最低限のnpmパッケージのみを利用して動作させてみます。index.jsconst 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); }); });デプロイ
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秒ほど掛かります
余談ですが、筆者は「修正が反映されていない」と思ったことが何度もありましたファンクションを実行
expressを導入してファンクションを書替える
WebAPIを機能追加するにあたりルーティングを体系的に記述したいのでexpressを導入します。
npmパッケージをインストール
functionsディレクトリに移動してnpmパッケージをインストールします。$ npm install express --save $ npm install body-parser --saveファンクションをexpress版に書換え
index.jsconst 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);ファンクションを実行
先ほどの手順でファンクションをデプロイ・実行して問題ないことを確認します。その他、CRUDのファンクションを作成
ここまででUserを全件を取得するファンクションを作成しました。続いて以下を作成します。
ここからはソースコードを掲載して簡単に動作確認する程度します。
必要に応じてカスタマイズしてください
- 特定のUserを1件取得
- Userを作成
- Userを更新
- Userを削除
特定のUserを1件取得
ソースコード
index.jsapp.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を作成
ソースコード
index.jsapp.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を気に入っています。
Userを更新
ソースコード
index.jsapp.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」に更新します。
Userを削除
ソースコード
index.jsapp.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); }); });確認
終わりに
冒頭でもお伝えしましたとおりjsonだけ返却するWebAPIがあれば十分でしたが、つい作成してしまいました。「FaaSやBaaSはサーバーレスで簡単」という触込みが多いですが、それぞれそれなりに独特のものがあると思っています。NoSQLにあまり触れていない筆者はRealtime Databaseに少し苦戦しましたが、簡易的なCRUD WebAPIにはなっていると思いますので、今後活用していくつもりです。
- 投稿日:2019-03-02T19:57:35+09:00
Firebase Cloud Functions, Realtime DatabaseでREST WebAPIを作る
前置き
外部からhttpsで接続出来るテスト用のWebAPIをとりあえず作成したいことがありました。固定のjsonを返却するだけのWebAPIを作成すれば良かったのですが、興味本位でRealtime Databaseも使って簡単なCRUD機能を提供するWebAPIを作りました。というよりもちょっと寄道して作ってしまいました
「ソースコードだけ見られれば十分」と思われる方々もいらっしゃると思いますが、ひととおり筆者が行った手順も含めて記事にしました。ソースコードはGitHubに公開しています。
※検証した実際のURLを掲載していますが削除する予定です。ご了承ください。なぜFirebaseを選んだか
jsonファイルをホスティングすれば良いのですが「せっかくならCRUDくらい付いていると嬉しい」という欲が出てしまいました。そのため、ホスティング案を除外して無料で動的なプログラムが動作する環境として、すぐ浮かんだ3つを検討しました。
Heroku
スキルセットの観点からRailsを利用することになりますが、APIモードでプロジェクトを作成するにしても、少し大掛かりな印象がありました。ほんの少しCRUD出来れば満足なので今回は採用を見送りました。AWS
API Gateway+Lambda+DynamoDBを利用することも検討しました。公式のサンプルにCloudFormationのymlもあり構築も楽できそうでした。一番興味はありましたが、API Gatewayの無料枠は12ヵ月の期限付きなので見送りました。他のサービスでも途中で規約が変わることがありますが現状で判断しました。Firebase
モバイル開発でも利用していて上記2つのサービスよりも馴染みがあったことと、自動的に課金されない明示的な無料枠の安心感も手伝って採用することにしました。Cloud FunctionsやRealtime Databaseに興味があるわけではありませんでした・・・消去法で選ばれてしまったFirebaseでありますが、気を取り直して前向きに作業を進めてます
事前準備
node.jsをインストール
$ node -v v10.15.2npmをインストール
$ npm -version 6.8.0Firebase CLIをインストール
npm install -g firebase-toolsプロジェクトを作成する
プロジェクトを作成したいパスに移動して以下のコマンドを実行します。
対話式で質疑についても例として記載します。ご参考まで。$ 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
- Firebaseコンソールでプロジェクト作成
![]()
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-restRealtime Databaseを準備する
- Realtime Databaseを作成
Cloud Firestoreという新しいデータベースも用意されていますが、従来のRealtime Databaseを利用することにします。
※スクリーンショットを撮直したのでプロジェクト名が異なっています。![]()
テストデータをインポート
データ取得用のファンクションの動作確認をするためテストデータを用意します。Firebaseコンソールの画像の赤い部分をクリックして「JSONをインポート」を選択します。
以下をインポートします。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 } ] }インデックスを作成
user_idをキーに検索するファンクションを作成するのでインデックスを作成しておきます。FirebaseコンソールのDatabaseでルールを選択します。以下を追加します。"users": { ".indexOn": ["user_id"] }ファンクションを作成してデプロイする
ようやく本題に入ります。ファンクションのソースコードはGitHubに公開しています。宜しければご覧ください。
プロジェクトを作成したディレクトリに移動
簡単に構成を確認します。ご察しと思われますがindex.jsにプログラムを書きます。├── firebase.json └── functions ├── index.js ├── node_modules ├── package-lock.json └── package.jsonUsersを全件取得するファンクションを作成
まずは最低限のnpmパッケージのみを利用して動作させてみます。index.jsconst 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); }); });デプロイ
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秒ほど掛かります
余談ですが、筆者は「修正が反映されていない」と思ったことが何度もありましたファンクションを実行
expressを導入してファンクションを書替える
WebAPIを機能追加するにあたりルーティングを体系的に記述したいのでexpressを導入します。
npmパッケージをインストール
functionsディレクトリに移動してnpmパッケージをインストールします。$ npm install express --save $ npm install body-parser --saveファンクションをexpress版に書換え
index.jsconst 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);ファンクションを実行
先ほどの手順でファンクションをデプロイ・実行して問題ないことを確認します。その他、CRUDのファンクションを作成
ここまででUserを全件を取得するファンクションを作成しました。続いて以下を作成します。
ここからはソースコードを掲載して簡単に動作確認する程度します。
必要に応じてカスタマイズしてください
- 特定のUserを1件取得
- Userを作成
- Userを更新
- Userを削除
特定のUserを1件取得
ソースコード
index.jsapp.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を作成
ソースコード
index.jsapp.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を気に入っています。
Userを更新
ソースコード
index.jsapp.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」に更新します。
Userを削除
ソースコード
index.jsapp.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); }); });確認
終わりに
冒頭でもお伝えしましたとおりjsonだけ返却するWebAPIがあれば十分でしたが、つい作成してしまいました。「FaaSやBaaSはサーバーレスで簡単」という触込みが多いですが、それぞれそれなりに独特のものがあると思っています。NoSQLにあまり触れていない筆者はRealtime Databaseに少し苦戦しましたが、簡易的なCRUD WebAPIにはなっていると思いますので、今後活用していくつもりです。
- 投稿日:2019-03-02T19:57:35+09:00
Firebase Cloud Functions, Realtime DatabaseでCRUD REST WebAPIを作る
前置き
外部からhttpsで接続出来るテスト用のWebAPIをとりあえず作成したいことがありました。固定のjsonを返却するだけのWebAPIを作成すれば良かったのですが、興味本位でRealtime Databaseも使って簡単なCRUD機能を提供するWebAPIを作りました。というよりもちょっと寄道して作ってしまいました
「ソースコードだけ見られれば十分」と思われる方々もいらっしゃると思いますが、ひととおり筆者が行った手順も含めて記事にしました。ソースコードはGitHubに公開しています。
※検証した実際のURLを掲載していますが削除する予定です。ご了承ください。なぜFirebaseを選んだか
jsonファイルをホスティングすれば良いのですが「せっかくならCRUDくらい付いていると嬉しい」という欲が出てしまいました。そのため、ホスティング案を除外して無料で動的なプログラムが動作する環境として、すぐ浮かんだ3つを検討しました。
Heroku
スキルセットの観点からRailsを利用することになりますが、APIモードでプロジェクトを作成するにしても、少し大掛かりな印象がありました。ほんの少しCRUD出来れば満足なので今回は採用を見送りました。AWS
API Gateway+Lambda+DynamoDBを利用することも検討しました。公式のサンプルにCloudFormationCloudFormation のymlもあり構築も楽できそうでした。一番興味はありましたが、API Gatewayの無料枠は12ヵ月の期限付きなので見送りました。他のサービスでも途中で規約が変わることがありますが現状で判断しました。Firebase
モバイル開発でも利用していて上記2つのサービスよりも馴染みがあったことと、自動的に課金されない明示的な無料枠の安心感も手伝って採用することにしました。Cloud FunctionsやRealtime Databaseに興味があるわけではありませんでした・・・消去法で選ばれてしまったFirebaseでありますが、気を取り直して前向きに作業を進めてます
事前準備
node.jsをインストール
$ node -v v10.15.2npmをインストール
$ npm -version 6.8.0Firebase CLIをインストール
npm install -g firebase-toolsプロジェクトを作成する
プロジェクトを作成したいパスに移動して以下のコマンドを実行します。
対話式で質疑についても例として記載します。ご参考まで。$ 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
- Firebaseコンソールでプロジェクト作成
![]()
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-restRealtime Databaseを準備する
- Realtime Databaseを作成
Cloud Firestoreという新しいデータベースも用意されていますが、従来のRealtime Databaseを利用することにします。
※スクリーンショットを撮直したのでプロジェクト名が異なっています。![]()
テストデータをインポート
データ取得用のファンクションの動作確認をするためテストデータを用意します。Firebaseコンソールの画像の赤い部分をクリックして「JSONをインポート」を選択します。
以下をインポートします。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 } ] }インデックスを作成
user_idをキーに検索するファンクションを作成するのでインデックスを作成しておきます。FirebaseコンソールのDatabaseでルールを選択します。以下を追加します。"users": { ".indexOn": ["user_id"] }ファンクションを作成してデプロイする
ようやく本題に入ります。ファンクションのソースコードはGitHubに公開しています。宜しければご覧ください。
プロジェクトを作成したディレクトリに移動
簡単に構成を確認します。ご察しと思われますがindex.jsにプログラムを書きます。├── firebase.json └── functions ├── index.js ├── node_modules ├── package-lock.json └── package.jsonUsersを全件取得するファンクションを作成
まずは最低限のnpmパッケージのみを利用して動作させてみます。index.jsconst 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); }); });デプロイ
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秒ほど掛かります
余談ですが、筆者は「修正が反映されていない」と思ったことが何度もありましたファンクションを実行
expressを導入してファンクションを書替える
WebAPIを機能追加するにあたりルーティングを体系的に記述したいのでexpressを導入します。
npmパッケージをインストール
functionsディレクトリに移動してnpmパッケージをインストールします。$ npm install express --save $ npm install body-parser --saveファンクションをexpress版に書換え
index.jsconst 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);ファンクションを実行
先ほどの手順でファンクションをデプロイ・実行して問題ないことを確認します。その他、CRUDのファンクションを作成
ここまででUserを全件を取得するファンクションを作成しました。続いて以下を作成します。
ここからはソースコードを掲載して簡単に動作確認する程度します。
必要に応じてカスタマイズしてください
- 特定のUserを1件取得
- Userを作成
- Userを更新
- Userを削除
特定のUserを1件取得
ソースコード
index.jsapp.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を作成
ソースコード
index.jsapp.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を気に入っています。
Userを更新
ソースコード
index.jsapp.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」に更新します。
Userを削除
ソースコード
index.jsapp.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); }); });確認
終わりに
冒頭でもお伝えしましたとおりjsonだけ返却するWebAPIがあれば十分でしたが、つい作成してしまいました。「FaaSやBaaSはサーバーレスで簡単」という触込みが多いですが、それぞれそれなりに独特のものがあると思っています。NoSQLにあまり触れていない筆者はRealtime Databaseに少し苦戦しましたが、簡易的なCRUD WebAPIにはなっていると思いますので、今後活用していくつもりです。
- 投稿日:2019-03-02T15:34:19+09:00
Puppeteerのセットアップから使い方まで〜ブラウザ操作の自動化〜
はじめに
Puppeteerを使ってみたら、とても簡単にブラウザ操作が自動化できたことに感動しました。
セットアップの方法とよく使うPuppeteerのAPIについてまとめましたので、これから使ってみようかなと思っている方の参考になれば嬉しいです。Puppeteerとは
読み方は「ぱぺてぃあ」。日本語だと人形使いという意味です。
Chromeブラウザを操作できるNodeのライブラリで、Chrome DevToolsのチームが開発を行っています。Puppetterの特徴
ブラウザ操作ができるツールとしては、Selenium Webdriverが有名です。
Selenium Webdriverとの違いは、Puppeteerはヘッドレスブラウザを使うことができるので、高速に動作させることができます。
また、PuppeteerはChromeのブラウザしか操作ができません。使ってみる
セットアップ
Node.jsが動く環境が必要です。
$ npm i puppeteerサクッと試したいだけならPuppeteerがWebツールを提供してくれているので、そちらを使うと便利です。
テストを書く
簡単なテストシナリオを書いてみます。
下記はGoogleのトップページに遷移して、画面のスクリーンショットを撮るテストシナリオです。test.jsconst 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.jsconst 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開発チームが開発していることもあって、ストレスなくブラウザ操作が実現できるのがとてもよかったです。あと環境構築もとても簡単なので、ちょっと試したい時にすぐ使えるのは便利だなーと思いました。
- 投稿日:2019-03-02T12:14:56+09:00
Google App Engineにmercury-parserをデプロイしてみる
発端
- 「Mercury Web Parser API を 2019/4/15 でシャットダウンするよ」とメールが来た。
- Chrome拡張で便利に使っていたので、なくなると困る。
- なのでとりあえずGoogle App Engineにデプロイする事にした。
作業環境
- Windows 10 Pro x64
- Git for Windows がインストール済み
- Node.js 8.15.1 がインストール済み
- App Engine スタンダード環境で Node.js を使用するためのクイックスタート などを参考にGoogle App Engineにプロジェクトを作成する
- Google Cloud SDK がインストール済み
mercury-parser リポジトリの取得
- 適当なフォルダでGitHubのリポジトリからクローンする。
git clone https://github.com/postlight/mercury-parser.gitapp.yaml app.js .gcloudignore package.json を用意する
- クローンしたフォルダに app.yaml と app.js と .gcloudignore を追加する。
app.yamlruntime: nodejs8app.jsconst 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にデプロイしてみる
手順
- mercury-parserをクローンしたフォルダをコマンドプロンプトで開き、
gcloud app create
を実行する。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) のひな形を作成する事が出来る。- yarn は
npm install -g yarn
でインストール出来る。yarn watch:test <入力したURLのドメイン名>
でテストが通るようになったら、yarn test_build
でdist/mercury.js
をビルドしてみる。yarn test_build && npm start
でローカル実行して試すも良し、yarn test_build && gcloud app deploy --project=<プロジェクトID>
でデプロイするも良し。ついでに
- 投稿日:2019-03-02T00:34:55+09:00
Alexa-hostedスキルを使用してAlexaスキルを作ってみる
AlexaスキルでGo
改め、Alexa-hostedスキルを使用してAlexaスキルを作ってみる
もともとはGo言語のwebhookを作って・・って考えてたのですが、途中から急に方向性かわったのでタイトルも変わりました。なので最初の方はタイトルにそぐわないです。すいません。
GO言語のはまた今度やります。
少し前にGo言語のハンズオンに参加してきました。
前半が講義、後半がハンズオンというあまりない形式の勉強会でした。
後半のハンズオンが資料と時間を与えられてもくもくする形式で緊張感があってなかなか楽しめました。
楽しんだ結果、go言語もそこそこ掴めた気がするので、手段が目的のシリーズをやってみます。何作ります?
と一緒です。google homeアプリの移植ですね。
アプリ名:リクガメの食べもの
リクガメにあげてもいい餌を教えてくれるアプリ。
俺: リクガメの食べ物につないで
Home: はい、リクガメの食べ物です
俺: ピーマンあげていい?
Home: はい。ピーマンはあげていい食べ物です。カルシウムは少ないです。(終了)まずはAlexa上で。
Alexaコンソールでのスキル作成については一度書いてますね。
一日かけて魚へんクイズスキルを作ってみる話キャプチャは省き、手順だけ記します。
- Alexaコンソールログイン
- スキルの作成
- スキル名:リクガメの食べ物
・・で、日本語、カスタムスキルと選択して進めますが・・お?なんだこれ?
Alexaがホスト?ベータ?
Alexa-hostedスキルとやらが増えてました。
google homeでfirebase functionを使って開発するイメージに近いんでしょうか。
nodejsだし。Goで作るつもりで進めてたんだけど、気になったのでいきなり方向転換です。
Alexa-hostedスキルでスキルを作ってみる
詳細はこちら
Alexa-hostedスキルを使用してスキルをエンドツーエンドで作成するよくわかんないけどとりあえず進めてみましょう。
インテント
- 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": "食べさせたい食べ物の名前を言ってください" } ] } ] } }バックエンド部分開発
コードエディタというのが使えるようになっております。
package.json
もありますね。ここでrequiredすればモジュールも普通に使えるんでしょうか。そんなわけでなんとなく作りました。
dialog flow上のエディタで作る場合と違って、複数ファイルにまたがって作れますね。SaveしたらDeployです。
なんか左下のLogs: Amazon CloudWatch
ってリンクからCloudWatchのログが見れます。テスト
そんなわけでなんとなくできました。
テレビ見つつ、ブログ書きつつで2時間くらいかかりました。この後はリリースって流れなんですが、GO言語で作り直すかも。
code
一応あげてみてます。
https://github.com/ikegam1/alexa-kamefood-nodejs-hosted
- 投稿日:2019-03-02T00:26:29+09:00
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 -s3.ソースコード
- ライブラリとFirebaseの設定
プロジェクト名/src/main.jsimport 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.jsimport 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 serve5.Unexpected console statement (no-console)が発生した場合の対処
- rulesに["no-console": "off"]を追加する
プロジェクト名/package.json"eslintConfig": { "rules": { "no-console": "off" } },6.その他
Googleログインのリダイレクトも試したが、リダイレクトが終わる前に完了処理が実行されてしまう。。。
対処方法がわからないため、断念。。。