- 投稿日:2020-03-25T23:39:58+09:00
【未完】楽天市場のAPIを使って買えるマスクを提案してくれるLINEBotを作ってみた
はじめに
ProtoOutStudioというイケイケなスクールの「LINE Bot+APIで表現してアウトプット」という課題で製作したものです。
こちらの1時間でLINE BOTを作るハンズオンの記事をベースにLINEBotを作成しました。
最近の新型ウィルスの影響で、ネットショッピングばかりしているのですが、ちょっと楽天市場のトップ画面を見るのに飽きてきたので、(マスクなどの)必要なものを、(売り切ればかりなので)在庫のある商品で、高額になりすぎて買う気がなくなってしまわない(金額の)範囲で、提案してもらえるLINEBotを考えました。
(Amazonは申請が大変そうに見えたのでお見送りしました)概要と作れなかったところ
概要
- LINEBotにほしいもの、「マスク」と入力したら
- 楽天市場のキーワード検索から「マスク」を検索
- 絞り込み検索で「購入可能」
- 「最安価でソート」
- 「最低金額○円以上」
- 「最高金額○円以下」
- 商品画像付き
- を5つくらい返してくれるBot
作れなかったところ
- ほしいもの 「マスク」と入力したら
- × → 楽天市場のキーワード検索から「マスク」を検索
- ○ → 絞り込み検索で「購入可能」
- ○ → 「最安価でソート」
- ○ → 「最低金額○円以上」
- ○ → 「最高金額○円以下」
- ○ → 商品画像付き
- × → を5つくらい返してくれるBot
入力したものをエンコードしてAPIのURLに入れて作成するところと、
(APIをベタでかくと動くBotはできたけど)これを複数表示するためにどこでFor文を回せばよいかわからなかった環境
Node.js v13.7.0
MacBook Pro macOS Mojave
Visual Studio Code v1.43.1できたもの
「マスクある?」と質問することで、楽天市場の中で在庫あり商品、最安価の商品名と商品URLと商品画像のあるマスクを提案してくれます。呼び出す楽天のAPIの分解
パラメーターは&でつないでね
https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706? format=json //楽天のAPIのURLだよjsonで返すよ keyword=mask //検索キーワード マスク genreId=501143 //ジャンル マスク availability=1 //販売可能は1 不可は0 sort=%2BitemPrice //価格が安い順に並べる minPrice=1000 //最低価格 maxPrice=4000 //最高価格 carrier=2 //何用の情報かPC: 0 mobile: 1 smartphone: 2 applicationId=***// 自分のapplicationIdコード
node.js'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); const PORT = process.env.PORT || 3000; const guidecat = 'https://i.gyazo.com/97840790b257952c89c59e7c176e114c.png'; const config = { channelSecret: '', channelAccessToken: '' }; const app = express(); app.post('/webhook', line.middleware(config), (req, res) => { console.log(req.body.events); Promise .all(req.body.events.map(handleEvent)) .then((result) => res.json(result)); }); const client = new line.Client(config); function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } let mes = event.message.text; if (mes.indexOf('?') > -1) { getNodeVer(event.source.userId); return client.replyMessage(event.replyToken, [{ type: 'image', originalContentUrl: guidecat, previewImageUrl: guidecat }, { type: "text", text: '在庫があって安いのはこれだよ〜' }]); } else { mes = event.message.text; console.log(mes); return client.replyMessage(event.replyToken, [{ type: 'image', originalContentUrl: guidecat, previewImageUrl: guidecat }, { type: "text", text: '〇〇?って聞いてほしいな' }]); } } const getNodeVer = async (userId) => { //キーワード部分にevent.message.textをエンコードしたものを入れたい //handleEvent の外でevent.message.textをどうやって持っていくのでしょう const res = await axios.get('https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706?format=json&keyword=mask&genreId=501143&availability=1&sort=%2BitemPrice&minPrice=1000&maxPrice=4000&carrier=2&applicationId=***'); const item = res.data; await client.pushMessage(userId, { type: 'text', // [0]をiにしたい text: item.Items[0].Item.itemName + "\n" + item.Items[0].Item.itemUrl + "\n¥" + item.Items[0].Item.itemPrice }); await client.pushMessage(userId, { type: 'image', originalContentUrl: item.Items[0].Item.mediumImageUrls[0].imageUrl, previewImageUrl: item.Items[0].Item.mediumImageUrls[0].imageUrl }); } app.listen(PORT); console.log(`Server running at ${PORT}`);参考サイト
- LINEBotでの画像の出しかた
- こういうとこまで行きたかった。。。
- 楽天ウェブサービス: API一覧
感想
深いAPIを操るの大変ですが自分好みの条件の商品をサクッと提案してもらえるBotはそれなりに便利そうなでちゃんと完成させねばです。
きっともっといい書き方や2回書かなくてもいいものとかたくさんある気がしますが、【未完】を取れるように早めにやっつけたいです。
- 投稿日:2020-03-25T23:39:58+09:00
【あと少し】楽天市場のAPIを使って買えるマスクを提案してくれるLINEBotを作ってみた
はじめに
ProtoOutStudioというイケイケなスクールの「LINE Bot+APIで表現してアウトプット」という課題で製作したものです。
こちらの1時間でLINE BOTを作るハンズオンの記事をベースにLINEBotを作成しました。
最近の新型ウィルスの影響で、ネットショッピングばかりしているのですが、ちょっと楽天市場のトップ画面を見るのに飽きてきたので、(マスクなどの)必要なものを、(売り切ればかりなので)在庫のある商品で、高額になりすぎて買う気がなくなってしまわない(金額の)範囲で、提案してもらえるLINEBotを考えました。
(Amazonは申請が大変そうに見えたのでお見送りしました)概要と作れなかったところ
概要
- LINEBotにほしいもの、「マスク」と入力したら
- 楽天市場のキーワード検索から「マスク」を検索
- 絞り込み検索で「購入可能」
- 「最安価でソート」
- 「最低金額○円以上」
- 「最高金額○円以下」
- 商品画像付き
- を5つくらい返してくれるBot
作れなかったところ
- ほしいもの 「マスク」と入力したら
- ○ → 楽天市場のキーワード検索から「マスク」を検索 (※3/27進めた!)
- ○ → 絞り込み検索で「購入可能」
- ○ → 「最安価でソート」
- ○ → 「最低金額○円以上」
- ○ → 「最高金額○円以下」
- ○ → 商品画像付き
- × → を5つくらい返してくれるBot
入力したものをエンコードしてAPIのURLに入れて作成するところと、
(APIをベタでかくと動くBotはできたけど)これを複数表示するためにどこでFor文を回せばよいかわからなかった環境
Node.js v13.7.0
MacBook Pro macOS Mojave
Visual Studio Code v1.43.1できたもの
https://t.co/E50LjkFK3l#protoout pic.twitter.com/xRjpcWEMYF
— 3yaka (@3yaka4) March 27, 2020「マスクある?」と質問することで、楽天市場の中で在庫あり商品、最安価の商品名と商品URLと商品画像のあるマスクを提案してくれます。
呼び出す楽天のAPIの分解
パラメーターは&でつないでね
https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706? format=json //楽天のAPIのURLだよjsonで返すよ keyword=mask //検索キーワード マスク genreId=501143 //ジャンル マスク availability=1 //販売可能は1 不可は0 sort=%2BitemPrice //価格が安い順に並べる minPrice=1000 //最低価格 maxPrice=4000 //最高価格 carrier=2 //何用の情報かPC: 0 mobile: 1 smartphone: 2 hits=3 //3商品取得 applicationId=*** //自分のapplicationIdコード
node.js'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); const PORT = process.env.PORT || 3000; const guidecat = 'https://i.gyazo.com/97840790b257952c89c59e7c176e114c.png'; const config = { channelSecret: '', channelAccessToken: '' }; const app = express(); app.post('/webhook', line.middleware(config), (req, res) => { console.log(req.body.events); Promise .all(req.body.events.map(handleEvent)) .then((result) => res.json(result)); }); const client = new line.Client(config); function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } let mes = event.message.text; if (mes.indexOf('?') > -1) { getNodeVer(event.source.userId, mes); return client.replyMessage(event.replyToken, [{ type: 'image', originalContentUrl: guidecat, previewImageUrl: guidecat }, { type: "text", text: '在庫があって安いのはこれだよ〜' }]); } else { // mes = event.message.text; return client.replyMessage(event.replyToken, [{ type: 'image', originalContentUrl: guidecat, previewImageUrl: guidecat }, { type: "text", text: '〇〇ある?って聞いてほしいな' }]); } } const getNodeVer = async (userId, mes) => { console.log(mes); let ownword = mes.split('ある?'); const res = await axios.get('https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706?format=json&keyword=' + encodeURIComponent(ownword) + '&genreId=501143&availability=1&sort=%2BitemPrice&minPrice=1000&maxPrice=4000&carrier=2&hits=3&applicationId=***'); const item = res.data; await client.pushMessage(userId, { type: 'text', text: item.Items[0].Item.itemName + "\n" + item.Items[0].Item.itemUrl + "\n¥" + item.Items[0].Item.itemPrice }); await client.pushMessage(userId, { type: 'image', originalContentUrl: item.Items[0].Item.mediumImageUrls[0].imageUrl, previewImageUrl: item.Items[0].Item.mediumImageUrls[0].imageUrl }); } app.listen(PORT); console.log(`Server running at ${PORT}`);参考サイト
- LINEBotでの画像の出しかた
- こういうとこまで行きたかった。。。
- 楽天ウェブサービス: API一覧
感想
深いAPIを操るの大変ですが自分好みの条件の商品をサクッと提案してもらえるBotはそれなりに便利そうなでちゃんと完成させねばです。
きっともっといい書き方や2回書かなくてもいいものとかたくさんある気がしますが、【未完】を取れるように早めにやっつけたいです。
- 投稿日:2020-03-25T21:47:57+09:00
[備忘録]Macの開発環境構築(anyenv + nodenv + Node.js)
構築の流れ
- Homebrewのインストール
- Gitのインストール
- anyenvのインストール
- nodenvのインストール
- Node.jsのインストール
環境
- macOS Catalina
- シェルは zsh
Homebrewのインストール
本家 のスクリプトを実行。
% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"Homebrewのバージョン確認。
% brew -vGitのインストール
Gitがインストールされているか確認する。
使い方が表示されればOK。% gitHomebrewでGitをインストール
% brew install gitanyenvのインストール
HomebrewかGitでanyenvをインストールする。
Homebrew版
% brew install anyenvGit版
% git clone https://github.com/anyenv/anyenv ~/.anyenvanyenvを初期化する。
% anyenv init
.zshrc
にパスを通す記述を追加する。% echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.zshrc % echo 'eval "$(anyenv init -)"' >> ~/.zshrcパス通したあとにシェルを再起動。
% exec $SHELL -lエラーが出た。
% ANYENV_DEFINITION_ROOT(/Users/[user-name]/.config/anyenv/anyenv-install) doesn't exist. You can initialize it by: > anyenv install --init指示通り
anyenv install --init
を実行。% anyenv install --init Manifest directory doesn't exist: /Users/[user-name]/.config/anyenv/anyenv-install Do you want to checkout ? [y/N]: y Cloning https://github.com/anyenv/anyenv-install.git master to /Users/[user-name]/.config/anyenv/anyenv-install... Cloning into '/Users/[user-name]/.config/anyenv/anyenv-install'... remote: Enumerating objects: 48, done. remote: Total 48 (delta 0), reused 0 (delta 0), pack-reused 48 Unpacking objects: 100% (48/48), done. Completed!完了!
anyenv使えるか確認。% anyenv anyenv 1.1.1 Usage: anyenv <command> [<args>] Some useful anyenv commands are: commands List all available anyenv commands local Show the local application-specific Any version global Show the global Any version install Install a **env uninstall Uninstall a specific **env version Show the current Any version and its origin versions List all Any versions available to **env See `anyenv help <command>' for information on a specific command. For full documentation, see: https://github.com/anyenv/anyenv#readmeプラグインを入れる
anyenvの更新を楽にするプラグインをインストールしておく。
まずはインストールするフォルダを作成。
-p
:上層のディレクトリが存在しなければ作成する。(この場合.anyenv
ディレクトリ)% mkdir -p ~/.anyenv/pluginsanyenv-update
URL:https://github.com/znz/anyenv-update
anyenvで入れた**env系とプラグインの更新をする。% git clone https://github.com/znz/anyenv-update.git ~/.anyenv/plugins/anyenv-updateanyenv-git
URL:https://github.com/znz/anyenv-git
anyenvで入れた**env系とプラグインのgitコマンドを実行する。% git clone https://github.com/znz/anyenv-git.git ~/.anyenv/plugins/anyenv-gitnodenvのインストール
anyenvでnodenvをインストール
anyenvでインストール可能な「**env」一覧を表示。
% anyenv install -lnodenvをインストール。
% anyenv install nodenvシェルを再起動。
% exec $SHELL -lnodenv使えるか確認。
% nodenv Usage: nodenv <command> [<args>] Some useful nodenv commands are: commands List all available nodenv commands local Set or show the local application-specific Node version global Set or show the global Node version shell Set or show the shell-specific Node version install Install a Node version using node-build uninstall Uninstall a specific Node version rehash Rehash nodenv shims (run this after installing executables) version Show the current Node version and its origin versions List installed Node versions which Display the full path to an executable whence List all Node versions that contain the given executable See `nodenv help <command>' for information on a specific command. For full documentation, see: https://github.com/nodenv/nodenv#readmeNode.jsのインストール
nodenvでNode.jsをインストール
nodenv installコマンドの使い方を表示。
% nodenv installインストール可能なNode.jsのバージョン一覧を表示。
% nodenv install -lNode.jsの公式サイトを確認して、推奨バージョンをインストール。
% nodenv install *.**.*インストールしたバージョンを確認。
% nodenv versionsグローバルとローカルのバージョンを指定。
# グローバル % nodenv global *.**.* # ローカル % cd [プロジェクトのパス] % nodenv local *.**.*参考URL
- 投稿日:2020-03-25T19:32:54+09:00
[Node.js勉強会]ExpressフレームワークでTODOアプリを作ろう
この記事について
Node.jsの概念や、Expressの使い方について詳しく書いてある記事ではありません。
ある程度の土台が用意してあるので、それを元に実際に手を動かして、TODOアプリを完成させることがこの記事の目的です。pugファイルと仮処理を記述 の部分までは、コピペで進めていただいても大丈夫です。
TODOの処理を記述 の部分から実際に、Node.jsの様々な書き方を試していただけたらと考えておりますNode.jsとは
Node.js はスケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動の JavaScript 環境です。
Node.js公式非同期で処理が実行されるので、DBとの通信のような時間がかかる処理を実行する場合は async, await の記述が必要です。
Expressとは
Node.js のための高速で、革新的な、最小限のWebフレームワーク
Express公式最小限の機能を持ったフレームワークです。基本的に利用するのは、ルーティングの機能かと思います。
LaravelやRailsのようなフレームワークの場合、プロジェクトを作成した時点でログイン機能、メール機能、など様々な機能が簡単に実装出来るように用意されていますが、Expressは全て自分でライブラリを選定して実装しないといけません。
そのため、フレームワーク自体の容量がとても小さいです。事前準備
・ Git
・ Docker手順1 dockerを利用して、Expressの実行環境を整える
スムーズにNode.jsの学習を進めるために、開発環境の構築をDockerで行います。
$ git clone https://github.com/KazukiSadasue/express-mysql-sample.git $ cd express-mysql-sample $ docker-compose up -d上記のコマンドで、dockerコンテナが起動して、サーバーも立ち上がります。
http://localhost
に接続して、以下の画像のように表示されたら実行環境は整いました。
*もしかしたら、最初の1回目にサーバーの立ち上げ失敗するかもしれません。
その場合は、docker-compose restart
で再度立ち上げると治ると思います。手順2 作成するアプリケーションについて
簡単なTODOアプリケーションを作成します。
ルーティングは以下のようにしようと思います。
パス メソッド 内容 / GET TODOトップ /create POST TODO作成 /done POST TODO完了 /delete POST TODO削除 TODOテーブルは以下のようにします。
カラム 型 説明 id INTEGER PRIMARYキー content STRING TODO内容(必須) isDone Boolean TODOの状態(必須、デフォルト0) deletedAt DATETIME 削除日時 createdAt DATETIME 作成日時 updatedAt DATETIME 更新日時 モデルの作成
テーブル設計から、TODOモデルを作成します。
modelsフォルダにtodo.jsを作成して、中身は以下のようにします。
todo.jsのコード
todo.js'use strict'; const loader = require('./sequelize-loader'); const Sequelize = loader.Sequelize; const Todo = loader.database.define( 'todos', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false, }, task: { type: Sequelize.STRING, allowNull: false, }, isDone: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: 0, }, }, { paranoid: true, freezeTableName: true, } ); module.exports = Todo;
次にapp.jsでモデルを読み込みます。
app.jsvar logger = require('morgan'); var helmet = require('helmet'); // モデルの読み込み +var Todo = require('./models/todo'); +Todo.sync();モデルの作成が終わったら、Dockerコンテナを再度立ち上げます。
$ docker-compose restartサーバーが再起動して、モデルに対応するテーブルが作成されます。
テーブルを確認するには、以下のコマンドを実行$ docker exec -it mysql bash # mysql -u root -p Enter Password: パスワードは入力せずにEnter mysql> use express-sample mysql> show tables; +--------------------------+ | Tables_in_express-sample | +--------------------------+ | todos | +--------------------------+todosテーブルが作成されているのが確認出来ました!
pugファイルと仮処理を記述
TODOアプリのデザインはBootstrapを用いて作成しました。
以下のコードをコピペして配置してください。
views/index.pugのコード
views/index.pugextends layout block content h1 TODOアプリ div form(action="/create", method="post").form-inline.mb-3 div.form-group input(type="text" name="task" placeholder="例: 晩御飯を作る").form-control button(type="submit").btn.btn-primary 追加 div.mb-3 h2 TODO form(name="todoform" method="post").mb-2 ul.list-group each todo in todoTasks li.list-group-item.px-5 input(name="todos", value=todo.id, type="checkbox", id=`todo-${todo.id}`).form-check-input label(for=`todo-${todo.id}`).form-check-label #{todo.task} if todoTasks.length button.btn.btn-success(onclick="done()").mr-2 達成 button.btn.btn-danger(onclick="deleteTodo()") 削除 else div TODOタスクはありません div h2 DONE form(name="doneform" method="post").mb-2 ul.list-group each done in doneTasks li.list-group-item.px-5 input(name="todos", value=done.id, type="checkbox", id=`todo-${done.id}`).form-check-input label(for=`todo-${done.id}`).form-check-label #{done.task} if doneTasks.length button.btn.btn-danger(onclick="deleteDone()") 削除 else div DONEタスクはありません script(src="javascripts/todo.js")
puglic/javascripts/todo.jsのコード
public/javascripts/todo.jsfunction done() { document.todoform.action="/done"; document.todoform.submit(); } function deleteTodo() { document.todoform.action="/delete"; document.todoform.submit(); } function deleteDone() { document.doneform.action="/delete"; document.doneform.submit(); }
routes/index.jsのコード
routes/index.jsvar express = require('express'); var router = express.Router(); // TODOトップページ router.get('/', async (req, res, next) => { const todoTasks = []; const doneTasks = [] res.render('index', { todoTasks, doneTasks, }); }); module.exports = router;
コードの記述が終わったら、
docker-compose restart
コマンドでコンテナを立ち上げなおします。TODOの処理を記述
ここまで見た目の部分と、モデルの定義まで終わりました。
あとは、routes/index.js の部分に処理を書き加えるだけでTODOアプリが完成します。
ここから先は、色々と手を動かしてNode.jsの書き方に触れていただけたらと思います。基本的な使い方
// ライブラリや、モデルの読み込みには、requireを利用する const { Op } = require('sequelize'); // getのルーティング router.get('/', () => {}); // postのルーティング router.post('/', () => {});Sequelizeの使い方
モデルの使い方は、 Sequelize公式ドキュメント を参考にします。
使い方の一部を抜粋してこちらに記述します。// async を記述することで、非同期関数を定義する。 router.get('/', async (req, res, next) => { // isDoneがtrueのデータを全件取得 // awaitを記述することで、DB処理の結果が返ってくるまで待つ const todos = await Todo.findAll(); })Sequelizeの使う上で、Node.jsは非同期通信であることに注意しないといけません。
async, awaitを利用して、DB処理が終わってから次の処理を行うようにしないと、不具合の原因となってしまいます。上記の内容を参考に、TODOアプリを完成させていただけたらと思います。
最後に
TODOアプリ完成しましたでしょうか?
最終的な私の実装例を記述致します。
routes/index.jsの実装例
routes/index.jsvar express = require('express'); var router = express.Router(); const Todo = require('../models/todo'); const { Op } = require('sequelize'); // TODOトップページ router.get('/', async (req, res, next) => { const todos = await Todo.findAll(); const todoTasks = todos.filter(todo => todo.isDone === false); const doneTasks = todos.filter(todo => todo.isDone === true); res.render('index', { todoTasks, doneTasks, }); }); // TODO作成 router.post('/create', async (req, res, next) => { await Todo.create({ task: req.body.task, }); res.redirect('/'); }); // TODO完了 router.post('/done', async (req, res, next) => { const todoModels = await getModelsByIds(req.body.todos); for(const todo of todoModels) { await todo.update({ isDone: true }); } res.redirect('/'); }); // TODO削除 router.post('/delete', async (req, res, next) => { const todoModels = await getModelsByIds(req.body.todos); for(const todo of todoModels) { await todo.destroy(); } res.redirect('/'); }); // リクエストのIDからTODOモデルを取得する async function getModelsByIds(ids) { if (typeof ids === 'string') { ids = ids.split(); } return await Todo.findAll({ where: { id: { [Op.in]: ids, } } }); } module.exports = router;
もっと説明の記述が必要、xxが分からない等ありましたらコメントお願い致します!
今回のコードの配置場所
https://github.com/KazukiSadasue/express-mysql-sample
- 投稿日:2020-03-25T18:50:13+09:00
LINE botを勉強会の受付に導入してみたい! ~connpass APIの紹介~
導入
仕事は別に勉強会を開いているのですが、有志による運営のため手が回らないこところが多いです。その中でも受付業務にフォーカスして手助けになるシステムのプロトタイピングを行っています。
私と同じくユーザコミュニティの運営やイベントの開催をされている方に読んでいただいて、少しでも負担が軽くなるシステム例としていただけたら嬉しいです。
勉強会といえば、IT関連の勉強会支援サービスconnpassですね。
と、いうことでconnpassのAPIを利用してLINE botと連携してみようと思いました。
この記事ではconnpass APIの紹介と簡単な使い方を書いています。connpass APIについて
こちらのconnpass APIからURLと検索クエリ、レスポンスについて解説されています。開催日時や場所だけでなく、サイトのHTMLまで入手できるとは....。
実際にconnpass APIを使ってみる
それでは試しにconnpass APIを使用し、どんなデータが返ってくるのか確かめてみようと思います。
実行環境はノートPC上とし、ngrokでトンネリングしてLINEサーバと通信します。今回の動きとしては、LINE botにテキストを投げかけるとイベント名「Noodl」で検索し、そのレスポンスに含まれるイベントURLを返します。
LINE botにテキストを投げるとこのように返答が来ます。
準備
使用するjsライブラリは次の通りです。npmコマンドでインストールしましょう。
- axios
- express
- @line/bot-sdk
npm initを行ったパスで次のコマンドを実行すると、必要なパッケージがインストールされます。
npm i axios express @line/bot-sdk実装
LINE botの準備とNode.jsはネットに沢山情報があるため割愛します。最終的に次のコードとなりました。
'use strict'; const axios = require('axios'); const express = require('express'); const line = require('@line/bot-sdk'); // LINE const PORT = process.env.PORT || 3000; const config = { channelSecret: 'LINE MessagingAPIのチャンネルシークレット', channelAccessToken: 'LINE Messaging APIのアクセストークン' }; const app = express(); app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない) app.post('/webhook', line.middleware(config), (req, res) => { console.log(req.body.events); //ここのif文はdeveloper consoleの"接続確認"用なので後で削除して問題ないです。 if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){ res.send('Hello LINE BOT!(POST)'); console.log('疎通確認用'); return; } Promise .all(req.body.events.map(handleEvent)) .then((result) => res.json(result)); }); const client = new line.Client(config); function handleEvent(event) { var event_url; if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // connpass APIにアクセス // このURLがconnpass APIにアクセスするURL axios.get('https://connpass.com/api/v1/event/?keyword_or=Noodl') .then(function (response) { // handle success // イベントURLを event_url = response.data.events[0].event_url; }) .catch(function (error) { // handle error console.log(error); }) .finally(function () { // always executed console.log(event_url); console.log( typeof event_url ); return client.replyMessage(event.replyToken, { type: 'text', text: event_url //実際に返信の言葉を入れる箇所 }); }); } app.listen(PORT); console.log(`Server running at ${PORT}`);重要なところだけ解説します。
URL指定
クエリの”keyword_or”が検索ワードを挿入する部分です。
https://connpass.com/api/v1/event/?keyword_or=NoodlURLを抜き出す
次の処理で、大量にあるデータの中からイベントURLを抜き出しています。
event_url = response.data.events[0].event_url;LINE botで返答
次の処理でイベントURLをユーザに送信しています。タイプはtextで問題ないです。
return client.replyMessage(event.replyToken, { type: 'text', text: event_url //実際に返信の言葉を入れる箇所 });おわりに
今回はconnpass APIを使用し、LINE botと連携してイベントURLを返すデモとなりました。他にも面白そうなデータ(イベント開催場所の緯度経度、日時等)があるので拡張していきたいと思います。
例えば、
- Google スプレッドシートと連携して参加者の出欠確認。
- GPSを拾って会場にいるときだけ受付可能にしたり。
- イベント開催場所の最寄りのラーメン屋さんを紹介してくれたり...。考えれば考えるほど面白いのがAPIの醍醐味ですね(笑)
- 投稿日:2020-03-25T15:02:32+09:00
create-react-appを卒業して自分でReact + TypeScriptの開発環境を作れるようになるということ
これまでReactの環境構築をする時はcreate-react-appに頼りっきりでしたが、いい加減自分で作れないとまずいなと思い忘備録も兼ねて残しておきます。
また、せっかくTypeScriptも使うのでwebpack.config.js
もTypeScriptで書けるようにしたいと思います。
最終的なディレクトリ構成は次のようになります。
. ┃━━ public ┃ ┗━ index.html ┃━━ src ┃ ┃━ index.tsx ┃ ┗━ App.tsx ┃━━ package.json ┃━━ package-lock.json ┃━━ tsconfig.json ┗━━ webpack.config.tsそれではやっていきましょう。
1. ディレクトリの初期化
適当なディレクトリを作って
npm init
するだけです。mkdir hogehoge cd hogehoge npm init -y
-y
オプションはお好みで(つけると全部初期値で自動的に初期化されます)。
初期化が終了するとpackage.json
ができているはずです。2. モジュールのインストール
React + TypeScriptの環境構築なので、とりあえず必要そうなモジュールをインストールしていきます。
バンドラにはWebpackを利用します。npm i -d react react-dom npm i -D typescript ts-loader webpack webpacl-cli webpack-dev-server @types/node @types/react @types/react-dom
@types/node
の中にwebpack.Configuration
が定義されており、これがWebpackの設定情報の型です。
これを使ってwebpack.config.ts
を書いていきます。webpack.config.tsimport { Configuration } from 'webpack' import path from 'path' const config: Configuration = { mode: 'development', entry: './src/index.tsx', module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, resolve: { extensions: [ '.tsx', '.ts', '.js', ], }, devtool: 'inline-source-map', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, } export default config感のいい人はお気づきかもしれませんが、現段階だと開発用サーバーの設定である
devServer
プロパティがありません(書いてみるとエラーになるはずです)。
そこで@types/webpack-dev-server
をインストールします。npm i -D @types/webpack-dev-serverこれで
webpack-dev-server
の設定ができるようになりました。webpack.config.ts
に設定を追記します。webpack.config.tsconst config: Configuration = { // ... devServer: { contentBase: path.resolve(__dirname, 'dist'), }, // ... }これでエラーが消えたはずです。
HtmlWebpackPlugin
の設定詳しくは割愛しますが、このプラグインを使うとテンプレートのHTMLのbodyタグの末尾にscriptタグを勝手に挿入してくれるので便利です。
というわけでインストールします。npm i -D html-webpack-plugin @types/html-webpack-pluginwebpack.config.tsimport HtmlWebpackPlugin from 'html-webpack-plugin' const config: Configuration = { // ... plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', }), ], // ... }テンプレートとなるHTMLファイルを
public/index.html
に作成します。public/index.html<html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>your app title</title> </head> <body> <div id="root"></div> </body> </html>ここではテンプレートHTMLのパスしか設定しませんが、
title
など他にもいくつかオプションがあるので気になる方は調べてみてください。3. TypeScriptの設定
tsconfig.json
を作成・編集します。tsconfig.json{ "compilerOptions": { "outDir": "./dist/", "noImplicitAny": true, "module": "es6", "target": "es5", "jsx": "react", "allowJs": true, "allowSyntheticDefaultImports": true } }
src
ディレクトリ内に、エントリポイントとなるindex.tsx
を作っていきます。とりあえず簡単なものだけ。src/App.tsximport React, { FC } from 'react' export const App: FC = () => <div>Hello World!</div>src/index.tsximport React from 'react' import ReactDOM from 'react-dom' import { App } from './App' ReactDOM.render(<App />, document.getElementById("root"))4. npm scriptsを設定する
package.json
を編集します。
開発用サーバーを立ち上げるコマンドと、ビルド用のコマンドを用意します。package.json{ // ... "scripts": { "dev": "webpack-dev-server --open", "build": "webpack" }, // ... } }ここまででほぼ完成みたいなものですが、このままだとコンパイルできません。
5. 設定ファイルを読み込めるようにする
今のままだと設定ファイルが
.ts
ファイルなので読み込めません。そこでts-node
というモジュールを使用します。
モジュール名の通りですが、Node.jsがTypeScriptを直接読めるようにするものです。npm i -D ts-nodeこれでコンパイルが通る・・・ようにはなりません。もう1ステップ必要です。
作成したwebpack.config.ts
ですが、import/export文を使用しているためこのままだと使えません。
これが使えるようにtsconfig.json
を編集します。tsconfig.json{ "compilerOptions": { // ... "esModuleInterop": true, "module": "commonjs", // ... } }詳細な説明は省きますが、「CommonJSモジュールだよ~」と教えてあげることで使えるようになります。
とりあえずはこれにて終了です。
npm run dev
なりnpm run build
して開発に励みましょう。6. おわりに
ところどころ端折りましたが、これでReact + TypeScriptの開発環境は作れたはずです。
似たような記事は結構あるのですが、同じReact + TypeScriptでもWebpackの設定までTypeScriptで行っているものは少なかったので記事にしました。型定義があるぶん補完も効くので書きやすいです。こんな記事でも誰かのお役に立てば幸いです。ご覧頂きありがとうございました。
- 投稿日:2020-03-25T14:26:20+09:00
Node.js を持ち歩けるようにして、どこでも簡易WEBサーバを起動出来るようにする
Node.jsで便利なライブラリをつくったとしても、お客さまのPCやサーバの本番環境に Node.jsの実行環境がないケース もあります。「Node.jsの実行環境を持ち歩けたらなぁ、、」ということでググってみたらこの記事が。。
インストーラなどの実行が不要な 解凍すればすぐに使えるNode.js実行環境の構築手順です。コレを使えば、実行環境ごとZipでアーカイブしたファイルを作成し作業環境でそれを解凍すればNode.js環境の構築完了、なんて事ができそうです。
ついでに、そのNode.js上でWebサーバを立ち上げることで、どこでも簡易WEBサーバが起動出来るようにしてみます。
Node.js実行環境の構築
公式からバイナリをダウンロード。Windows Binary (.zip) の64-bitを選択します。2020/03/15時点「node-v12.16.1-win-x64.zip」が最新版のようです。
ダウンロード後、任意の場所に解凍しましょう。たとえばT:\Tools\nodejs_portable
などなど。さて上記ディレクトリには、
node.exe
などが入っていると思いますが、同じディレクトリに起動コマンドrun.bat
を置いておきます。中身はテキストでこんな感じ。@echo off set PATH=%cd%;%PATH% set NODE_PATH=%cd%\node_modules\npm\node_modules;%cd%\node_modules\npm cmdいわゆる、このディレクトリにパスを通しているだけです。
基本的な環境構築は以上です。実行してみる
さてエクスプローラ上で
run.bat
をダブルクリックして、node.js 実行環境を起動してみましょう。T:\Tools\nodejs_portable>こんな感じで起動すればOKです。
T:\Tools\nodejs_portable> node --version v12.16.1 T:\Tools\nodejs_portable> npm --version 6.13.4 T:\Tools\nodejs_portable>一応 Hello Worldを。
T:\Tools\nodejs_portable> mkdir app T:\Tools\nodejs_portable> cd app T:\Tools\nodejs_portable\app> type index.js ← あらかじめメモ帳とかで作成しておきましょう console.log('Hello World.') T:\Tools\nodejs_portable\app> node index.js ←実行 Hello World. T:\Tools\nodejs_portable\app>実行OKですね。つづいて npm なども動作確認してみます。
(上でつくったappは一旦削除したとして) T:\Tools\nodejs_portable> mkdir app T:\Tools\nodejs_portable> cd app T:\Tools\nodejs_portable\app> npm init -y Wrote to T:\Tools\nodejs_portable\app\package.json: { "name": "app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }下記のような
index.js
をつくってみました。T:\Tools\nodejs_portable\app> type index.js const request = require('request') request.get('http://www.yahoo.co.jp', (err, res, body) => { console.log(body) })
npm i
でインストールします。T:\Tools\nodejs_portable\app> > npm i --save request npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142 npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN app@1.0.0 No description npm WARN app@1.0.0 No repository field. + request@2.88.2 added 47 packages from 58 contributors and audited 63 packages in 9.683s found 0 vulnerabilities T:\Tools\nodejs_portable\app> node index.js .... ばばっってYahooのサイトがなんか返ってくればOK T:\Tools\nodejs_portable\app>OKそうですね!
-- 注意 --
いわゆるグローバルインストールnpm install -g xx
について。
グローバルインストールした際は、T:\Tools\nodejs_portable\node_modules
にインストールされる想定だったのですが、すでに Node.jsがインストールされている環境のばあいそちらのディレクトリにインストールされてしまいました。すでにNode.jsがインストールされている環境では、グローバルインストールは実施しない方がよさそうです。
ご注意ください。簡易WEBサーバの構築
さて、さいごにWEBサーバです。Node.jsを用いたWEBサーバの構築は
[Node.js] 簡単なWebサーバとして静的ファイル配信/ディレクトリ一覧機能のサンプルコード
ココをまるまる参考にさせていただきました!感謝です。さてやってみます。
(上でつくったappは一旦削除したとして) T:\Tools\nodejs_portable> mkdir app T:\Tools\nodejs_portable> cd app T:\Tools\nodejs_portable\app> npm init -y Wrote to T:\Tools\nodejs_portable\app\package.json: { "name": "app", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" } T:\Tools\nodejs_portable\app> npm i --save express npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN app@1.0.0 No description npm WARN app@1.0.0 No repository field. + express@4.17.1 added 50 packages from 37 contributors and audited 126 packages in 10.259s found 0 vulnerabilitiesさて
index.js
(WEBサーバのプログラム)は以下でOKです。単純です。T:\Tools\nodejs_portable\app>type index.js 'use strict'; const express = require('express'); const app = express(); app.use(express.static('./dist')); app.listen(process.env.PORT || 3000);ポート番号:3000 で、ドキュメントルートを
./dist
としたWEBサーバを起動するためのJavaScriptコードが完成です。疎通のため単純なhtmlを
./dist
配下に配置します。T:\Tools\nodejs_portable\app> mkdir dist T:\Tools\nodejs_portable\app> type dist\index.html <html> <body> <h1>テスト</h1> </body> </html> T:\Tools\nodejs_portable\app>実行してみる
さてWEBサーバを起動してみます。
T:\Tools\nodejs_portable\app> node index.js ...起動したようです。
別のコマンドプロンプトからアクセスしてみます。(あもちろんWEBブラウザでもよいです)
C:\Users\xx> curl http://localhost:3000/index.html <html> <body> <h1>テスト</h1> </body> </html>OKそうですね!
例によって、インストールディレクトリ(例だと
T:\Tools\nodejs_portable
ですね) をZip化して持ち運べば、任意の場所で解凍すればOKの、簡易WEBサーバの完成です。静的なWEBサーバですが、ちゃちゃっとつかうフロントのサーバとしては十分でしょう。おつかれさまでした!
関連リンク
- 投稿日:2020-03-25T13:10:30+09:00
obniz+赤外線LED+TypeScriptでリモコンコンセント(OCR-05W)を動かす
はじめに
こんにちは。電気毛布エンジニアの@tmitsuoka0423です。
昨日書いた「obniz+赤外線LEDでリモコンコンセント(OCR-05W)を動かす」では、obnizのパーツライブラリページ上で動作確認しましたが、今回はTypeScriptで実装していきます。
ソースコードはhttps://github.com/tmitsuoka0423/obniz-ocr-05wで公開しています。【クラウドファンディング実施中!】mouful(モウフル)
「電気毛布につけるだけ!スマホで布団の温度調節できるmouful(モウフル)」
というクラウドファンディングを実施しています。
電気毛布の
△中~強にして寝る→暑くて体がカラカラになってしまう
△弱にして寝る→温まらなくて眠れない
△強→弱にして寝る→面倒臭くてやらなくなる
という課題を解決します。○1週間レンタルでフィードバックしてくれる方
○開発メンバー
を募集しています!クラウドファンディングページはこちら→https://camp-fire.jp/projects/view/220261
今回使うもの
品名 画像 価格 obniz Board 5,500円くらい 赤外線センサー OSRB38C9AA 50円くらい 赤外線LED OSI5FU5111C-40 20円くらい リモコンコンセント OCR-05W 1,200円くらい obnizと素子を接続する
リモコンの赤外線信号を解析する
まずは、赤外線センサーを使ってリモコンの信号を受信するプログラムを作成します。
(確認してないですが、このプログラムを使ってOCR-05W以外のリモコンの信号も受信できると思います。)receive.tsimport Obniz from 'obniz'; const obniz = new Obniz('OBNIZ_ID_HERE'); obniz.onconnect = async () => { const sensor = obniz.wired('IRSensor', { vcc: 0, gnd: 1, output: 2 }); console.log('リモコンのボタンを短く押してください…'); sensor.start(function (arr) { console.log('受信したシグナル:'); console.log(JSON.stringify(arr)); obniz.close(); }); }これをコンパイルして実行する。
と表示されるので、リモコンのボタンを押すと、めっちゃ長い信号が受信される。昨日から作っているobniz+赤外線LED+リモコンコンセントをTypeScriptで書き直し中。
— Takahiro Mitsuoka (@tmitsuoka0423) March 25, 2020
まずはリモコン受信。#obniz #iot pic.twitter.com/Uj8A0TqliA無事受信できた。
リモコンコンセントにobnizから信号を送信する
プログラムは以下の通り。
send.tsimport Obniz from 'obniz'; const on: (0 | 1)[] = [1, 1, 1, 1, 1, (略)]; // 受信したリモコンの信号をコピペする。 const off: (0 | 1)[] = [1, 1, 1, 1, 1, (略)]; // GitHubに全量を載せています。 const obniz = new Obniz('OBNIZ_ID_HERE'); obniz.onconnect = async () => { const led = obniz.wired('InfraredLED', { anode: 3, cathode: 4 }); led.send(on); // led.send(off); console.log('信号を送信しました。'); obniz.close(); }動かしてみる。
obniz+赤外線LED+TypeScriptで信号送信もできた。#obniz #iot pic.twitter.com/6fFlRiVIyr
— Takahiro Mitsuoka (@tmitsuoka0423) March 25, 2020まとめ
obniz+TypeScriptは補完が効くようになるのでオススメ。
- 投稿日:2020-03-25T13:04:38+09:00
node.jsのテンプレートリテラルと+による文字列結合の速度差
はじめに
文字列を取り扱う際、毎回テンプレートリテラルと文字列連結だとどちらの方が早いのだろう。という疑問が毎回頭を過ぎってました。
ということで軽くですがテストしました。環境
- Windows 10 pro
- node.js v13.0.1
試す
計測には
performance
を使用しました。
10^7回文字列連結する処理を10回繰り返し、平均実行時間を出しました。
なお実行時間の単位はms
です。まずは単純な文字列同士の結合です。
const { performance } = require("perf_hooks"); const a = "hogehoge"; const b = "fugafuga"; /** concat_text */ let concat_sum = 0; for (let i = 0; i < 10; i++) { const start = performance.now(); for (let j = 0; j < 10 ** 7; j++) { const hoge = a + b; } const time = performance.now() - start; console.log(time); concat_sum += time; } console.log("concat_text average: " + concat_sum / 10); /** template_literal */ let template_sum = 0; for (let i = 0; i < 10; i++) { const start = performance.now(); for (let j = 0; j < 10 ** 7; j++) { const hoge = `${a}${b}`; } const time = performance.now() - start; console.log(time); template_sum += time; } console.log(`template_literal average: ${template_sum / 10}`);result34.08500099973753 39.77130000013858 11.797801000066102 11.937899000011384 11.332999000325799 11.727699999697506 11.424000999890268 12.096899999771267 11.576799999922514 11.971499999985099 concat_text average: 16.772190099954607 116.54179899999872 163.70389899984002 121.10849899984896 71.12280000001192 71.46999999973923 71.70690099988133 74.72199900029227 72.5004999996163 71.70279900031164 71.67179999966174 template_literal average: 90.62509959992022
単純な文字列の結合だと
+
で結合した方が若干早いようです。
連結する数を4個(a + b + c + d)
に増やした場合以下のようになりました。
+
は実行時間に然程影響は無かった- テンプレートリテラルは実行時間が倍になった。
次に連結の際にnumberの計算もするよう変更し、前テストと同じ回数行いました。
const { performance } = require("perf_hooks"); const a = "hogehoge"; /** concat_text */ let concat_sum = 0; for (let i = 0; i < 10; i++) { const start = performance.now(); for (let j = 0; j < 10 ** 7; j++) { const hoge = a + (8 + 2); } const time = performance.now() - start; console.log(time); concat_sum += time; } console.log("concat_text average: " + concat_sum / 10); /** template_literal */ let template_sum = 0; for (let i = 0; i < 10; i++) { const start = performance.now(); for (let j = 0; j < 10 ** 7; j++) { const hoge = `${a}${8 + 2}`; } const time = performance.now() - start; console.log(time); template_sum += time; } console.log(`template_literal average: ${template_sum / 10}`);result270.05879899999127 226.13540100026876 226.2756000002846 230.80309899989516 245.48059999989346 221.4089999999851 226.89270000020042 227.69470000034198 225.0682989996858 220.0896009998396 concat_text average: 231.9907799000386 41.350199999753386 114.77740000002086 38.32340000011027 109.85499900020659 48.483599999919534 102.05140100000426 43.74809999996796 92.99629899999127 40.28349999990314 42.15730100031942 template_literal average: 67.40262000001967
テンプレートリテラルの方が早いですね。
連結する数を4個(a + (8 + 2) + (9 + 1) + (10 - 3))
に増やした場合以下のようになりました。
- テンプレートリテラルは実行時間に然程影響は無かった
+
は実行時間が倍になった。まとめ
+
を用いた連結は複雑な処理を入れるかどうかでタイムに差が出た- テンプレートリテラルは複雑な処理をする際にタイムにブレが出にくい
- 連結数を増やした場合、早い方のタイムはブレが出にくい
といってもここまで大量に処理しない限り誤差の範囲ですので個人の好みになって来ると思います。
私は見やすさ的にもタグ関数的にもテンプレートリテラルを使いたいと思います。また、今回はnodeのみで実験しましたがブラウザ上だとまた違った結果になりそうです。
初学者ですので間違い等あれば訂正いただけると嬉しいです。
- 投稿日:2020-03-25T12:42:52+09:00
Windows10にてReact公式チュートリアル用のローカル開発環境構築時、node最新化、npx実行で突っかかった備忘録
はじめに
React公式チュートリアルをやってみたところ
ローカル開発環境構築中にnode最新化、npx実行で突っかかったので、備忘録。実施したチュートリアル
Reactチュートリアル(日本語)なお、自分はReactは触ったことが無い。
環境
- windows10 64bit
- node 6.9.4 -> 13.11.0
- npm 6.13.7
ローカル開発環境の構築
チュートリアルを進める方法には、2つのオプションがある。
オプション 1: ブラウザでコードを書く
始めるのに一番手っ取り早い方法です!
...
オプション 2: ローカル開発環境
これは完全にオプションであり、このチュートリアルを進めるのに必須ではありません!今回はオプション2を実施した。
node最新化
既存nodeバージョン確認
- 最新の Node.js がインストールされていることを確かめる。
とあるので、nodeを最新化する。
現行入っているバージョンを確認。$ node -v v6.9.4nodistでnodeインストール
入れ替え方法は下記を参考にした。
windowsでNode.jsをバージョン管理する$ nodist dist ... 13.9.0 13.10.0 13.10.1 13.11.0 $ nodist 13.11.0 13.11.0 Installing 13.11.0 13.11.0 [===============] 53396/53396 KiB 100% 0.0s Installation successful. $ nodist + 13.11.0 13.11.0 $ node -v v6.9.4
何故かnodeのバージョンが変わっていない。既存nodeアンインストール
うーん。わからないので、「プログラムと機能」から「Node.js」をアンインストール。
再度nodeバージョン確認
その後、nodeのバージョンを確認。
$ node -v v13.11.0npm最新化
nodeが最新化された。npmも最新化しておく。
$ nodist npm match npm matc $npm -v 6.13.7新しいプロジェクト作成
npxでcreate-react-app実行
- Create React App のインストールガイドに従って新しいプロジェクトを作成する
チュートリアルに従いnpxを実行する。
$ npx create-react-app my-app 'npx' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。npxが認識されなかった。
nodistでnodeインストール時にnpxもインストールされるはずだが……。npxを手動インストール
https://paradox-tm.hateblo.jp/entry/2018/04/25/115243
上記記事で同様の現象が出ていたので、記事を参考に強制的にnpxをインストール。$ npm install -g npx ... added 493 packages from 654 contributors in XX.XXXs $ npx create-react-app my-app ... Happy hacking!プロジェクトの動作確認
以降、チュートリアルに従いソースファイル作成し、最後にnpm startでサーバ起動。
http://localhost:3000 を開くと、空の三目並べの盤面が表示されることを確認できた。以上です。
- 投稿日:2020-03-25T08:23:45+09:00
Node.jsでファイル出力
下記の『同期処理メソッド「writeFileSync」』を参考に実施
node.jsでファイルの入出力操作
- 投稿日:2020-03-25T04:28:58+09:00
N予備校の教材でAjaxでCORSを試してみる
やりたいこと
N予備校のAjaxのページに「同一生成ポリシーを満たさない場合CORSにひかかって通信できません」と書かれているが、そのあたりの回避策について特に紹介されていなかったので
Access-Control-Allow-Origin
を試してみる。前提知識
- CORS https://developer.mozilla.org/ja/docs/Web/HTTP/CORS
- N予備校 https://www.nnn.ed.nico/courses/497/chapters/6891
- この教材をそのまま使います(非会員は見れません)
環境
- Vagrant ubuntu(Node.jsにて上記のリンクのサーバが稼働している)
- 手元のマシン(Mac)
実験
- 手元のマシンのブラウザから、Vagrant上のNode.jsに対してAjax通信を行い失敗することを確認する
- CORSを回避するためにサーバー側にヘッダーを挿入する
修正前
- LoadAvgが表示されない
- コンソールログをみると以下のエラーが発生している
Access to XMLHttpRequest at 'http://localhost:8000/server-status' from origin 'http://localhost' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.コード修正
server-status.js'use strict'; const express = require('express'); const router = express.Router(); const os = require('os'); router.get('/', (req, res, next) => { res.setHeader('Access-Control-Allow-Origin', 'http://localhost') ?追加 res.json({ loadavg: os.loadavg() }); }); module.exports = router;
Access-Control-Allow-Origin
を追加することで特定のOriginからの通信を許容する修正後
- LoadAvgが表示された
Access-Control-Allow-Origin: http://localhost ? 追加されている Connection: keep-alive Content-Length: 19 Content-Type: application/json; charset=utf-8 Date: Tue, 24 Mar 2020 19:25:17 GMT ETag: W/"13-78iQH47CLcJ0F+4s06WtpVC9PNc" Strict-Transport-Security: max-age=15552000; includeSubDomains X-Content-Type-Options: nosniff X-DNS-Prefetch-Control: off X-Download-Options: noopen X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block
- 投稿日:2020-03-25T01:07:51+09:00
GraphQL Mesh は何を解決するのか? ~ Qiita API を GraphQL でラップして理解する GraphQL Mesh ~
GraphQL Mesh とは
The Guild から GraphQL Mesh が発表されました。
? GraphQL Mesh - Query Anything, Run Anywhere ?https://t.co/PlZpAC9b54
— Urigo (@UriGoldshtein) March 23, 2020
? I'm very proud to announce our new open source library - GraphQL Mesh!
Use #GraphQL to query:
? openapi/Swagger
? gRPC
? SOAP
? SQL
? GraphQL
? More!
Without changing the source!
Thread 1/5 pic.twitter.com/xo0G5smUwpGraphQL Mesh は REST API や gRPC などの既存のバックエンド API サービスと接続するプロキシとして機能します。
GraphQL Mesh は、開発者が他の API 仕様(gRPC、OpenAPI、Swagger、oData、SOAP、GraphQL など)で記述されたサービスに対して、GraphQL のクエリを通じて簡単にアクセス可能にすることを目的として作られました。従来、GraphQL プロキシを実装するためには、バックエンド API サービスに対して以下の作業を行う必要がありました。
- その API 仕様を読み解き、
- GraphQL サーバを構築し、
- スキーマ、リゾルバ、バックエンド API との通信処理を実装する
複数のバックエンド API をラップする GraphQL サーバを実装するためだけに多大な労力を割いていたのです。
もちろん、openapi-to-graphql のように、OpenAPI 定義を GraphQL のスキーマに読み換えるツールや、スキーマ定義からモックサーバを構築する graphql-tools などは登場していました。
今回登場した GraphQL Mesh は革新的です。バックエンド API の API 仕様さえあれば、そのバックエンド API に対して GraphQL クエリが即座に実行できる GraphQL プロキシが手に入ります。本記事では GraphQL Mesh の簡単な使用方法とアーキテクチャの構成パターンについて解説します。
※ 本記事は こちら の記事を参照しています。
使用方法
バックエンドに OpenAPI で記述された REST API サービスがあることを想定して、GraphQL Mesh によるプロキシサーバを構築します。今回バックエンドの API は Qiita API を使用します。
1. インストール
GraphQL Mesh はいくつかのコアライブラリを組み合わせてインストールします。
$ yarn add graphql \ @graphql-mesh/runtime \ @graphql-mesh/cli \ @graphql-mesh/openapi使用可能な API(と実装予定の API)は 3/25 現在、以下の通りです。
Package Status Supported Spec @graphql-mesh/graphql
Available GraphQL endpoint (schema-stitching, based on graphql-tools-fork
)@graphql-mesh/federation
WIP Apollo Federation services @graphql-mesh/openapi
Available Swagger, OpenAPI 2/3 (based on openapi-to-graphql
)@graphql-mesh/json-schema
Available JSON schema structure for request/response @graphql-mesh/postgraphile
Available Postgres database schema @graphql-mesh/grpc
Available gRPC and protobuf schemas @graphql-mesh/soap
Available SOAP specification @graphql-mesh/mongoose
Available Mongoose schema wrapper based on graphql-compose-mongoose
@graphql-mesh/odata
WIP OData specification 2. 設定ファイルにバックエンド API の API 仕様を記述する
次に、
.meshrc.yaml
というファイルを作成し、バックエンド API の API 仕様を記述しましょう。今回は OpenAPI を使用します。他にも gRPC、oData、SOAP、GraphQL などをサポートしています。.meshrc.yaml
はプロジェクトのルートディレクトリに配置します。sources: - name: Qiita handler: openapi: source: ./qiita.openapi.yamlQiitaAPI の OpenAPI 定義
.qiita.openapi.yaml
は以下のように記述しています。
Qiita APIの仕様 (OpenAPI)
swagger: "2.0" info: version: 0.0.1 title: Qiita API host: "qiita.com" basePath: "/api/v2" schemes: - https consumes: - application/json produces: - application/json paths: "/tags/{tagId}/items": get: parameters: - in: path name: tagId type: string required: true - $ref: "#/parameters/pageParam" - $ref: "#/parameters/perPageParam" responses: "200": description: 指定されたタグが付けられた投稿一覧を、タグを付けた日時の降順で返します。 schema: title: タグ記事一覧 type: array items: $ref: "#/definitions/Item" "/users/{userId}": get: parameters: - in: path name: userId type: string required: true responses: "200": description: ユーザを取得します。 schema: $ref: "#/definitions/User" "/users/{userId}/items": get: parameters: - in: path name: userId type: string required: true - $ref: "#/parameters/pageParam" - $ref: "#/parameters/perPageParam" responses: "200": description: ユーザの投稿の一覧を作成日時の降順で返します。 schema: title: ユーザー記事一覧 type: array items: $ref: "#/definitions/Item" "/items": get: parameters: - $ref: "#/parameters/pageParam" - $ref: "#/parameters/perPageParam" - name: query in: query description: 検索クエリ required: false type: string responses: "200": description: 投稿の一覧を作成日時の降順で返します。 schema: title: 記事一覧 type: array items: $ref: "#/definitions/Item" parameters: pageParam: in: query name: page description: ページ番号 (1から100まで) type: number perPageParam: in: query name: per_page description: 1ページあたりに含まれる要素数 (1から100まで) type: number definitions: ErrorMessage: description: エラーの内容を説明するmessageプロパティと、エラーの種類を表すtypeプロパティで構成されます type: object properties: message: type: string type: type: string Group: description: "Qiita:Teamのグループを表します。" type: object properties: created_at: type: string id: type: integer name: type: string private: type: boolean updated_at: type: string url_name: type: string Tag: description: タグ properties: name: type: string example: Ruby versions: type: array items: type: string example: 0.0.1 User: properties: description: description: 自己紹介文 type: string facebook_id: type: string followees_count: description: このユーザがフォローしているユーザの数 type: integer followers_count: description: このユーザをフォローしているユーザの数 type: integer github_login_name: type: string id: type: string items_count: description: "このユーザが qiita.com 上で公開している投稿の数 (Qiita:Teamでの投稿数は含まれません)" type: integer linkedin_id: type: string location: type: string name: type: string organization: type: string permanent_id: description: ユーザごとに割り当てられる整数のID type: integer profile_image_url: description: 設定しているプロフィール画像のURL type: string twitter_screen_name: type: string website_url: type: string Item: type: object properties: rendered_body: type: string body: type: string coediting: type: boolean comments_count: type: integer created_at: type: string id: type: string likes_count: type: string private: type: boolean reactions_count: type: integer title: type: string updated_at: type: string url: type: string page_views_count: type: integer tags: type: array items: $ref: "#/definitions/Tag" user: $ref: "#/definitions/User" group: $ref: "#/definitions/Group"3. GraphQL Mesh サーバを起動する
GraphQL Mesh サーバを起動します。以下コマンドは
npm scripts
に設定しておくと良いでしょう。$ yarn graphql-mesh serve yarn run v1.22.4 info: ?️ => Serving GraphQL Mesh GraphiQL: http://localhost:4000/http://localhost:4000/ で GrapiQL が起動します。ブラウザを開いて確認しましょう。
4. GraphQL クエリを実行する
Qiita 記事の情報と、記事に紐づくユーザ情報も合わせて取得します。複数の REST API で取得できる情報をネストして記述し、1回のクエリで取得できることこそが GraphQL の真骨頂です。
query getItems { getItems{ title likesCount user { name itemsCount organization description } } }きちんと取得できているようです。
さらに OpenAPI のモデルの定義を正確に読み解き、GraphQL のスキーマ定義にもきちんと反映ができています。素晴らしい。
GraphQL Mesh の活用方法
GraphQL Mesh はバックエンド API のプロキシとして機能します。この性質から、クライアントに対する GATEWAY としてふるまい、複数のバックエンドを束ねた構成をとっても良いでしょう。
また、複数のマイクロサービスが内部で相互通信する際に、HUB とする構成を取ることもできます。
まだ開発初期段階らしく、GitHub の README には以下のように記されています。
Note: this project is early and there will be breaking changes along the way
今後大きく変更されることがあるかもしれません。ただ、このツールのコアコンセプトには非常に感銘を受けます。AWS の AppSync などの GraphQL マネージドサービス系がこの考え方を取り入れたら、Web API の業界に大きなインパクトがありそうだと感じました。
- 投稿日:2020-03-25T00:18:18+09:00
homebrewでnodeモジュールのダウングレード
homebrewでダウングレードしたnodeを使用する際に、詰まった部分を残します。
環境
インストール済みのnodeバージョン:13.11.0
インストールしたいnodeバージョン:12.16.1macOS:10.14.3
nodeバージョン12系をインストール
バージョン指定の方法が12系の場合
@12
で最新の12系のバージョンがインストールできます。$ brew install node@12 ==> Downloading https://homebrew.bintray.com/bottles/node@12-12.16.1.mojave.bott ==> Downloading from https://akamai.bintray.com/3d/3dee3426e2ea8928d0724a801591a ######################################################################## 100.0% ==> Pouring node@12-12.16.1.mojave.bottle.tar.gz ==> Caveats node@12 is keg-only, which means it was not symlinked into /usr/local, because this is an alternate version of another formula. If you need to have node@12 first in your PATH run: echo 'export PATH="/usr/local/opt/node@12/bin:$PATH"' >> ~/.bash_profile For compilers to find node@12 you may need to set: export LDFLAGS="-L/usr/local/opt/node@12/lib" export CPPFLAGS="-I/usr/local/opt/node@12/include" ==> Summary ? /usr/local/Cellar/node@12/12.16.1: 4,293 files, 57.8MB12系のバージョンはインストールが完了しました。
しかし、13系のバージョンを残したままインストールすると複数のノードパッケージをインストールできますが、同時に使用できるようにすることはできません。
したがって、既存のパッケージ(ここでは13系)が入っている場合は最初にその紐付きを解除する必要があります。インストール済みのバージョンの紐付きを解除
以下のコマンドで現在紐付いているバージョンを解除できます。
$ brew unlink node Unlinking /usr/local/Cellar/node/13.11.0... 0 symlinks removed使用したいバージョンを紐付ける
今回は12系のnodeバージョンを紐付けたいので以下のように設定します。
$ brew link node@12 Warning: node@12 is keg-only and must be linked with --force If you need to have this software first in your PATH instead consider running: echo 'export PATH="/usr/local/opt/node@12/bin:$PATH"' >> ~/.bash_profile紐付けを行なった際に
.bash_profile
のnodeのパスを設定してくださいと警告が出ているので、設定します。$ echo 'export PATH="/usr/local/opt/node@12/bin:$PATH"' >> ~/.bash_profile
.bash_profile
を念のため更新$ source ~/.bash_profile最後にnodeのバージョンを確認
$ node -v v12.16.1成功です。
余談ですが、今回バージョンを落とした理由は
gatsby-cli v2.11.1
をインストールする際に、nodeモジュールが13.11.0の場合インストールに失敗してしまう事がきっかけでした。ダウングレードにより、無事
gatsby-cli
の導入もできました。$ gatsby --version Gatsby CLI version: 2.11.1