20200325のNode.jsに関する記事は14件です。

【未完】楽天市場のAPIを使って買えるマスクを提案してくれるLINEBotを作ってみた

はじめに

ProtoOutStudioというイケイケなスクールの「LINE Bot+APIで表現してアウトプット」という課題で製作したものです。

こちらの1時間でLINE BOTを作るハンズオンの記事をベースにLINEBotを作成しました。
最近の新型ウィルスの影響で、ネットショッピングばかりしているのですが、ちょっと楽天市場のトップ画面を見るのに飽きてきたので、(マスクなどの)必要なものを、(売り切ればかりなので)在庫のある商品で、高額になりすぎて買う気がなくなってしまわない(金額の)範囲で、提案してもらえるLINEBotを考えました。
(Amazonは申請が大変そうに見えたのでお見送りしました)

概要と作れなかったところ

概要

  • LINEBotにほしいもの、「マスク」と入力したら
    1. 楽天市場のキーワード検索から「マスク」を検索
    2. 絞り込み検索で「購入可能」
    3. 「最安価でソート」
    4. 「最低金額○円以上」
    5. 「最高金額○円以下」
    6. 商品画像付き
    7. を5つくらい返してくれるBot

作れなかったところ

  • ほしいもの 「マスク」と入力したら 
    1. × → 楽天市場のキーワード検索から「マスク」を検索
    2. ○ → 絞り込み検索で「購入可能」
    3. ○ → 「最安価でソート」
    4. ○ → 「最低金額○円以上」
    5. ○ → 「最高金額○円以下」
    6. ○ → 商品画像付き
    7. × → を5つくらい返してくれるBot

入力したものをエンコードしてAPIのURLに入れて作成するところと、
(APIをベタでかくと動くBotはできたけど)これを複数表示するためにどこでFor文を回せばよいかわからなかった

環境

Node.js v13.7.0
MacBook Pro macOS Mojave
Visual Studio Code v1.43.1

できたもの

Image from Gyazo
「マスクある?」と質問することで、楽天市場の中で在庫あり商品、最安価の商品名と商品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}`);

参考サイト

感想

深いAPIを操るの大変ですが自分好みの条件の商品をサクッと提案してもらえるBotはそれなりに便利そうなでちゃんと完成させねばです。
きっともっといい書き方や2回書かなくてもいいものとかたくさんある気がしますが、【未完】を取れるように早めにやっつけたいです。

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

【あと少し】楽天市場のAPIを使って買えるマスクを提案してくれるLINEBotを作ってみた

はじめに

ProtoOutStudioというイケイケなスクールの「LINE Bot+APIで表現してアウトプット」という課題で製作したものです。

こちらの1時間でLINE BOTを作るハンズオンの記事をベースにLINEBotを作成しました。
最近の新型ウィルスの影響で、ネットショッピングばかりしているのですが、ちょっと楽天市場のトップ画面を見るのに飽きてきたので、(マスクなどの)必要なものを、(売り切ればかりなので)在庫のある商品で、高額になりすぎて買う気がなくなってしまわない(金額の)範囲で、提案してもらえるLINEBotを考えました。
(Amazonは申請が大変そうに見えたのでお見送りしました)

概要と作れなかったところ

概要

  • LINEBotにほしいもの、「マスク」と入力したら
    1. 楽天市場のキーワード検索から「マスク」を検索
    2. 絞り込み検索で「購入可能」
    3. 「最安価でソート」
    4. 「最低金額○円以上」
    5. 「最高金額○円以下」
    6. 商品画像付き
    7. を5つくらい返してくれるBot

作れなかったところ

  • ほしいもの 「マスク」と入力したら 
    1. ○ → 楽天市場のキーワード検索から「マスク」を検索 (※3/27進めた!)
    2. ○ → 絞り込み検索で「購入可能」
    3. ○ → 「最安価でソート」
    4. ○ → 「最低金額○円以上」
    5. ○ → 「最高金額○円以下」
    6. ○ → 商品画像付き
    7. × → を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
 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}`);

参考サイト

感想

深いAPIを操るの大変ですが自分好みの条件の商品をサクッと提案してもらえるBotはそれなりに便利そうなでちゃんと完成させねばです。
きっともっといい書き方や2回書かなくてもいいものとかたくさんある気がしますが、【未完】を取れるように早めにやっつけたいです。

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

[備忘録]Macの開発環境構築(anyenv + nodenv + Node.js)

構築の流れ

  1. Homebrewのインストール
  2. Gitのインストール
  3. anyenvのインストール
  4. nodenvのインストール
  5. Node.jsのインストール

環境

  • macOS Catalina
  • シェルは zsh

Homebrewのインストール

本家 のスクリプトを実行。

% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

Homebrewのバージョン確認。

% brew -v

Gitのインストール

Gitがインストールされているか確認する。
使い方が表示されればOK。

% git

HomebrewでGitをインストール

% brew install git

anyenvのインストール

HomebrewかGitでanyenvをインストールする。

Homebrew版

% brew install anyenv

Git版

% git clone https://github.com/anyenv/anyenv ~/.anyenv

anyenvを初期化する。

% 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/plugins

anyenv-update

URL:https://github.com/znz/anyenv-update
anyenvで入れた**env系とプラグインの更新をする。

% git clone https://github.com/znz/anyenv-update.git ~/.anyenv/plugins/anyenv-update

anyenv-git

URL:https://github.com/znz/anyenv-git
anyenvで入れた**env系とプラグインのgitコマンドを実行する。

% git clone https://github.com/znz/anyenv-git.git ~/.anyenv/plugins/anyenv-git

nodenvのインストール

anyenvでnodenvをインストール

anyenvでインストール可能な「**env」一覧を表示。

% anyenv install -l

nodenvをインストール。

% anyenv install nodenv

シェルを再起動。

% exec $SHELL -l

nodenv使えるか確認。

% 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#readme

Node.jsのインストール

nodenvでNode.jsをインストール

nodenv installコマンドの使い方を表示。

% nodenv install

インストール可能なNode.jsのバージョン一覧を表示。

% nodenv install -l

Node.jsの公式サイトを確認して、推奨バージョンをインストール。

% nodenv install *.**.*

インストールしたバージョンを確認。

% nodenv versions

グローバルとローカルのバージョンを指定。

# グローバル
% nodenv global *.**.*

# ローカル
% cd [プロジェクトのパス]
% nodenv local *.**.*

参考URL

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

[Node.js勉強会]ExpressフレームワークでTODOアプリを作ろう

この記事について

Node.jsの概念や、Expressの使い方について詳しく書いてある記事ではありません。
ある程度の土台が用意してあるので、それを元に実際に手を動かして、TODOアプリを完成させることがこの記事の目的です。

pugファイルと仮処理を記述 の部分までは、コピペで進めていただいても大丈夫です。
TODOの処理を記述 の部分から実際に、Node.jsの様々な書き方を試していただけたらと考えております

今回作成する、TODOアプリの動作は以下のような形です。
express-comp.mov.gif

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 で再度立ち上げると治ると思います。

express-first-step.png

手順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.js
var 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.pug
extends 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.js
function 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.js
var 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 コマンドでコンテナを立ち上げなおします。

すると、以下のような状態になるかと思います。
express-second-step.png

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.js
var 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

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

LINE botを勉強会の受付に導入してみたい! ~connpass APIの紹介~

導入

 仕事は別に勉強会を開いているのですが、有志による運営のため手が回らないこところが多いです。その中でも受付業務にフォーカスして手助けになるシステムのプロトタイピングを行っています。

Noodlで受付嬢を創った

 私と同じくユーザコミュニティの運営やイベントの開催をされている方に読んでいただいて、少しでも負担が軽くなるシステム例としていただけたら嬉しいです。

 勉強会といえば、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にテキストを投げるとこのように返答が来ます。

LINE bot.jpg

準備

 使用する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=Noodl

URLを抜き出す

 次の処理で、大量にあるデータの中からイベント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の醍醐味ですね(笑)

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

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.ts
import { 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.ts
const config: Configuration = {
  // ...
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
  },
  // ...
}

これでエラーが消えたはずです。

HtmlWebpackPlugin の設定

詳しくは割愛しますが、このプラグインを使うとテンプレートのHTMLのbodyタグの末尾にscriptタグを勝手に挿入してくれるので便利です。
というわけでインストールします。

npm i -D html-webpack-plugin @types/html-webpack-plugin
webpack.config.ts
import 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.tsx
import React, { FC } from 'react'

export const App: FC = () => <div>Hello World!</div>
src/index.tsx
import 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で行っているものは少なかったので記事にしました。型定義があるぶん補完も効くので書きやすいです。

こんな記事でも誰かのお役に立てば幸いです。ご覧頂きありがとうございました。

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

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サーバですが、ちゃちゃっとつかうフロントのサーバとしては十分でしょう。

おつかれさまでした!

関連リンク

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

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と素子を接続する

以下図のように接続します。
Screenshot from Gyazo

リモコンの赤外線信号を解析する

まずは、赤外線センサーを使ってリモコンの信号を受信するプログラムを作成します。
(確認してないですが、このプログラムを使ってOCR-05W以外のリモコンの信号も受信できると思います。)

receive.ts
import 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();
  });
}

これをコンパイルして実行する。
Screenshot from Gyazo
と表示されるので、リモコンのボタンを押すと、めっちゃ長い信号が受信される。

無事受信できた。

リモコンコンセントにobnizから信号を送信する

プログラムは以下の通り。

send.ts
import 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+TypeScriptは補完が効くようになるのでオススメ。

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

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}`);
result
34.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}`);
result
270.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のみで実験しましたがブラウザ上だとまた違った結果になりそうです。

初学者ですので間違い等あれば訂正いただけると嬉しいです。

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

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バージョン確認

  1. 最新の Node.js がインストールされていることを確かめる。

とあるので、nodeを最新化する。
現行入っているバージョンを確認。

$ node -v
v6.9.4

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

npm最新化

nodeが最新化された。npmも最新化しておく。

$ nodist npm match
npm matc

$npm -v
6.13.7

新しいプロジェクト作成

npxでcreate-react-app実行

  1. 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 を開くと、空の三目並べの盤面が表示されることを確認できた。

以上です。

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

Node.jsでファイル出力

下記の『同期処理メソッド「writeFileSync」』を参考に実施
node.jsでファイルの入出力操作

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

N予備校の教材でAjaxでCORSを試してみる

やりたいこと

N予備校のAjaxのページに「同一生成ポリシーを満たさない場合CORSにひかかって通信できません」と書かれているが、そのあたりの回避策について特に紹介されていなかったのでAccess-Control-Allow-Originを試してみる。

前提知識

環境

  • 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GraphQL Mesh は何を解決するのか? ~ Qiita API を GraphQL でラップして理解する GraphQL Mesh ~

image.png

GraphQL Mesh とは

The Guild から GraphQL Mesh が発表されました。

GraphQL 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.yaml

QiitaAPI の 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
    }
  }
}

きちんと取得できているようです。

image

さらに OpenAPI のモデルの定義を正確に読み解き、GraphQL のスキーマ定義にもきちんと反映ができています。素晴らしい。

image

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 の業界に大きなインパクトがありそうだと感じました。

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

homebrewでnodeモジュールのダウングレード

homebrewでダウングレードしたnodeを使用する際に、詰まった部分を残します。

環境

インストール済みのnodeバージョン:13.11.0
インストールしたいnodeバージョン:12.16.1

macOS: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.8MB

12系のバージョンはインストールが完了しました。

しかし、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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む