Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8812 articles
Browse latest View live

結婚式の招待にLinebotを導入してみた話

$
0
0

経緯

いつもはITコンサルティングの会社で働いているTsugaです。
この度結婚することになり、妻と共に式の準備に勤しんでいます。

準備をする中で、ちょっとこの風習は古いんじゃないのか?と思うことがいくつかあったので、自分たちの式では少し工夫をしてみました。

結婚式のここがイケてない

1 招待状

未だに結婚式の招待状は手紙で用意することが前提になっています。

これを業者に丸っと外注すると、非常にお値段的に高くつきますし、自分たちで用意するとなると、それはそれで大変でやりたくありませんでした。

何より、手紙をもらった側も、返信の際は暗黙のしきたりに従って返さねばならず、ちょっと億劫です。

あと、招待状でしか会場の情報や開始時間の情報は送られてこないので、
自分なんかは管理が苦手なので当日になって焦って探したりします。

もう、普通に紙で管理する必要が全くないので電子化したいなと。

2 ご祝儀

これ、自分が参列する時も悩みの種です。

銀行って仕事時間中にしかやってなくて行きづらく、ピン札を用意するのは一苦労。
ご祝儀袋を用意するのも地味に労力使うのです。

自分たちの結婚式に来る人たちにはその苦労をさせたくないし、気持ちよく結婚式に来て欲しい。

だったらもう現金じゃなくて、Webで決済できればよくない??

できたもの

その不満を形にしてできたのがこれです。

https://lin.ee/lFWuSeZ

以下がポイント。
1. それ単体では味気ないGoogle formsをBotが送ることで少しだけカッコよくなる。
2. 会場、時間の情報などにいつでも答えられる
3. ご祝儀をWebで払え!とは言いづらいので聞かれた時にBotが案内することで角が立たない

招待状を受け取った友人から「自分の式でもこれやりたい!」との声をもらったので、
作り方を残しておきます。

作り方

Line developersに登録する

まず、Line developersに登録します。
https://developers.line.biz/ja/
私は自分の普段使っているLineアカウントで登録しました。

プロバイダーを作る

こんな感じでプロバイダーを作ります。
名前はなんでもいいです。Botの製作者として出るものです。

スクリーンショット 2020-03-01 23.00.11.png

チャネルを作る

プロバイダーを作るとチャネル設定の画面にリダイレクトされます。
スクリーンショット 2020-03-01 23.00.54.png

この中から、今回は真ん中のMessaging APIを選択します。
すると、基本項目を設定するページに飛ぶので、適当に埋めてチャネルを作成してください。

  • 必須項目
    • チャンネルの種類
    • プロバイダー
    • チャネル名(7日後から変更可能)
    • チャネル説明(後から変更可能)
    • 大業種
    • 小業種
    • メールアドレス
  • 任意項目
    • チャネルアイコン
    • プライバシーポリシーURL
    • サービス利用規約URL

チャネルの設定

スクリーンショット 2020-03-01 23.10.15.png

Messaging API設定から、チャネルアクセストークンを発行してください。
どこかにペーストして保存しておいてください。あとで使います。

Line公式アカウント機能は今回使わないので、一通りの機能を無効にします。
応答メッセージかあいさつメッセージの編集ボタンから
Official Account Managerに飛べるので、クリックしてください。

Response Settings

Response Settingsをいじります。

Response modeはBotに、Greeting messageはDisabledにします。
Auto-ResponseはDisabledに、WebhooksはEnabledに変更してください。

スクリーンショット 2020-03-02 0.54.06.png

次に、Messaging API SettingsからWebhook URLの設定欄に飛べますので、
そこからChannel secretの文字列をどこかに保存しておいてください。チャネルアクセストークン同様、あとで使います。

Botサーバの用意

Bot開発ではこの黄色の部分を作ります。
スクリーンショット 2020-03-02 22.51.53.png
今回はNode.js+Expressで作りました。
もうNode開発環境がある方はこの章はすっ飛ばして大丈夫です。

Herokuの準備

Herokuにデプロイするので、Herokuのアカウントを作っておいてください。
https://dashboard.heroku.com/

Herokuは有名なホスティングサービスです。デプロイまでコードをあげるだけで勝手にやってくれるしログもとってくれて本当に楽です。無料枠でも割と普通に使えるので素敵。

こちらのHeroku公式スタートガイドに従っていろいろ初期設定を済ませてください。

もしNode.js, npmが入ってなかったら
このURLから落としてくるのが早いです。
環境に合わせてインストーラを落としてきて、ガイドに従ってインストールしてください。

作業内容は一応書いておくと以下。

> brew install heroku/brew/heroku
> heroku login
> git clone https://github.com/heroku/node-js-getting-started.git
> cd node-js-getting-started
> heroku create
> git push heroku master

これら一連の設定で順当に行けばアプリをデプロイできるはずです(超簡単!)
heroku openで、自分で作ったサイトに飛べます。
こんな感じ。

アプリのURLをコピーして、Line Official Account ManagerのMessaging APIの設定に行って、WebhookURLに設定してください。
https://生成されたドメイン用文字列.herokuapp.com/webhook
今回は/webhookを付けようと思います。(公式チュートリアル準拠)
これで下準備はOKです。

コーディング

Linebot用の公式SDKを入れます。

npm install @line/bot-sdk --save

そんで、こんな感じにLinebotの応答処理を追加します。雛形は以下のような感じ。

 index.js
constexpress=require('express')constpath=require('path')constPORT=process.env.PORT||5000constline=require('@line/bot-sdk');// 追加constconfig={channelSecret:process.env.SECRET_KEY,channelAccessToken:process.env.ACCESS_TOKEN};constclient=newline.Client(config);// 追加express().use(express.static(path.join(__dirname,'public'))).set('views',path.join(__dirname,'views')).set('view engine','ejs').get('/',(req,res)=>res.render('pages/index'))// 以下のPOSTメソッド追加.post('/webhook',line.middleware(config),(req,res)=>{Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));}).listen(PORT,()=>console.log(`Listening on ${PORT}`))// 追加。応答処理consthandleEvent=(event)=>{// この中に処理を書きます。}

configのchannelSecret, channelAccessTokenはLinebotにアクセスするために必要なものです。
Linebotのチャネル設定のところで取りかたは書いているのでご参考に。

そのまま文字列で書くのはセキュリティ的によろしくないので、環境変数から取れるようにしていきます。

heroku config:set SECRET_KEY="取得した文字列"
heroku config:set ACCESS_TOKEN="取得した文字列"

はい、これでheroku上の環境変数はOKです。

ローカルでの開発

Herokuの環境変数にシークレットキーとアクセストークンを仕込みましたが、ローカルでも開発したいと思うのでローカルの環境変数設定をします。
dotenvを使うのが便利です。

npm install dotenv --save

これでdotenvを入れてローカルの.envファイルからprocess.envの設定内容を取得できるようにします。
.envファイルに設定を書きます。

 .env
ACCESS_TOKEN="取得した文字列"
SECRET_KEY="取得した文字列"

そしてindex.jsの頭あたりに.envの読み込み部分を書いておきます。

index.js
require('dotenv').config();// 追加...

それで、ローカルでサーバを立てた際に、Lineサーバからアクセスできるように解放しなければなりません。
ngrokや、Localtunnelのようなソフトウェアを使えば、ローカルのサーバをインターネットに公開することができます。
ngrokのインストールはこちら。
Installing ngrok on OSX
LinebotのwebhookURL設定をngrokが生成してくれるURLに書き換えれば、ローカルのサーバで試せます。
スクリーンショット 2020-03-02 22.29.30.png

Botでやりたいこと

  1. フォローしてくれた人に、参加可否アンケートフォームを送ること
  2. 質問に答えられること
  3. 聞かれたときだけご祝儀のWeb決済フォームを送ること

です。まずは1から。

①フォローしてくれた人に参加可否アンケートフォームを送る機能

Linebotでは、Botに対してどんなアクションが取られたかをイベントオブジェクトという形で受け取ることができます。

フォローしてくれた人にたいして何かしらの反応をしたいので、フォローイベントが送られてきた時にだけ、
特定のメッセージを返すような実装をします。

index.js
// メイン処理consthandleEvent=(event)=>{switch(event.type){// follow Event時の返答case"follow":returndoReply(event,[getTxtMsg("フォローいただきありがとうございます。こちらからご参加の可否についてご回答をお願いします!"),getTxtMsg("https://forms.gle/ホゲホゲ")])default:break;}}/**
 * テキストメッセージのオブジェクトを取得する
 * 
 * @param {String} msg テキストメッセージ
 */constgetTxtMsg=(msg)=>{return{"type":"text","text":`${msg}`}}// リプライ処理(Herokuログに残す用) 別になくてもいいconstdoReply=(ev,obj)=>{console.log(ev,obj);returnclient.replyMessage(ev.replyToken,obj)}

これでgit commitして、git push heroku masterしてBotを確認してみましょう。

BotをLineの友達に追加するためのURLは、Official Account ManagerからGain Friendsのメニューに行くとあります。

スクリーンショット 2020-03-02 22.10.42.png

Followイベントへのレスポンスができました。

②質問に答えられる機能

次に、質問に答えられるようにしていきましょう。
ユーザからの質問はメッセージイベントとしてBotサーバに送られてきます。

クイックリプライ機能

場所は?と聞かれたら、挙式の場所を聞きたいのか、披露宴の場所を聞きたいのかといったことを聞き返そうと思います。
こちらから聞いたことに対して素早く回答してもらうのには、テンプレートメッセージを利用する方法もありますが、今回は自然にLineのやりとりっぽく見えるクイックリプライ機能でリプライにはポストバックアクション(メッセージアクション)を使います。

index.js
// メイン処理consthandleEvent=(event)=>{switch(event.type){// follow Event時の返答case"follow":...// Message Event時の返答case"message":handleMessageEvent(event);break;default:break;}}/**
 * メッセージイベントに対する応答を判断する
 * 
 * @param {Object} ev イベントオブジェクト 
 */consthandleMessageEvent=(ev)=>{switch(ev.message.type){case"text":if(isMatchQuickReplyPhrase(ev.message.text)){// クイックリプライでユーザが送ったメッセージには反応しないreturn;}if(ev.message.text.includes("場所")||ev.message.text.includes("会場")){returndoReply(ev,getQuickRpl("挙式(親族)、披露宴(親族・友人)どちらの場所を知りたいですか?",[getItm(msg.celemonyPlace.msgText,msg.celemonyPlace.data),getItm(msg.partyPlace.msgText,msg.partyPlace.data)]))}else{returndoReply(ev,txtMsg("対応してないキーワードに対する返答"))}}}/**
 * クイックリプライの対象文言に完全一致するかを判定する
 * @param {string} msgText 
 */constisMatchQuickReplyPhrase=(msgText)=>{letkeys=Object.keys(msg)letisMatch=falsekeys.forEach(elm=>{if(msgText==msg[elm].msgText){isMatch=true;}})returnisMatch;}// クイックリプライ用のメッセージ、データconstmsg={"celemonyPlace":{"msgText":"挙式はどこで行われますか?","data":"celemonyPlace"},"partyPlace":{"msgText":"披露宴はどこで行われますか?","data":"partyPlace"},}/**
 * クイックリプライ用のオブジェクトを取得する
 * 
 * @param {String} txt リプライの文言
 * @param {Object} itms アクションオブジェクト
 */constgetQuickRpl=(txt,itms)=>{return{"type":"text","text":txt,"quickReply":{"items":itms}}}/**
 * クイックリプライ用のアイテムオブジェクトを取得する
 * 
 * @param {String} txt テキスト
 * @param {String} dt Postbackイベント用データ 
 */constgetItm=(txt,dt)=>{return{"type":"action","action":{"type":"postback","label":txt,"text":txt,"data":dt}}}

IMG_1586.jpg

上記のコードではisMatchQuickReplyPhrase関数でクイックリプライで使われるポストバックアクションの文言と完全一致する場合に反応しないようにしています。
次に説明するポストバックイベントだけに反応させたいのですが、これをしないとメッセージイベントとしても反応してしまって要らないレスポンスを返してしまいます。

ポストバックイベントに返答する

ユーザーが使うクイックリプライ用のアイテムオブジェクトをみてもらうとわかりますが、ポストバックイベントにdataというキーがあります。
その内容をみて質問に対する答えを返したいと思います。

index.js
// メイン処理consthandleEvent=(event)=>{switch(event.type){// follow Event時の返答case"follow":...// Message Event時の返答case"message":...// Postback Event時の返答case"postback":handlePostbackEvent(event);break;default:break;}}/**
 * ポストバックイベントに対する応答を判断する
 * 
 * @param {Object} ev イベントオブジェクト 
 */consthandlePostbackEvent=(ev)=>{switch(ev.postback.data){casemsg.celemonyPlace.data:returndoReply(ev,[txtMsg("挙式会場はこちらです。"),celemonyPlace])casemsg.partyPlace.data:returndoReply(ev,[txtMsg("披露宴会場はこちらです。"),partyPlace])}}// 披露宴会場constpartyPlace={"type":"location","title":"披露宴会場 ラーメン二郎 三田本店","address":"2 Chome-16-4 Mita, 港区 Minato City, Tokyo 108-0073","latitude":35.643564,"longitude":139.739017}// 挙式会場constcelemonyPlace={"type":"location","title":"挙式会場 湯島天神","address":"〒113-0034 東京都文京区湯島3丁目30−1","latitude":35.707849,"longitude":139.767824}

IMG_1587.jpg

しれっとロケーションタイプのメッセージを使っています。
メッセージオブジェクトはいろいろありますが、下記の記事がまとめてくれていて見やすいので是非ご参考にしてください。
https://qiita.com/kakakaori830/items/52e52d969800de61ce28

もうこれらを使えば大体のQ&Aは応用してできるはずです。

③ご祝儀のWeb決済対応

自分では実装しません。Paypalアカウントを作ってください。
送金用のURLを発行して、返すだけです。
一応、ソースコードはこんな感じになります。

index.js
constexpress=require('express')constpath=require('path')constPORT=process.env.PORT||5000constline=require('@line/bot-sdk');require('dotenv').config();// 追加constconfig={channelSecret:process.env.SECRET_KEY,channelAccessToken:process.env.ACCESS_TOKEN};constclient=newline.Client(config);express().use(express.static(path.join(__dirname,'public'))).set('views',path.join(__dirname,'views')).set('view engine','ejs').get('/',(req,res)=>res.render('pages/index')).post('/webhook',line.middleware(config),(req,res)=>{Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));}).listen(PORT,()=>console.log(`Listening on ${PORT}`))// メイン処理consthandleEvent=(event)=>{switch(event.type){// follow Event時の返答case"follow":returndoReply(event,[getTxtMsg("フォローいただきありがとうございます。こちらからご参加の可否についてご回答をお願いします!"),getTxtMsg("https://forms.gle/ホゲホゲ")])// Message Event時の返答case"message":handleMessageEvent(event);break;// Postback Eventcase"postback":handlePostbackEvent(event);break;default:break;}}/**
 * ポストバックイベントに対する応答を判断する
 * 
 * @param {Object} ev イベントオブジェクト 
 */consthandlePostbackEvent=(ev)=>{switch(ev.postback.data){casemsg.celemonyPlace.data:returndoReply(ev,[getTxtMsg("挙式会場はこちらです。"),celemonyPlace])casemsg.partyPlace.data:returndoReply(ev,[getTxtMsg("披露宴会場はこちらです。"),partyPlace])casemsg.onWeb.data:returndoReply(ev,[getTxtMsg("Webでの決済はPaypal経由でのみ受け付けております。\n以下のURLからお願いいたします!"),getTxtMsg("https://paypal.me/ホゲホゲ?locale.x=ja_JP")])casemsg.onHand.data:returndoReply(ev,getTxtMsg("お気持ちをありがとうございます!\n当日お渡しの際はピン札のご用意の必要はございません。\n当日は美味しい食事や飲み物をたくさん用意してお待ちしております!"))default:break;}}// 披露宴会場constpartyPlace={"type":"location","title":"披露宴会場 ラーメン二郎 三田本店","address":"2 Chome-16-4 Mita, 港区 Minato City, Tokyo 108-0073","latitude":35.643564,"longitude":139.739017}// 挙式会場constcelemonyPlace={"type":"location","title":"挙式会場 湯島天神","address":"〒113-0034 東京都文京区湯島3丁目30−1","latitude":35.707849,"longitude":139.767824}/**
 * メッセージイベントに対する応答を判断する
 * 
 * @param {Object} ev イベントオブジェクト 
 */consthandleMessageEvent=(ev)=>{switch(ev.message.type){case"text":if(isMatchQuickReplyPhrase(ev.message.text)){// クイックリプライでユーザが送ったメッセージには反応しないreturn;}if(ev.message.text.includes("場所")||ev.message.text.includes("会場")){returndoReply(ev,getQuickRpl("挙式、披露宴どちらの場所を知りたいですか?",[getItm(msg.celemonyPlace.msgText,msg.celemonyPlace.data),getItm(msg.partyPlace.msgText,msg.partyPlace.data)]))}elseif(ev.message.text.includes("祝儀")||ev.message.text.includes("祝金")){returndoReply(ev,[getTxtMsg(`お祝い金は当日のお渡し、\nまたは事前のWebでの決済にていただければ幸いです。`),getQuickRpl("お祝い金のお渡し方法について、Webでの事前決済をご希望ですか?",[getItm(msg.onHand.msgText,msg.onHand.data),getItm(msg.onWeb.msgText,msg.onWeb.data)])])}else{returndoReply(ev,getTxtMsg("対応してないキーワードに対する返答"))}}}/**
 * クイックリプライの対象文言に完全一致するかを判定する
 * @param {string} msgText 
 */constisMatchQuickReplyPhrase=(msgText)=>{letkeys=Object.keys(msg)letisMatch=falsekeys.forEach(elm=>{if(msgText==msg[elm].msgText){isMatch=true;}})returnisMatch;}// クイックリプライ用のメッセージ、データconstmsg={"celemonyPlace":{"msgText":"挙式はどこで行われますか?","data":"celemonyPlace"},"partyPlace":{"msgText":"披露宴はどこで行われますか?","data":"partyPlace"},"onHand":{"msgText":"当日手渡しをします","data":"onHand"},"onWeb":{"msgText":"Web決済を希望します","data":"onWeb"}}/**
 * クイックリプライ用のオブジェクトを取得する
 * 
 * @param {String} txt リプライの文言
 * @param {Object} itms アクションオブジェクト
 */constgetQuickRpl=(txt,itms)=>{return{"type":"text","text":txt,"quickReply":{"items":itms}}}/**
 * クイックリプライ用のアイテムオブジェクトを取得する
 * 
 * @param {String} txt テキスト
 * @param {String} dt Postbackイベント用データ 
 */constgetItm=(txt,dt)=>{return{"type":"action","action":{"type":"postback","label":txt,"text":txt,"data":dt}}}/**
 * テキストメッセージのオブジェクトを取得する
 * 
 * @param {String} msg テキストメッセージ
 */constgetTxtMsg=(msg)=>{return{"type":"text","text":`${msg}`}}// リプライ処理(Herokuログに残す用)constdoReply=(ev,obj)=>{console.log(ev,obj);returnclient.replyMessage(ev.replyToken,obj)}

結果はこちら。
IMG_1588.jpg

改めて以下のURLから今回のボットのサンプルに触れるようにしておきます。
https://lin.ee/lFWuSeZ

今回ハードコーディングで全て実装しました。我々の結婚式本番で使っているBotはもう少しいろいろきめ細やかに作り込んでいますが、
一通り触って見て、LinebotはとてもSDKも使いやすく、できることもわかりやすいため開発しやすいなと思いました。

しかし、もう少し賢く応答してくれるBotを作ろうと思うと、これでは限界があります。
賢いBotはDialog Flowなどチャットボット作成に向いたサービスがあるのでそっちの方が良さそうです。

これから2ヶ月後くらいに結婚式本番なので、そこでもさらにLinebotを活かした余興ができないか試してみようと思います。


[Node.js + Heroku] 環境変数を実行環境ごとに切り替えて接続する

$
0
0

ローカルで開発して、Herokuでデプロイする際に環境変数を切り替えて接続したかったのでその手順を記録しました。

Herokuのバージョンは、下記の通りです。

$ heroku --version
heroku/7.39.0 darwin-x64 node-v12.13.0

ローカルの環境変数を設定

npmのconfigパッケージをnpmインストールします。

$ npm install config

次に、default.jsonを作成しましょう。

$ mkdir config
$ vi config/default.json

このdefault.jsonを読み込んで接続します。

your-app/
  └ config/
    └ default.json

ローカル開発の環境設定を記述します。

default.json
{"localDB":{"dbConfig":{"host":"localhost","port":3000,"dbName":"your-db-name"}}}

Herokuの環境変数を設定

以下はHerokuにログインして、CLIで設定する手順です。

Heroku CLI Commands | Heroku Dev Center

$ heroku login
$ heroku config:set <ENV_NAME>=<your-db-url> -a<your-app-name>

これで、共有サーバーの環境変数を設定できました。
CLIでなくても、ブラウザ上で設定可能です。

実行環境に応じて、環境変数を切り替えて接続

まずは、アプリケーションの接続先の設定を切り替える処理を確認します。
ここで、Node.jsのフレームワークであるExpressのファイルの一部を見てみましょう。

bin/www
/**
 * Get port from environment and store in Express.
 */varport=normalizePort(process.env.PORT||'3000');app.set('port',port);

process.env.PORTが参照できなければ、環境変数にローカルDBの接続先を代入しています。Expressを使用している場合は、アプリケーションの接続先の設定が切り替わるようになっていますね。


次は、DBの接続先を切り替える方法を確認します。
まずは、default.jsonの設定を読み込んで変数に代入しましょう。

app.js
constconfig=require('config');constlocalDBConfig=config.get('localDB.dbConfig');

localhost接続でなければ、Herokuの環境変数を読み込むように設定します。

下記はHerokuで実行した場合、mLabのmongodbを接続するようにした例です。
process.env.PORTが参照できなければ、環境変数にローカルDBの接続先を代入しています。

app.js
constMONGODB_ENV=process.env.MONGODB_ENV||`mongodb://${localDBConfig.host}/${localDBConfig.dbName}`;

後続の処理で、設定した環境変数を参照して接続するといいでしょう。

注意事項

APIキー等の認証情報を含む場合は、gitの管理対象からconfigファイルを外しましょう。

備考

Docker上のCentOSでnodebrewを入れる

$
0
0

はじめに

Centosにnodebrewを入れる記事はたくさん存在するのですが、
Dockerでやった時に詰まったので個人的なメモがてら残しておきます。

環境

  • docker
$ docker --version
Docker version 19.03.5, build 633a0ea
  • Dockerfile
FROM centos:centos7

やったこと

1. 従来の記事を読んでinstall

$ curl -L git.io/nodebrew | perl - setup

↓ 悲しみのエラー

Can't exec "which": No such file or directory at - line 927.
Can't exec "which": No such file or directory at - line 927.
Need curl or wget at - line 927.

whichがないと言われinstall出来ない。
どうやら、centos:centos7にはwhichなどがない模様。

2. whichをinstall

$ yum -yinstall which

3. 再びnodebrewをinstall

$ curl -L git.io/nodebrew | perl - setup

↓ install 完了!

Fetching nodebrew...
Installed nodebrew in$HOME/.nodebrew

========================================
Export a path to nodebrew:

export PATH=$HOME/.nodebrew/current/bin:$PATH========================================

3. 欲しいバージョンを入れて使用

$ /root/.nodebrew/current/bin/nodebrew install v12.16.1
$ /root/.nodebrew/current/bin/nodebrew use v12.16.1

今回は、nodeのv12系を入れたかったので、こんな感じでinstallしました。

余談ですがdocker等でサーバを立てたりする時にpassを通すと後から確認する時困ることが多いので、僕はフルパスで起動するようにしています。
もちろん、自分のmacではしっかりpassを通してます。

4. まとめ

$ yum -yinstall which
$ curl -L git.io/nodebrew | perl - setup
$ /root/.nodebrew/current/bin/nodebrew install v12.16.1
$ /root/.nodebrew/current/bin/nodebrew use v12.16.1
$ /root/.nodebrew/current/bin/node --version

↓ nodeが使えることを確認

v12.16.1

おわりに

dockerを触っていると、環境構築ばかり行うので詰まった時とかはqiitaに残しておくのが必要ですね...!

MacOS Catalinaにnodebrewを通じてNode.js(npm)を設定する

$
0
0

困ったこと

  • MacOS Caltalinaの標準ターミナルであるzshにはnpmが標準で入っていない スクリーンショット 2020-03-04 7.38.22.png

実行

手順
1. nodebrewをcurlを利用してインストール
$curl -L git.io/nodebrew | perl - setup
2. Vimを開いて下記nodebrewのパスを~/.zshrc(zshの設定ファイル)に記入
export PATH=$HOME/.nodebrew/current/bin:$PATH
スクリーンショット 2020-03-04 7.48.26 1.png
3. ~/.zshrcをリロードして適応
$source ~/.zshrc
4. node.jsの最新バージョンをインストール
$nodebrew install latest
5. nodebrewで使用するnode.jsのバージョンを指定
下記、赤枠部のようにインストール時にバージョン番号が表示されるため、それを利用してnode.jsのバージョン指定。
(X.X.Xの部分は各環境によって変わる)
スクリーンショット 2020-03-04 9.29.36.png
$nodebrew use v.X.X.X
6. コマンドで動作確認
$npm -v
スクリーンショット 2020-03-04 9.32.33.png

参照文献

AzureEventGridを触ってみたよ

$
0
0

はじめに

仕事でIoTHub, EventHubs, ServiceBusと触ってきましたが、それぞれ利用用途が異なります。AWSもそうですがたくさんのキューサービスが存在します。
今回はEventGridを触っていきたいと思います。

EventGridとはなにか?

このサービスはEventメッセージを扱うサービスで、送る側は後にどんな処理が行われるかについては関知しません。このへんはIoTHub,EventHubsと同じですね。
逆にメッセージを扱うサービスはServiceBusで受け取った側が最後まで責任をもってキューからデータを削除してあげる必要があります。

ServiceBusについては先日まとめたエントリーをご参照ください。

イベントソースこちらにある通りたくさんのサービスに対応しています。

  • Container Registry
  • EventHubs
  • IoTHubs
  • Key Vault
  • MediaService
  • ServiceBus
  • ストレージ
  • AppConfiguration
  • SignalR
  • MachineLearning

トピックというのはイベントのエンドポイントのことです。イベントのソースはトピックに対してイベントを送信します。
サブスクリプションには、どのトピックから受信してどのイベントハンドラ起動させるかを設定しておきます。

イベントハンドラはイベントの送信先です。こちらにある通り色々なサービスに対応しています

  • AzureAutomation
  • AzureFunctions
  • EventHubs
  • LogicApps
  • ServiceBus
  • QueueStorage
  • WebHook

AzureFunctionsにつなげば超強力なBinding機能でCosmosにもつなげますし使いどころはたくさんありそうです。

EventGridドメインを作る

image.png

イベントスキーマに何を設定すればよいのか悩むと思いますが、「クラウドイベントスキーマ v1.0」というものがあります。
これはCNCFが策定するクラウドイベントの標準仕様でAWSやOracle,MSが策定に参画しており、イベントのクラウド間相互運用性や互換性が高まることを狙っているものです。

現在ではFunctionがネイティブサポートされていないため、CloudEventsではなく、「イベントグリッドスキーマ」を使っていきます。

Functionの作成

イベントサブスクリプションを定義していきたいところですが、先にFunctionを作っておかないといけないので作っておきます。

いつも通りAzure Functions Core Toolsで作っていきます。

func new

した後に「JavaScript」→「6. Azure Event Grid trigger」を選択して関数のひな型を作りAzureFunctionsをデプロイしておきます。

このへんの操作は詳しくは下記のエントリを参照ください

今回は、1つのイベントソースで2つのFunctionを起動させてみたいと思いますので2つのFunctionをデプロイしました。

それぞれソースはこんな感じです

azure-eventgrid-samples/index.js
module.exports=asyncfunction(context,eventGridEvent){context.log("#1")context.log(eventGridEvent);};
azure-eventgrid-samples-2/index.js
module.exports=asyncfunction(context,eventGridEvent){context.log("#2")context.log(eventGridEvent);};

トピックと紐づける

それではサブスクリプションを登録していきましょう。
特に難しいところはないはずです。ドメイントピックの名前は今回はtopicという名前にしました。

image.png

同じトピックに2つのサブスクリプションを紐付けましょう

image.png

イベントを送る

Azure Event Grid libraries for JavaScript Example codeのままだとエラーが出てしまいます。

eventsには、eventTimeとtopicが必要なので忘れずに設定しましょう。

index.js
constEventGridClient=require("azure-eventgrid");constmsRestAzure=require("ms-rest-azure");constuuid=require("uuid").v4;constmoment=require("moment");consttopicCred=newmsRestAzure.TopicCredentials(process.env.TOPIC_KEY);constEGClient=newEventGridClient(topicCred);consttopicEndpoint=process.env.TOPIC_ENDPOINT;constevents=[{id:uuid(),subject:'TestSubject',topic:'topic',dataVersion:'1.0',eventType:'Microsoft.MockPublisher.TestEvent',eventTime:newDate().toISOString(),data:{name:'uzresk',message:'hello!'}}];returnEGClient.publishEvents(topicEndpoint,events).then((result)=>{returnPromise.resolve(console.log('publish successfully.'))}).catch((err)=>{console.log("ERROR-------------------------");console.log(err);});

TOPIC_ENDPOINTですが、ドメインの画面にある「ドメインエンドポイント」をそのまま設定してはいけません。

https://xxxxxxx.japaneast-1.eventgrid.azure.net/api/events

ではなく↓を設定します。

xxxxxxx.japaneast-1.eventgrid.azure.net

では実行してみます。

$ node index.js
publish successfully.

少し経つと、EventGridドメインにある画面で1 Published Event, 2 Matched Eventとなっていれば成功です。

image.png

Functionsのログも確認しておきましょう

image.png

image.png

Dead Letter

イベントハンドラに渡せなかったりした場合はDeadLetterにデータが渡ります。
ServiceBusの場合はDeadLetterQueueというキューにデータが渡りましたがEventGridの場合はストレージに入るようです。

image.png

イベントの最大試行回数分動いてからDeadLetterにデータが渡るため実験する場合は短めに設定しましょう。

対象の関数を停止しておきわざと失敗させます。
配信不能だったデータは以下のパスに保管されます。(日付はUTCです)

ストレージアカウント/トピック名/サブスクリプション名/年/月/日/xxxxxx.json

[
  {
    "id": "xxxxxxxxxx-3b29-4bfc-a634-xxxxxxxxxx",
    "eventTime": "2020-02-26T23:22:02.0000000Z",
    "eventType": "Microsoft.MockPublisher.TestEvent",
    "dataVersion": "1.0",
    "metadataVersion": "1",
    "topic": "/subscriptions/xxxxxxxxxx/resourceGroups/rg-xxxxxxxx/providers/Microsoft.EventGrid/domains/evg-tisesk/topics/topic",
    "subject": "TestSubject",
    "deadLetterReason": "MaxDeliveryAttemptsExceeded",
    "deliveryAttempts": 2,
    "lastDeliveryOutcome": "Forbidden",
    "publishTime": "2020-02-26T23:22:03.8286689Z",
    "lastDeliveryAttemptTime": "2020-02-26T23:22:04.1762595Z",
    "data": {
      "name": "uzresk",
      "message": "hello!"
    }
  }
]

DeadLetterとなったデータはBlobStorage Trigger Functionを使って再試行や通知を行うことで対応ができるかと思います。

.Netのサンプルは「.NETのAzure Event Grid Dead Letterサンプル」にありますが、普通のTrigger Functionsと同じです。

DeadLetterに入る時点で時間が経っているのであまり気にする状況はないかと思いますが、BlobTriggerは最大10分間遅延することは忘れないでおきましょう。

関数アプリを従量課金プランで使用しているときに、関数アプリがアイドル状態になっている場合、新しい BLOB の処理が最大で 10 分間遅延する可能性があります。 この待機時間を避けるには、Always On が有効な App Service プランに切り替えることができます。 BLOB ストレージ アカウントで Event Grid トリガーを使用することもできます。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-storage-blob-trigger?tabs=csharp#event-grid-trigger

ServiceBusとつないでみる

同じようにサブスクリプションを作って、イベントハンドラをServiceBusにします。

image.png

ServiceBusExplorerで確認してみると確かにデータが入っていますね。

image.png

EventGridの後ろにServiceBusを挟むのはなぜか?

https://docs.microsoft.com/ja-jp/azure/event-grid/delivery-and-retry

Event Grid は、持続性のある配信を提供します。 各サブスクリプションに対して、最低 1 回は各メッセージを配信します。 イベントは、直ちに各サブスクリプションの登録済みのエンドポイントに送信されます。 エンドポイントがイベントの受信を確認しない場合、Event Grid はイベントの配信を再試行します。

ここに記載の通り最低1回、2回送信されることもありうる訳です。

EventGridのバックエンドの処理が冪等にできない場合や、1回しか処理できない場合はEventGridのバックエンドにServiceBusを配置し、ServiceBusのキューの設定で重複排除しておくことで1回しか処理されないようなメッセージを配信することが可能になるわけです。

Lambda から S3 を使う (Node.js)

$
0
0

バケットの一覧

list_buckets.js
'use strict'constAWS=require('aws-sdk')vars3=newAWS.S3()exports.handler=async(event)=>{console.log("*** start ***")try{vardata=awaits3.listBuckets().promise()console.log(data.Buckets)}catch(ee){console.log(ee)}constresponse={statusCode:200,}returnresponse}

バケット内のファイルの一覧

list_files.js
// ---------------------------------------------------------------varAWS=require('aws-sdk')vars3=newAWS.S3()exports.handler=async(event)=>{console.log("*** start ***")constbucket='bucket01'varparams={Bucket:bucket}try{vars3Objects=awaits3.listObjectsV2(params).promise();console.log(s3Objects)}catch(ee){console.log(ee)}constresponse={statusCode:200,body:JSON.stringify('Hello from Lambda!'),}returnresponse}// ---------------------------------------------------------------

ファイルの作成

s3_put.js
console.log('Loading function');constaws=require('aws-sdk');consts3=newaws.S3();exports.handler=async(event,context)=>{console.log("*** start *** ")constbucket='bucket01'constkey='Hello6.txt'varstr_data='Good Morning\n'str_data+='Good Afternoon\n'str_data+='Good Evening\n'str_data+='Good Night\n'console.log(str_data)console.log("REGION: "+process.env.AWS_REGION)constparams={Bucket:bucket,Key:key,Body:str_data,}try{vardata=awaits3.putObject(params).promise()console.log(data)console.log("*** s3.putObject ***")}catch(error){console.log("*** error *** s3.putObject ***")console.log(error)}console.log("*** end *** ")}

Serverless Framework で AWS Lambda と API Gateway をデプロイする

$
0
0

Serverless Meetup Tokyo #16 (オンライン開催)を拝見していて、ちょうど今、AWS への各種リソースのデプロイの自動化が課題だったので、よい機会と思い Serverless Framework を使ってみた。
尚、Serlverless Framwork については知っていたが、offline-start しか使ったことがなかった程度の人間です。

やったこと

弊社では Webシステムを、

  • フロントエンド: SPA(Angular)
  • バックエンド:
    • REST っぽいAPI: Lambda + API Gateway
    • DB: PostgreSQL 他

で組むことが増えてきていて、プロジェクトの取っ掛かり時の環境構築を手動でやっているのがダルくなってきた。
今回は、「REST っぽいAPI: Lambda + API Gateway」のところを Serverless Framework で自動化してみた。

手順

1. 環境構築用 IAM の作成

IAM コンソールで「serverless_deployment」という名前で作成。以下のポリシーをアタッチした。

  • AWSLambdaFullAccess
  • AmazonS3FullAccess
  • AmazonAPIGatewayAdministrator
  • AWSCloudFormationFullAccess

さらに、以下のようなインラインポリシーを追加した。

{"Version":"2012-10-17","Statement":[{"Sid":"VisualEditor0","Effect":"Allow","Action":["iam:DeleteRolePolicy","iam:CreateRole","iam:DeleteRole","iam:PutRolePolicy"],"Resource":"*"}]}

IAMFullAccess はさすがにヤバいかなと思い必要な権限だけ抽出したものだが、たぶん他の xxxFullAccess も必要なものだけにした方がよいだろう。

作成した IAM のアクセスキーなどを自PCの ~/.aws/credentialsに追加した。ちなみに環境は Windows 10 内の WSL(Ubuntu)。

.aws/credentials

[serverless-deployment]
aws_access_key_id = AKIAxxxxx
aws_secret_access_key = 49s9xxxxxxxxxxxxxxxxxxxxxxxxxxxx

2. ツールのインストール

awscli は現在最新の v2 をインストール

node は 12.14.1

そして Serverless Framework をインストールする。

npm install serverless -gsource ~/.bash_profile
serverless --version> Framework Core: 1.65.0
> Plugin: 3.4.1
> SDK: 2.3.0
> Components: 2.22.3

global じゃなくてもいいけど、パス通すのが面倒なので。

3. テンプレートからプロジェクトの作成

serverless create --template aws-nodejs

で、nodejs のテンプレートから Serverless Framework のプロジェクトを作成。

また、npm initで適当に package.jsonを作って、

npm install--save-dev serverless-plugin-custom-binary

を実行しておく。これは後に必要になるプラグイン。

lsすると以下のようなファイルとディレクトリがある。

handler.js  node_modules  package-lock.json  package.json  serverless.yml

4. serverless.yml を編集する

serverless.ymlを開いて次のように編集する(これだと最早テンプレートの意味ないが)。

service:my-awesome-serviceplugins:-serverless-plugin-custom-binarycustom:apiGateway:binaryMediaTypes:-image/jpegprovider:name:awsruntime:nodejs12.xstage:${opt:stage, 'dev'}region:ap-northeast-1apiName:${self:service}-${self:provider.stage}functions:api:handler:handler.helloname:${self:service}-api-${self:provider.stage}events:-http:path:/{proxy+}method:getintegration:lambda

まず service: my-awesome-service、これが AWS に作成されるリソース名の元になるのでちゃんと考えて命名しよう。重複したらどうなちゃうのかは不明。 kebab-caseを採用しておくと良いと思われる。例えばサービス名で S3 Bucket を作りたいとき、Bucket 名は CamelCase(大文字) を許可してないため。

次に Plugins と binaryMediaTypes。これを行うために先に serverless-plugin-custom-binary をインストールしておいた。

provider-apiName。これは API Gateway の名前なんだけど、これをしない場合 <stage名>-<service名>になる。Lambda とかは <service名>-<stage名>となり逆で気持ち悪いので、他の同じになるように直している。

stage: ${opt:stage, 'dev'}。単純に stage: devとするだけだと、 --stage prodを引数で指定された値が ${self:provider.stage}に代入されないので注意。

functions-api。Labmda に <service名>-<stage名>-apiという名前の関数が作成される。"api" は任意の名称で ok。

functions-api-name。既定だと <service名>-<stage名>-apiになるが、<service名>-api-<stage名>にしたい(stage名は最後尾に統一したい) ので name: ${self:service}-api-${self:provider.stage}とした。

handler: handler.hello。 handles.jsの hello 関数を呼び出すの意。

path: /{proxy+}。呼び出し URL のパス部分を全てスルーする。https://hoge.net/dev/fuga/piyo/gegeとか。

integration: lambda。既定で ON ぽいので要らないかも。

5. AWS にデプロイする

serverless deploy --aws-profile serverless-deployment

を実行する。--aws-profile serverless-deploymentで AWSプロファイルを指定している事に注意。

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service MyAwesomeService.zip file to S3 (1.13 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...............................
Serverless: Stack update finished...
Service Information
service: MyAwesomeService
stage: dev
region: ap-northeast-1
stack: MyAwesomeService-dev
resources: 11
api keys:
  None
endpoints:
  GET - https://xxxx.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+}
functions:
  api: MyAwesomeService-dev-api
layers:
  None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

なんやかんや実行されてデプロイされたみたい。

6. デプロイされたか確認

API Gateway

image.png

image.png

Lambda

image.png

image.png

cURL で呼び出してみる。

 curl https://xxxx.execute-api.ap-northeast-1.amazonaws.com/dev/aaa/bbb/ccc
{"statusCode":200,"body":"{\n  \"message\": \"Go Serverless v1.0! Your function executed successfully!\"...

うまくいったみたい。

7. 後片付け(削除)

serverless remove --aws-profile serverless-deployment

ですべてのリソースがキレイさっぱり消えます。これはこれで怖いので IAM の権限で制限したほうが良さそう。

トラブルシューティング

Q: デプロイが全然終わらない

A: Console から CloudFormation の該当スタックを削除してリトライ

Q: sls deploy や remove が「S3 のバケットが無い」とかでエラーになる

A: 該当バケット(my-awesome-service-veri-serverlessdeploymentbucke-rzr9e2jjdrlvのようなごちゃごちゃしたやつ) を手動作成するか、Console から CloudFormation の該当スタックを削除してリトライ

今後やりたいこと

  • Lambda へ VPC の設定
  • Lambda タイムアウト値の設定
  • スクリプトでビルドとか Webpack した結果を Serverless でデプロイ
  • S3 Bucket の作成
  • S3 に SPA をデプロイ
  • リソース権限周りをもっと深堀り

参考

Node.js製Lambdaの速度劣化箇所をX-RAYを使って特定する

$
0
0

はじめに

お気に入りのテレビ番組の有無を答えてくれるAlexaスキルを作ってみた」でNode.js製のLambdaを作ったのですが、

ただ、スキルを呼び出した後、返答が返ってくるまで少しタイムラグがあるのが気になりますね

いずれにしてもX-RAYを仕掛けて、番組表取得で遅くなっているのか、その後の処理で遅くなっているのかなどを見極めたいと思っています。

ということで、X-RAYを使って、速度が遅くなっている箇所を特定したいと思います。

コンソールからの設定

まず最初にコードには手を入れず、LambdaのコンソールからX-RAYの設定をしてみます。
Lambdaのコンソールにて、対象関数の「AWS X-RAY」の箇所のチェックボックスをONにします。
ScreenShot_lambda.png

この状態でLambdaを動かした後、X-RAYのコンソールの「Service Map」を開くとこんな感じで表示されます。
ScreenShot-XRAY-1.png

「Traces」から詳細を見ることができます。
ScreenShot-XRAY-2.png

この時点だとLambdaの実処理としては2.1秒かかっている、というのがわかる程度ですね。

コード修正1:外部とのhttp通信時間を調べる

処理の内訳を調べるため、まず外部とのhttp通信の時間がどれくらいを占めているのかを調べてみます。
今回のコードはaxiosを使ってyahooのテレビ表をhttpsで取りに言っているのですが、その所要時間を調べるために以下のコードを冒頭に入れてみます。
※xray-sdkは事前にnpmなりyarnなりで用意しておいてください。

constawsXRay=require('aws-xray-sdk')awsXRay.captureHTTPsGlobal(require('https'))awsXRay.capturePromise()

このコードを追加した状態で、再度ビルド、デプロイした後、実際に動作、X-RAYコンソールを開く、という操作をしてみます。

Service Map
ScreenShot-XRAY-3.png

Traces
ScreenShot-XRAY-4.png

ざっくりですが、2秒ちょっとの処理時間のうち前半約1秒がhttpsを使った番組表取得に費やされていることがわかります。

コード修正2:任意の区間を計測する

今度はコードに手を入れていきます。
経過時間を測りたい区間にcaptureFuncやcaptureAsyncFuncを埋め込んでいきます。

awsXRay.captureFunc('Cheerio.load',()=>{$=Cheerio.load(response.data)})
awsXRay.captureFunc('scrape',()=>{constleft=$('div.leftarea',elm)constright=$('div.rightarea',elm)dateString=$('p:first-of-type > em',left).text()timeString=$('p:nth-of-type(2) > em',left).text()titleString=$('p:first-of-type > a',right).text()})
awsXRay.captureFunc('make-speechText',()=>{if(filtered.length>0){// 今日ある場合は、番組詳細まで返すconstsubStr=filtered.map(x=>{consttimeString=x.time.replace('','から')constresult=timeString+''+x.title+''returnresult}).join('')speechText='今日は'+subStr+'あります。'}elseif(programInfos.length>0){// 今日はないけど、明日以降見つかったら、日付と時刻を返す。constsubStr=programInfos.map(x=>{constresult=x.date.replace('/','')+''+x.time.replace('','から')returnresult}).join('と、')speechText='今日はイッテQはありませんが、'+subStr+'にあるようです。'}else{// ないspeechText='今日はイッテQはありません。'}})

今までと同じく実行後にX-RAYコンソール…
Service Mapは特に変わらないので、Tracesのみ。
ScreenShot-XRAY-5.png

これで、先に見たテレビ欄の取得とCheerioでのhtmlロードで処理時間の大半を使っていることがわかりました。

終わりに

今回のX-RAYでの調査により、遅いと感じていたAlexaスキルの処理時間の大半はHTTPS通信(1回)とCheerio.loadによるHTMLの読み込み処理(1回)で占められていることがわかりました。

となると、対応方法としては、以下のどちらかかな、と考えています。

  • 番組表取得(HTTPS通信)とCheerioによるHTML解析処理は定期処理で事前に終わらせておく。Alexaスキルから呼び出されたときは解析済みの結果を使って文言整形する
  • Cheerio以外のHTMLパーサを使用する(libxmljsとか???)

X-RAYは万能薬ではないですが、使ってみるとなかなか便利な機能だと思いますので、まだ使ったことない方は、ぜひともお試しあれ。


AWS Lambda入門②(Node編)〜DynamoDBにアクセスする〜

$
0
0

概要

DynamoDBとは

  • AWSが提供するマネージドなデータベースサービスです
  • RDBとは異なりkey-value形式なドキュメントデータベースです

LambdaからDynamoDBにアクセスしてみる

DynamoDBの設定

  • まずはDynamoDBを使うためにServerlesFrameworkの設定をします

DynamoDBのテーブル定義の設定

  • DynamoDBはデータベースなのでテーブルの作成からはじめます
  • これまでと同じようにこれもServerlessFrameworkの機能で行うことができます
  • 今回はHelloテーブルを作ってみます
  • serverless.ymlを修正します
    • 見づらいのでデフォルトで記載されていたコメントアウトは全て削除しています
serverless.yml
service:sls-sample# AWS周りの設定provider:name:awsruntime:nodejs12.xregion:ap-northeast-1stage:devenvironment:DYNAMODB_TABLE:${self:service}-${self:provider.stage}iamRoleStatements:-Effect:AllowAction:-dynamodb:Query-dynamodb:Scan-dynamodb:GetItem-dynamodb:PutItem-dynamodb:UpdateItem-dynamodb:DeleteItemResource:'arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}*'# Lambdaの設定functions:hello:handler:handler.hello# DynamoDBの設定resources:Resources:Hello:Type:AWS::DynamoDB::TableProperties:TableName:${self:provider.environment.DYNAMODB_TABLE}-helloAttributeDefinitions:-AttributeName:idAttributeType:SKeySchema:-AttributeName:idKeyType:HASHProvisionedThroughput:ReadCapacityUnits:1WriteCapacityUnits:1
  • 大きく分けて前段と後段の2つの定義を追加しています
serverless.yml
environment:DYNAMODB_TABLE:${self:service}-${self:provider.stage}iamRoleStatements:-Effect:AllowAction:-dynamodb:Query-dynamodb:Scan-dynamodb:GetItem-dynamodb:PutItem-dynamodb:UpdateItem-dynamodb:DeleteItemResource:'arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}*'
  • environmentはファイル内で扱える変数のような感じです
    • テーブル名につけるprefixが何度か登場することになるのでenvironmentに定義しています
    • self:serviceは一行目のserviceの値でself:provider.stageは7行目あたりのstageの値
  • iamRoleStatementsはLambdaを実行するユーザの権限にDynamoDBへのアクセス許可を追加しています
serverless.yml
resources:Resources:Hello:Type:AWS::DynamoDB::TableProperties:TableName:${self:provider.environment.DYNAMODB_TABLE}-helloAttributeDefinitions:-AttributeName:idAttributeType:SKeySchema:-AttributeName:idKeyType:HASHProvisionedThroughput:ReadCapacityUnits:1WriteCapacityUnits:1
  • テーブルの定義をしています
  • テーブル名は上で定義したprefixにつなげてhelloという名前にしています

テーブルを作成する

  • serverless.ymlに設定を追加した状態でデプロイすると自動でテーブルが作成されます
sls deploy
  • Webコンソールにアクセスしてテーブルができていることを確認してみましょう

スクリーンショット 2020-03-03 0.38.26.png

DynamoDBへのアクセス処理の実装

ライブラリの追加

  • DynamoDBにアクセスするために必要なライブラリを追加します
npm i aws-sdk
# or
yarn add aws-sdk

DynamoDBへの接続処理

  • 今回は以下の3つの処理を実装しようと思います
    • 全量取得
    • IDで検索して1件取得
    • 1件登録
  • 全部一気にやると量が多いのでまずは全量取得からいきます
  • handler.jsを修正します
handler.js
'use strict';// AWS SDKをimportconstAWS=require('aws-sdk');// DynamoDBにアクセスするためのクライアントの初期化constdynamo=newAWS.DynamoDB.DocumentClient();// 環境変数からテーブル名を取得(あとでserverless.ymlに設定します)consttableName=process.env.tableName;// 全量取得module.exports.getAll=async()=>{constparams={TableName:tableName,};try{// DynamoDBにscanでアクセスconstresult=awaitdynamo.scan(params).promise();// 正常に取得できたらその値を返すreturn{statusCode:200,body:JSON.stringify(result.Items),};}catch(error){// エラーが発生したらエラー情報を返すreturn{statusCode:error.statusCode,body:error.message,};}};module.exports.hello=asyncevent=>{return{statusCode:200,body:JSON.stringify({message:event}),};};
  • 説明はだいたいコードのコメントに書いておきました
  • 今回はテーブルの内容を全量取得するので.scan()を使いましたがDynamoDB Clientは以下のようなAPIを提供します
  • 複数件取得
    • scan: 全件取得
    • query: 条件に該当した項目を全件取得
  • 単項目操作
    • get: 1件取得
    • put: 1件置換
    • update: 1件部分更新
    • delete: 1件削除
  • 新しくmodule.exportを追加したのでserverless.ymlも修正します
    • functionの項目を修正します
serverless.yml
# ...省略functions:hello:handler:handler.hellogetAll:handler:handler.getAllenvironment:tableName:${self:provider.environment.DYNAMODB_TABLE}-hello# ...省略
  • handler.jsmodule.export.getAllとしたのでhandler: handler.getAllを追加しました
  • environmenthandler.jsに対して環境変数としてテーブル名を渡しています

ローカルで動作確認

  • Lambdaのコードができたので動かしてみます
  • デプロイする前にローカルで動作確認しましょう

DynamoDBをローカルで動かすための設定

  • serverless-dynamodb-localというDynamoDBをローカルで動かすライブラリがあるのでインストールします
yarn add -D serverless-dynamodb-local
  • DynamoDBをローカルで動かすための設定も追加します
  • serverless.ymlの一番下に追加してください
serverless.yml
# ...省略plugins:-serverless-dynamodb-localcustom:dynamodb:stages:devstart:port:8082inMemory:truemigrate:trueseed:trueseed:hello:sources:-table:${self:provider.environment.DYNAMODB_TABLE}-hellosources:[./seeds/hello.json]
  • pluginsは先程インストールしたserverless-dynamodb-localを使うことを宣言しています
  • custom.dynamodbでローカルでDBを起動する時に使う設定をしています
    • portは何でも大丈夫です(デフォルトは8000)
    • seedは事前に登録しておくテストデータの設定です
      • 登録するデータは./seeds/hello.jsonに定義しておきます
  • テストデータとしてseeds/hello.jsonを作成します
seeds/hello.json
[{"id":"1","message":"Hello"},{"id":"2","message":"Hello!!!"},{"id":"3","message":"Hello World"}]
  • ServerlessFrameworkを使ってDBをインストールしセットアップを完了させます
sls install dynamodb

Lambda関数にローカルDB用の処理を追加

  • ローカルのDBを使うためにhandler.jsに少し手を加えます
handler.js
'use strict';constAWS=require('aws-sdk');// 環境変数にLOCALが設定されていたらローカルDB用の設定を使う(portはymlで定義したものを設定)constoptions=process.env.LOCAL?{region:'localhost',endpoint:'http://localhost:8082'}:{};constdynamo=newAWS.DynamoDB.DocumentClient(options);// ...省略

ローカルでDBにアクセスする

  • 準備が長くなりましたがいよいよアクセスしてみます
  • まずはDBを起動します
sls dynamodb start
  • 以下のようなログが出ればOKです
$ sls dynamodb start
Dynamodb Local Started, Visit: http://localhost:8082/shell
Serverless: DynamoDB - created table sls-sample-dev-hello
Seed running complete for table: sls-sample-dev-hello
  • LambdaのgetAll関数を叩きます
    • LOCAL=trueは環境変数としてLOCALにtrueを設定しています(handler.jsでローカルのDBを見に行く判定で使っていたやつ)
LOCAL=true sls invoke local--function getAll
  • 以下のようなログがでればOKです!
{"statusCode":200,"body":"[{\"message\":\"Hello\",\"id\":\"1\"},{\"message\":\"Hello!\",\"id\":\"2\"},{\"message\":\"Hello World\",\"id\":\"3\"}]"}

AWSにデプロイして動作確認

  • ローカルで確認できたらAWSにデプロイしましょう
sls deploy
  • コマンド一発でデプロイできて便利ですね
  • serverlessコマンドでアクセスしてみましょう
sls invoke --function getAll
  • 現状データがないのでデータは0件ですが200が返ってきていれば成功です
{"statusCode":200,"body":"[]"}
  • この時点でデータがとれることを確認したい人はWebコンソール上でデータを追加した上で叩いてみてください
    • あとでもいい人は次でデータ登録処理も追加するのでそのあとに取得できることは確認できます

スクリーンショット 2020-03-05 1.35.36.png

残りの関数を追加する

  • 同じ要領で1件取得と1件登録の処理を追加してみましょう

関数と設定の追加

  • handler.jsの一番下に関数を追加する
handler.js
// ...省略// 1件取得module.exports.get=asyncevent=>{// パラメータで渡されたidを取得const{id}=event;// 検索条件のidを指定constparams={TableName:tableName,Key:{id},};try{constresult=awaitdynamo.get(params).promise();return{statusCode:200,body:JSON.stringify(result.Item),};}catch(error){return{statusCode:error.statusCode,body:error.message,};}};// 1件登録module.exports.put=asyncevent=>{// 一意な値を作るためにタイムスタンプを取得constid=String(Date.now());const{message}=event;constparams={TableName:tableName,Item:{id,message},};try{constresult=awaitdynamo.put(params).promise();return{statusCode:200,body:JSON.stringify(result),};}catch(error){return{statusCode:error.statusCode,body:error.message,};}};
  • 全件取得の時は.scan()を使いましたが1件取得は.get()1件登録はput()を使っています
  • serverless.ymlのfunctionの項目に設定を追加します
servreless.yml
# ...省略functions:hello:handler:handler.hellogetAll:handler:handler.getAllenvironment:tableName:${self:provider.environment.DYNAMODB_TABLE}-helloget:handler:handler.getenvironment:tableName:${self:provider.environment.DYNAMODB_TABLE}-helloput:handler:handler.putenvironment:tableName:${self:provider.environment.DYNAMODB_TABLE}-hello

ローカルで動作確認

  • 1件登録
LOCAL=true sls invoke local--function put --data'{"message": "Hello!!!!!"}'
  • 以下のようなログが出ればOK
{"statusCode":200,"body":"{\"id\":\"1583340674414\",\"message\":\"Hello!!!!!\"}"}
  • 1件取得
LOCAL=true sls invoke local--function get --data'{"id": "1"}'
  • 以下のようなログが出ればOK
{"statusCode":200,"body":"{\"message\":\"Hello\",\"id\":\"1\"}"}

AWSで動作確認

  • ServerlessFrameworkのコマンドでデプロイします
sls deploy
  • 1件登録
sls invoke --function put --data'{"message": "Hello!!!!!"}'
  • 以下のようなログが出ればOK
{"statusCode":200,"body":"{\"id\":\"1583340839287\",\"message\":\"Hello!!!!!\"}"}
  • 1件取得
    • 直前の1件登録で追加したデータのIDを指定してみましょう
sls invoke --function get --data'{"id": "1583340839287"}'
  • 以下のようなログが出ればOK
{"statusCode":200,"body":"{\"id\":\"1583340839287\",\"message\":\"Hello!!!!!\"}"}

おまけ

  • 今回はscanとgetとputを作成しましたがquery(検索条件を指定して複数件取得)の場合は以下のような感じになります
    • パラメータで指定したIDに合致するレコードが複数ある場合に全部取得できるような感じです
module.exports.query=asyncevent=>{const{id}=event;constparams={TableName:tableName,KeyConditionExpression:'id = :id',ExpressionAttributeValues:{':id':id},};try{constresult=awaitdynamo.query(params).promise();return{statusCode:200,body:JSON.stringify(result.Items),};}catch(error){return{statusCode:error.statusCode,body:error.message,};}};

感想

  • LambdaからDynamoDBへのアクセスはDynamoDB Clientを使うと扱いやすいですね
  • ServerlessFrameworkや周辺ライブラリを使うとローカルでも動作確認できるので環境周りもとても充実しています
  • けっこう長くなってしまいましたがここまで読んでいただきありがとうございました

【Node.js】定義したクラスを別のファイルで使用する

$
0
0

定義したクラスを別のファイルで使用するには、module.exportsを使います。

まず元となるクラスを作成

smartPhone.js
classiphone{constructor(){}call(){console.log('call');}mail(){console.log('mail');}}classandroid{constructor(){}call(){console.log('call');}mail(){console.log('mail');}}

module.exportsで外部に公開します。

smartPhone.js
module.exports=iphone

これだけで、外部へ公開することができます。
しかし、今回クラスは2つあります。
クラスが複数ある場合はどうするかというと、objectにしてしまいます。

smartPhone.js
module.exports={IPHONE:iphone,ANDROID:android}

Keyは適当な名前、valueはクラス名を設定します。
これで指定したクラスが外部から読み込めるようになりました。

早速別ファイルから読み込んでみます。
requireで呼び出します。

usePhone.js
constiphone=require('./smartphone').IPHONEconstandroid=require('./smartphone').ANDROID

requireの引数にファイル名を指定します。".js"は省略可能です。
上記ではクラスごとで定数に代入していますが、わざわざこんなことはしません。

usePhone.js
const{IPHONE,ANDROID}=require('./smartphone')

分割代入をします。

あとはクラスをnewすればOKです。

usePhone
consta=newIPHONE()a.call()constb=newANDROID()b.call()

local host に接続したいのですがfirebase に繋がってしまいます。

$
0
0

こんにちは、 node初心者です。
個人プロジェクトでセットアップをしているのですがnodeを起動してlocalhost にアクセスしようとしたところで以前installしたfirebaseに繋がってしまいます。
何か策はないでしょうか?よろしくお願いします。!

Screen Shot 2020-03-05 at 16.58.05.png

Screen Shot 2020-03-05 at 16.58.22.png

gulpへの理解を深め、環境構築をしてみよう!

$
0
0

はじめに

フロントエンドエンジニアとして働き始めたばかりの新米エンジニアです。

今回はフロントエンドに必須のタスクランナーであるgulpについてです。

自分自身、gulpをきちんと理解して使用しておらず、勉強しても時間が経過すると忘れると思ったので、今回はメモとして残す意味で、あとは誰かの役に立てば良いな思いながら書いて行こうと思います。

初心者の方、他のタスクランナーからgulpに移行しようと思っている方にも分かりやすく、gulpについて&環境構築&実践の説明をしていきます。

主な内容

  • gulpとは
  • gulpで出来る事
  • 導入までの簡単な流れ
    • Node.jsとは
    • package.jsonとは
    • npmとは
  • 実際に環境構築してみよう
    • 1 . Node.jpのインストール
    • 2 . package.jsonファイルの作成
    • 3 . gulpをインストール
    • 4 . gulpfile.jsを作成
  • 準備:package.jsonの編集の仕方
  • 実践:gulpを動かそう!
  • さいごに

以上の流れで今回は進めていきたいと思います。

フォルダ構成

今回はデスクトップにいファイルを作成しました。

gulp-test
       ├ htdocs/
       │  └ index.html
       │   └ css/
       │      └ style.css
       └ src/
          └ scss/
              └ style.css

gulpとは

  • フロントエンド開発用のタスクランナーです。(ビルドシステムヘルパーと呼ばれる事もある)
  • タスクランナーとは、処理(タスク)を自動化で行ってくれるツールです。 gulpにはパッケージが沢山あり、プロジェクトに合わせて実行したい処理を組み合わせる事で、様々なタスクを実行できます。

つまり、普通だったら手間がかかるめんどくさい作業を、自動的に実行してくれるのがgulpと言う事です。

使用したい機能(パッケージ)を選択して導入するだけで、自分でカスタマイズした好きな機能で実装することができるのです。しかも案件ごとに。

なんて素敵なツールなのでしょう。

<参考サイト>
gulpの公式サイト:https://gulpjs.com/
リポジトリ:https://github.com/gulpjs/gulp

※開発の現場では、今回ご紹介するgulpGruntがよく使用されます。
→gulpとGruntの違いについて詳しく知りたい方は「絶対つまずかないGulp入門」こちらの記事が分かりやすかったのでご参考までに。

gulpで出来る事

では、それほどめんどくさい作業をgulpが行ってくれるのか。
よく使用されるパッケージの例を挙げてみたいと思います。

SassやLESSなどのコンパイル

SassやLESSなどのCSSプリプロセッサ(CSSメタ言語)を自動的にCSSにコンパイルしてくれます。
※コンパイル:ソースコード(プログラミング言語)を、オブジェクトコード(コンピュータが実行可能な形式)に変換する作業
※エディタの拡張機能で、コンパイルを自動的に行ってくれる物もあります。

ベンダープレフィックスの自動付与

IE対策などで使用する、CSSのベンダープレフィックス(-webkit-や-ms-がついたcssコード)を自動的に吐き出してくれます。

<参考記事>
ベンダープレフィックスとは

画像の圧縮

ウェブサイトを表示するロード時間を減らす方法として、画像の圧縮があります。
サイトからの離脱率を減らし、ユーザーに気持ちよくサイトをみてもらう為にも、画像の圧縮は必須です。
手作業でtinypngtinyjpgjpegminiでも圧縮する事もできますが、gulpはその作業を自動的に行ってくれます。

CSSやJavaScriptなどのミニファイ

JavaScriptやCSSなどのコードの中の不要な改行、インデントなどを削除し、動作はそのままで圧縮・軽量化してくれます。
これもロード時間を減らす方法として大事な作業です。
※ミニファイ:圧縮,軽量化

ブラウザーシンク

HTML/CSS/JSを保存するたびに、ブラウザをリロードしてくれます。

他にも多くのパッケージがgulpには存在しているので、お好みでカスタマイズして使用できます。

gulpのパッケージが書いてある一覧ページがありましたので、ご参考にどうぞ。
gulp-パッケージ一覧 : https://qiita.com/oreo3@github/items/0f037e7409be02336cb9

導入までの簡単な流れ

gulpを導入するまでの流れ、説明を大雑把にしていきます。

 1 . Node.jsのインストール
 2 . package.jsonファイルを作成
 3 . gulpをインストール

Node.js とは

JavaScriptは本来クライアントサイドで動く言語であり、HTMLで書かれたページに動きをつけたりする。
→Node.jsはサーバサイドで動くJavaScriptである。

package.jsonとは

npmではpackege.jsonというファイルで依存関係のあるパッケージを一括インストールできたり、タスクも管理できる。
必要な設定事項を記述することで、簡単にプロジェクトの管理や環境構築を行うことができるのだ。

npmとは

正式名称は"Node Package Managernpm"と言い、Node.jsのパッケージを管理することができる。
Node.jsをインストールすることで自動的にインストールされます。

環境構築してみよう

次は、実際にコマンドを打ってgulpを使用してみましょう。

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

スクリーンショット 2020-01-22 0.05.11.png

こちらのリンクからNode.jsをダウンロードします。

Node.jsの公式サイト
https://nodejs.org/ja/

Windowsの方はこちらからです

Node.jsダウンロードページ
https://nodejs.org/ja/download/

ちなみに、ダウンロードの際は推奨版を入れる事をオススメします!!!
(最新版だとパッケージで対応してない場合がある)

ダウンロードが終わったら、保存されたファイルを解凍して開いてくださいね。
スクリーンショット 2020-01-22 0.11.24.png

インストールが完了したらターミナルを起動し、以下のコマンドを実行します。

node -v   ・・・Node.jsのバージョンの確認
↓
v12.13.1

自身がダウンロードしたバージョンが表示されたら完了です。

*余談

ちなみにですが、ターミナルの使用方法は複数あり、

  • 「ターミナル」Macに入ってるアプリケーション
  • 「iTerm2」
  • エディタ(VScode,Atom,Sublimeなど)

から使用することが多いです。
(私はエディタ:VScodeからがほとんど)

詳細はこちらをご参考にしてください◎

VSCodeでターミナルを起動するショートカット:https://utano.jp/entry/2018/01/visual-studio-code-terminal-window/
Macターミナルは何ができる?初心者向けの使い方と困った時のQ&A:https://www.sejuku.net/blog/4202

2.package.jsonファイルを作成

パッケージをインストールするにあたり、まずはプロジェクトディレクトリ配下にpackage.jsonファイルを準備します。

この記事の冒頭に表記したフォルダ構成のところを参考にしてくださいね。

cdコマンド(=移動する)でプロジェクトのディレクトリの場所まで移動します(パスは適宜変更して下さい)。

cd \Users\{ユーザ名}\Desktop\gulp-test

今回はデスクトップにgulpを導入したいファイル(gulptest)を作成したので、上記のようなパスになりました。

次です。
node.jsをインストールをするとnpmコマンドが使えるようになっています。
package.jsonファイルを作成するために、このコマンドを入力してください。

npm init -y

この表示が出てきたら成功です。

package.json
{"name":"gulp-test","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"keywords":[],"author":"","license":"ISC"}

-yをつけたことで、設定がデフォルトになりました。

もし、npmをインストール済みであれば、アップデートします。

npm update -g npm

(npmのアップデートはnpm自体で実行)
もし、permissionがなんとか〜〜〜ってエラーが出たら、頭にsudoをつけて実行してみてください!

sudo npm update -g npm

sudoコマンドは、スーパーユーザー:root(や他のユーザ)の権限でコマンドを実行するときに使用します。

わざわざユーザーを切り替えずに、他のユーザーの権限で操作が可能なので、非常に便利で、必殺技みたいな感じです。(乱用禁止)

npmのバージョンも確認してみましょう。

npm -v   ・・・npmのバージョンの確認
↓
6.13.6

3. gulpをインストール

やっと、gulpを導入するところまで来ました。

gulpのダウンロード

npm install -D gulp   ・・・ローカルインストール
npm install -g gulp   ・・・グローバルインストール

両方のコマンドを打って導入します。

ローカルインストールはそのプロジェクトのフォルダ内でのみ利用でき、グローバルインストールはそのマシン上のどこからでも利用できるようになるります。
デフォルトはローカルインストールされるようになっており、グローバルインストールしたい場合は-gオプションを付けて実行します。

<参考記事>
gulpのアプローチ "なぜグローバルとローカルにインストールが必要なのか"

node_mosulesというファイルが作成されていたら成功です!!!

先程まではpackage.jsonファイルしかありませんでしたが、
gulを導入したことで、ファイルが3つに増えているかと思います。

 package.json
 node_modules
 package-lock.json

また、package.jsonに記載されてる内容を確認すると
devDependecesという項目が追加されてるかと思います。

jackage.json
{"name":"gulp-test","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"keywords":[],"author":"","license":"ISC","devDependencies":{"gulp":"^4.0.2"}}

下二行ですね。ちゃんと、インストールしたgulpのバージョンも記載してあります。"gulp": "^4.0.2"

ここまで来たらあと少しです!

4.gulpfile.jsを作成

gulpを使用する準備ができたので、
具体的に実行するタスク内容を設定するgulpfile.jsを他のファイルと同じディレクトリに作ります。

touch gulpfile.js

中身は空ですがgulpfile.jsが作成されたかと思います。
touchはgulpに関係はなく、ターミナルのコマンドでファイルを新規作成するものです。

この時点でディレクトリ内に存在してるファイルは
 
 node_modules
 package-lock.json
 package.json
 gulpfile.js

の4つです。

フォルダの中身を確認してみましょう。

gulp-test
       ├ htdocs...
       ├ src...
       ├ node_modules
       ├ package-lock.json
       ├ package.json
       └ gulpfile.js

desktop配下のgulp-testというファイルの中に、4つのファイルが追加されてるのがわかります。

最後にgulpのバージョンを確認してみましょう。

gulp -v   ・・・gulpのバージョンの確認
↓
CLI version: 2.2.0
Local version: 4.0.2

もし、Localのバージョンが下記のようにUnknownになっていたら

CLI version: 2.2.0
Local version: Unknown

以下のコマンドを入力すれば大丈夫です◎

sudo npm install --save-dev gulp 

gulpfile.js内に
以下のコードを入力することで、gulpが使用できるようになります。

gulpfile.js
vargulp=require("gulp");

これで、gulpを使用する一通りの作業はできたと思います。

お疲れ様でした!!!

準備:gulpfile.jsの編集の仕方

コマンドラインで必要なパッケージをインストール

以下のコマンドを使用する事で、パッケージをインストールできます。

npm install --save-dev {パッケージ名}

また、コマンドは以下のように省略することも可能です。

//"install" → "i"
//"--save-dev" → "-D"

npm i -D gulp {パッケージ名}

gulpfile.jsでタスクを作成

package.jsに記入するタスクの書き方の例です。

gulpfile.js
//使うパッケージの宣言var{変数}=require('{パッケージ名}');//個々のタスクgulp.task('{タスク名}',function(){//実行される処理});//watchタスク… タスク名は任意。「gulp.watch()」でファイルの状態を監視・タスク実行gulp.task('watch',function(){gulp.watch('{監視したいファイルのパス}',[{実行するタスク名}]);});//defaultタスク… 「npx gulp」コマンドで実行するタスクを設定gulp.task('default',['{タスク名}','watch']);

地味に大事な余談

パッケージをダウンロードする時のコマンドnpm i -D gulp {パッケージ名}-Dを実行することで、package.jsondevDependenciesインストールしたパッケージ名とバージョンが記録されます。
-Dつけない場合はdependenciesに記録されます。

devDependenciesは主に開発用。(gulpやsassなど)
dependenciesは表側。(jqueryやslickなど)

実践:gulpを動かそう!

では、実際にgulpを動かしてみましょう!
私がいつも使用するのはこれらのパッケージです。

  • SASSのコンパイル
  • ベンダープレフィックスを自動的に付与
  • エラーが原因でタスクが強制停止するのを防止

この3つを行うために必要なgulpfile.jsを書きたいと思います。

まずは、ターミナルに以下のコマンドを実行して、パッケージをインストールします。
※省略した形で書きます。

npm i -D gulp 
npm i -D gulp-sass 
npm i -D gulp-postcss 
npm i -D gulp-autoprefixer 
npm i -D gulp-sass-glob 
npm i -D gulp-csscomb

全部打つのは面倒なので、もっと省略した形があります。
以下のようにパッケージごとに半角で区切って一行にまとめて書くことで、一括インストールすることも可能です。

//npm i -D {パッケージ} {パッケージ} {パッケージ}
//例 ) npm i -D gulp gulp-sass gulp-postcss gulp-autoprefixer gulp-sass-glob gulp-csscomb

実行します。

次に、gulpfile.jsにはタスクの内容を記述します。

gulpfile.js
vargulp=require('gulp');//gulpを使用する宣言varsass=require('gulp-sass');//SASSの使用varpostcss=require('gulp-postcss');varcsscomb=require('gulp-csscomb');//cssを綺麗に並び替えるvarsassGlob=require('gulp-sass-glob');// Sassの@importにおけるglobを有効にするgulp.task('sass',function(){returngulp.src('src/**/*.scss').pipe(sassGlob()).pipe(sass({outputStyle:'expanded'}).on('error',sass.logError)).pipe(postcss([require('autoprefixer')({grid:true}),])).pipe(gulp.dest('htdocs/css/')).pipe(csscomb());});gulp.task('w',function(){gulp.watch('src/**/*.scss',gulp.series('sass'));});

確認:gulpが実行されているか見てみよう!

それでは現在のディレクトリの確認です。
構造はこのようになっているかと思います。

gulp-test
       ├ htdocs/
       │  └ index.html
       │   └ css/
       │      └ style.css
       └ src/
       │   └ scss/
       │       └ style.css
       ├ node_modules
       ├ gulpfile.js
       ├ package.json
       └ package-lock.json

それでは、実際にsassがコンパイルされるか、確認してみたいと思います。
src/scss/style.cssに以下のコードを記入しましょう。

style.scss
.box{display:flex;align-items:center;justify-content:center;color:plum;}.button{transition:0.3s;background-color:grey;color:#000;&:hover{transition:0.3s;background-color:#fff;}}

そして、実行するために、このコマンドを入力します。

gulp w

すると、

htdocs/css/style.cssには見事コンパイルされ、ベンダープレフィックスが付与されたコードになってるかと思います。

style.css
.box{display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center;-webkit-box-pack:center;justify-content:center;color:plum;}.button{-webkit-transition:0.3s;transition:0.3s;background-color:grey;color:#000;}.button:hover{-webkit-transition:0.3s;transition:0.3s;background-color:white;}

command + sで保存する必要もなく、
stylle.scssの内容を変更するだけで自動的にコンパイルされるのもわかるかと思います。

自動的に更新されるのはとても便利ですね。

自分の好きなプラグインを見つけて、オリジナルのgulpを作ってみましょう!

お疲れ様でした!!!

おまけ

今までの流れが、初めてgulpを使用する人に向けた内容です。

一度gulpの環境構築をしたら、
2回目以降は初回と少し違った流れになります。

主に違うのは
①Node.jsをインストールしなくて良い
②nodeとnpmとgulpのバージョンを確認して、必要があればアップデートする
くらいです。

npmのアップデートの時に、npm audit fixというエラーが出る時があるので、
私が以前書いた記事を先に軽く読んで、参考にしていただけると幸いです。

<参考サイト>

昔gulpを使用していたサ大体ここら辺のコマンドを実行したら動きます◎

npm init -y
npm install -D gulp
gulp -v
npm install -D gulp gulp-sass

ご参考までに。

さいごに

gulpの使い方は、なんとなくご理解いただけたでしょうか?
最初は私も訳がわからなかったのですが、最近は使っていくうちに理解できるようになってきました。

フロントエンドは毎年新しい技術が出てくるため追いつくのが大変ですが、がんばっていきましょう><!!!

実践できるか何度も確認して記事にしていますが、間違いや不備等見つけた方がいらっしゃいましたら
ご連絡いただけると幸いです。

参考サイト一覧

https://www.go-next.co.jp/blog/web/soft_tool/13407/#Gulp
https://parashuto.com/rriver/tools/updating-node-js-and-npm
https://ics.media/entry/3290/

Lambda から DynamoDB にアクセス (Node.js)

$
0
0

テーブルの一覧

list_tables.js
varAWS=require("aws-sdk");vardynamodb=newAWS.DynamoDB({region:'us-east-1'})varparams={Limit:100}exports.handler=async(event)=>{console.log("*** start ***")try{vardata=awaitdynamodb.listTables(params).promise()console.log(data)}catch(ee){console.log(ee)}constresponse={statusCode:200,body:JSON.stringify('Hello from Lambda!'),}returnresponse;}

テーブルの説明

describe_table.js
varAWS=require("aws-sdk");vardynamodb=newAWS.DynamoDB({region:'us-east-1'})vartable="Movies"varparams={TableName:table}exports.handler=async(event)=>{console.log("*** start ***")try{vardata=awaitdynamodb.describeTable(params).promise()console.log(data)}catch(ee){console.log(ee)}constresponse={statusCode:200,body:JSON.stringify('Hello from Lambda!'),}returnresponse}

Nuxt.jsでprocess.env.NODE_ENVを参照する際の挙動についてまとめてみた

$
0
0

Nuxtでprocess.env.NODE_ENVを参照する際にハマりがちだったので挙動をまとめました。

なお、環境は以下のとおりです。

package.json
"dependencies":{"cross-env":"7.0.0","nuxt":"2.11.0"},

※この記事で記載しているファイルは内容を一部抜粋したものです。

デフォルトの挙動

まず、何の設定もせずにyarn devしたときとyarn build && yarn startしたときの値を確認します。

package.json
"scripts":{"dev":"nuxt","build":"nuxt build","start":"nuxt start"},
pages/index.vue
<template><div/></template><script>exportdefault{mounted(){console.log(process.env.NODE_ENV);}};</script>
実行方法process.env.NODE_ENV
yarn dev"development"
yarn build && yarn start"production"

このようにデフォルトで値が入っていることがわかりました。次に、環境変数を指定した際の挙動を見ていきます。

環境変数を指定したとき

package.json
"scripts":{"dev":"cross-env NODE_ENV=dev nuxt","build":"cross-env NODE_ENV=build nuxt build","start":"cross-env NODE_ENV=start nuxt start"},
実行方法process.env.NODE_ENV
yarn dev"development"
yarn build && yarn start"production"

環境変数に指定した値が入ると思いきや、デフォルトの値が優先されてしまいました。これでは困るので、次にNODE_ENVnuxt.config.jsに指定したときの挙動を見てみます。

env を指定したとき

nuxt.config.js
env:{NODE_ENV:process.env.NODE_ENV}
package.json
"scripts":{"dev":"cross-env NODE_ENV=dev nuxt","build":"cross-env NODE_ENV=build nuxt build","start":"cross-env NODE_ENV=start nuxt start"},
実行方法process.env.NODE_ENV
yarn dev"dev"
yarn build && yarn start"build"

今回はこのようにビルド時に埋め込んだ値が取得できることがわかりました。

まとめ

というわけで、process.env.NODE_ENVの値を Nuxt で定義したコンポーネントから取得する際は、nuxt.config.jsenvプロパティにNODE_ENVを指定したほうが間違いは起こりにくいかもしれません。

しかし、今回の記事では触れていませんが、例えば Nuxt を express で動かす際などは、process.env.NODE_ENVの値をserver.jsなどのファイルから参照する関係で、予想と違う形でprocess.env.NODE_ENVの値が返ってくることがあるかもしれません。

あまりないケースかとは思いますが、そのようなことを考えると build 時とサーバー起動時に設定するNODE_ENVの値は同じにしておいたほうが無難っぽいです。

Node.js Expressフレームワークを使用する(雛形生成)

$
0
0

はじめに

前回の投稿でExpressフレームワークを前準備しましたので、次にexpressコマンドで雛形を生成します。。

環境

OS:Windows 10 Pro 64bit
node.js:v12.16.1
npm:v6.13.4
Express:v4.16.1

雛形の生成

expressコマンドで生成します。
あらかじめコマンドプロンプトで作業フォルダに移動します。
今回は「D:\Node\ExpressTest01」を作業フォルダにします。

D:
CD Node\ExpressTest01

移動しましたら、次のコマンドで雛形を生成します。

express --view=ejs

「--view=ejs」オプションはテンプレートエンジンにEJSを使用すると言う意味になります。

D:\Node\ExpressTest01>express --view=ejs

   create : public\
   create : public\javascripts\
   create : public\images\
   create : public\stylesheets\
   create : public\stylesheets\style.css
   create : routes\
   create : routes\index.js
   create : routes\users.js
   create : views\
   create : views\error.ejs
   create : views\index.ejs
   create : app.js
   create : package.json
   create : bin\
   create : bin\www

   install dependencies:
     > npm install

   run the app:
     > SET DEBUG=expresstest01:* & npm start

これで雛形は生成されました。
Visual Studio Codeで開きますとこのようになります。
express01.jpg

使用するライブラリの一括インストール

次のコマンドを実行して、ライブラリの一括インストールを行います。

npm install

npmはpackage,jsonの「dependencies」に指定されているライブラリを一括インストールする機能があります。
雛形を生成した際に、必要なライブラリ情報が全て記載されるので便利です。

D:\Node\ExpressTest01>npm install
npm notice created a lockfile as package-lock.json. You should commit this file.
added 53 packages from 38 contributors and audited 141 packages in 5.167s
found 0 vulnerabilities

動作確認

コマンドプロンプトで次のコマンドを実行します。

npm start
D:\Node\ExpressTest01>npm start

> expresstest01@0.0.0 start D:\Node\ExpressTest01
> node ./bin/www

ブラウザで「http://localhost:3000」にアクセスします。
次のキャプチャが表示されればOKです。
express02.jpg

コマンドプロンプトには次のように表示されます。

GET / 304 14.612 ms - -
GET /stylesheets/style.css 304 1.752 ms - -

停止したい時は「Ctrl + C」です。
「バッチ ジョブを終了しますか (Y/N)?」と聞かれますので、「Y」を入力します。

バッチ ジョブを終了しますか (Y/N)? Y

まとめ

このような流れで雛形を生成します。


TypeScriptでスクレイピングしてみよう

$
0
0

初めに

TypeScriptによるスクレピングの簡単な手法を紹介したいと思います。
記事のポイントはあくまでもTypeScriptの使用、高度なスクレピング技法の紹介ではありません。

前提条件

  • ある程度Typescriptの文法が分かってること
  • Node.jsの環境が整って、npmコマンド使えること
  • グローバル環境にTypeScriptに入ってること
  • 法に触れること、人に迷惑かけることをしないこと

プロジェクト初期化

mkdir[好きなディレクトリ] &&cd[好きなディレクトリ]

package.jsonとtsconfig.jsonの初期化

npm init -y && tsc --init

プロジェクトのフォルダ内にsrcフォルダを作ります。

mkdir src

tscofig.jsonのrootDirをsrcフォルダに指定します。

tscofig.json
 ...
     "rootDir": "./src",       /* Specify the root directory of input files. Use to control the output directory structure with --outDir.*/
...

srcフォルダ内にcrowllwe.tsファイルを作って、中身 console.log('test')を追加します。

crowllwe.ts
console.log('test');

現時点使用するライブラリをインストール

  • npm install typescript -D
  • npm install ts-node -D

package.jsonを修正します。

package.json
...
  "scripts": {"dev": "ts-node ./src/crowller.ts"},
...

コマンドラインで npm run devを実行します。testがもし正常に表示出来たらオーケーです。

$ npm run dev

>[好きなディレクトリ名]@1.0.0 dev [好きなディレクトリ名]
> ts-node ./src/crowller.ts

test

ここまで初期化は完了です。
ディレクトリ構成は以下の通りです。

好きなディレクトリ
|-node_modules
|-src
|- |- crowller.ts
|- package-lock.json
|- package.json
|- tsconfig.json

HTMLレスポンス取得

ターゲットサイトからHtmlレスポンスもらう必要がある為、リクエスト送れるライブラリsuperagentを使用します。

npm install superagent --save

インストール終わったら、crowller.tsにimportします。

crowller.ts
importsuperagentfrom'superagent'

この場合、恐らくIDEに怒られます。vscode使用してコーティングする場合、以下のメッセージが表示されます。

'superagent' が宣言されていますが、その値が読み取られることはありません。ts(6133)
モジュール 'superagent' の宣言ファイルが見つかりませんでした。'/qiita-spider-ts/node_modules/superagent/lib/node/index.js' は暗黙的に 'any' 型になります。
  Try `npm install @types/superagent` if it exists or add a new declaration (.d.ts) file containing `declare module 'superagent';`ts(

なぜなら、superagentはjavascriptで書かれているライブラリ、Typescriptが直接認識することができません。
その場合、ライブラリの翻訳ファイルが必要になります。翻訳ファイルは.d.tsの拡張子を持ってます。

翻訳ファイルをインストールします。

npm install @types/superagent -D

これでエラーが解決できるはずです、それでも消えない場合、一回IDEを再起動することお勧めします。
実際リクエスト送信して、HTMLリスポンス受けとってみましょう。
ターゲットサイトは任意で構いません。

crowller.ts
importsuperagentfrom'superagent'classCrowller{privateurl="url"constructor(){this.getRawHtml();}asyncgetRawHtml(){constresult=awaitsuperagent.get(this.url);console.log(result.text)}}constcrowller=newCrowller()

npm run devで実行すると、レスポンスもらえたらオーケーです。

サンプル
...
<spanclass='c-job_offer-detail__term-text'>給与</span></div></th><tdclass='c-job_offer-detail__description'><strongclass='c-job_offer-detail__salary'>550万 〜 800万円</strong></td></tr><tr><th>
...

レスポンスから必要なデータを抜き取る

正規表現で抜き取ることもできますが、今回は多少便利になるcheerioというライブラリを使用します。
ドキュメント

npm install cheerio --save
npm install @types/cheerio -D

cheerioを使用すれば、jQueryのような文法でHTMLをから内容を抜き取れます。
実際使ってみます、下記のDOM構造からテキスト内容を抜き取るためにcrowller.tsを修正します。
tempsnip.png

crowller.ts
importsuperagentfrom'superagent';importcheeriofrom'cheerio';classCrowller{privateurl="url"constructor(){this.getRawHtml();}asyncgetRawHtml(){constresult=awaitsuperagent.get(this.url);this.getJobInfo(result.text);}getJobInfo(html:string){const$=cheerio.load(html)constjobItems=$('.c-job_offer-recruiter__name');jobItems.map((index,element)=>{constcompanyName=$(element).find('a').text();console.log(companyName)})}}constcrowller=newCrowller()

実行してみます。

$ npm run dev

> qiita-spider-ts@1.0.0 dev 好きなディレクトリ名\qiita-spider-ts
> ts-node ./src/crowller.ts

xxx株式会社
株式会社xxx
xxx株式会社
...

データの保存

srcフォルダと同じ階層でデータ保存用のdataフォルダを新規追加します。

|- node_modules
|- src
|- data
|- |- crowller.ts
|- package-lock.json
|- package.json
|- tsconfig.json

取得したデータをjson形式でdataフォルダに保存します。
その前にデータに含む要素を決めるためのインターフェースを定義します。
転職サイトをターゲットにしてるため、会社名ポジション提示年収の三つをインターフェースの要素として追加します。

crowller.ts
...interfacejobInfo{companyName:string,jobName:string,salary:string}...

そして配列に継承させて、データを入れていきます。

crowller.ts
...getJobInfo(html:string){const$=cheerio.load(html)constjobItems=$('.c-job_offer-box__body');constjobInfos:jobInfo[]=[]//インターフェース継承jobItems.map((index,element)=>{constcompanyName=$(element).find('.c-job_offer-recruiter__name a').text();constjobName=$(element).find('.c-job_offer-detail__occupation').text();constsalary=$(element).find('.c-job_offer-detail__salary').text();jobInfos.push({companyName,jobName,salary})});constresult={time:(newDate()).getTime(),data:jobInfos};console.log(result);}...

再度実行してみます。データが綺麗になってることが分かります。

$ npm run dev

> qiita-spider-ts@1.0.0 dev 好きなディレクトリ名\qiita-spider-ts
> ts-node ./src/crowller.ts

{ time: 1583160397866,
  data:
   [ { companyName: 'xx株式会社',
       jobName: 'フロントエンドエンジニア',
       salary: 'xxx万 〜 xxx万円' },
     { companyName: '株式会社xxxx',
   ...

保存用の関数を定義

generateJsonContentというデータ保存用の関数を定義します。

crowller.ts
...asyncgetRawHtml(){constresult=awaitsuperagent.get(this.url);constjobResult=this.getJobInfo(result.text);//整形後のデータを受け取ります。this.generateJsonContent(jobResult);//保存用の関数に渡します。}// 保存用の関数generateJsonContent(){}...getJobInfo(html:string){...constresult={time:(newDate()).getTime(),data:jobInfos};returnresult}

でも、そのままデータを受け取れないので保存用のinterfaceを定義します。

crowller.ts
interfaceJobResult{time:number,data:JobInfo[]}

それを保存用の関数の引数型として渡します。

crowller.ts
...generateJsonContent(jobResult:JobResult){}...

データをファイルに保存するために、node.jsのファイル操作関連のライブラリをimport

crowller.ts
importfsfrom'fs';importpathfrom'path'

generateJsonContent関数の中身書いていきます。

scowller.ts
...generateJsonContent(jobResult:JobResult){constfilePath=path.resolve(__dirname,'../data/job.json')letfileContent={}if(fs.existsSync(filePath)){fileContent=JSON.parse(fs.readFileSync(filePath,'utf-8'));}fileContent[jobResult.time]=jobResult.data;fs.writeFileSync(filePath,JSON.stringify(fileContent));}...

今の内容ですと、恐らく fileContent[jobResult.time]がエラーになると思います。
エラーの内容は以下の通り。

(property) JobResult.time: number
Element implicitly has an 'any' type because expression of type 'number' can't be used to index type '{}'.
  No index signature with a parameter of type 'number' was found on type '{}'.ts(7053)

これを解決するには fileContentに型を振る必要があります。
そのまま let fileContent:any = {}にしてもいいですが、
ちゃんとしたインターフェース定義した方がtypescriptらしいです。

crowller.ts
...interfaceContent{[propName:number]:JobInfo[];}...generateJsonContent(jobResult:JobResult){...letfileContent:Content={}...}

最後に実行してみましょう。

npm run dev

dataフォルダの下にjob.jsonファイルが作られて、データも保存されてるはずです。

tempsnip.png

終わりに

最初計画として、Typescriptを使ってExpressでスクレピングコントロールできるAPIを作るまでやりたかったのですが、
流石に長すぎて良くないと思いましたので、また今度時間ある時に。

crowller.ts
importfsfrom'fs';importpathfrom'path'importsuperagentfrom'superagent';importcheeriofrom'cheerio';interfaceJobInfo{companyName:string,jobName:string,salary:string}interfaceJobResult{time:number,data:JobInfo[]}interfaceContent{[propName:number]:JobInfo[];}classCrowller{privateurl="url"constructor(){this.getRawHtml();}asyncgetRawHtml(){constresult=awaitsuperagent.get(this.url);constjobResult=this.getJobInfo(result.text);this.generateJsonContent(jobResult)}generateJsonContent(jobResult:JobResult){constfilePath=path.resolve(__dirname,'../data/job.json')letfileContent:Content={}if(fs.existsSync(filePath)){fileContent=JSON.parse(fs.readFileSync(filePath,'utf-8'));}fileContent[jobResult.time]=jobResult.data;fs.writeFileSync(filePath,JSON.stringify(fileContent));}getJobInfo(html:string){const$=cheerio.load(html)constjobItems=$('.c-job_offer-box__body');constjobInfos:JobInfo[]=[]jobItems.map((index,element)=>{constcompanyName=$(element).find('.c-job_offer-recruiter__name a').text();constjobName=$(element).find('.c-job_offer-detail__occupation').text();constsalary=$(element).find('.c-job_offer-detail__salary').text();jobInfos.push({companyName,jobName,salary})});constresult={time:(newDate()).getTime(),data:jobInfos};returnresult}}constcrowller=newCrowller()

AWS Lambda入門③(Node編)〜API Gatewayで関数を公開する〜

$
0
0

概要

Api Gatewayとは

  • API GatewayはAWSが提供するサービスでHTTPでアクセス可能なAPIを作成できるサービスです
  • AWSの様々なサービスの呼び出しができるのでクライアントからするとLambdaなどをHTTPアクセスで実行するような感覚で使うことができます

API GatewayでLambda関数を公開する

Hello関数を公開してみる

  • まずは前回までで作ってあるHello関数を公開してみます
  • serverless.ymlに設定を追加します
serverless.yml
# ...省略functions:hello:handler:handler.helloevents:-http:path:hellomethod:get# ...省略
  • handlerの下にいくつか追加しました
    • pathはアクセスするときのpathです
    • methodはアクセスするときのHTTPメソッドです
  • 簡単ですね!

ローカルで動作確認

  • デプロイする前にまずはローカルで動作確認します
  • API Gatewayをローカルで動かすために必要なライブラリを追加します
yarn add -D serverless-offline@next

serverless-offlineのドキュメントは2020/3/5時点でα版の6系で書かれているとのことなので@nextをつけて6系をインストールする

  • serverless.ymlに設定を追加します
serverless.yml
# ...省略plugins:-serverless-offline# 追加した行-serverless-dynamodb-localcustom:serverless-offline:# 追加した行httpPort:8083# 追加した行dynamodb:stages:devstart:port:8082inMemory:truemigrate:trueseed:trueseed:hello:sources:-table:${self:provider.environment.DYNAMODB_TABLE}-hellosources:[./seeds/hello.json]# ...省略
  • pluginsserverless-offlineを追加しました
  • customにサーバ起動時のポート番号を追加しました
    • デフォルトは3000です
  • 以下のコマンドでサーバを起動します
sls offline start
  • ログにURLが表示されているはずです
┌───────────────────────────────────────────┐
   │                                           │
   │   GET | http://localhost:8083/dev/hello   │
   │                                           │
   └───────────────────────────────────────────┘

スクリーンショット 2020-03-06 1.31.34.png

  • うまくいけば画像のような結果になっているはずです
  • Lambdaを直接叩いていた時はstatusCodemessageの2つが返ってきていましたが今回はmessageだけです
  • statusCodeはAPI GatewayがHTTPレスポンスのステータスコードとしてセットして返却してくれています

AWSにデプロイして動作確認

  • ローカルで確認できたのでAWSにデプロイします
  • いつも通りServerlessFrameworkのコマンドを使います
sls deploy
  • デプロイに成功すると作成されたAPIのURLがログに出力されます
    • こんな感じ
Serverless: Stack update finished...
Service Information
service: sls-sample
stage: dev
region: ap-northeast-1
stack: sls-sample-dev
resources: 24
api keys:
  None
endpoints:
  GET - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello
functions:
  hello: sls-sample-dev-hello
  put: sls-sample-dev-put
  get: sls-sample-dev-get
  query: sls-sample-dev-query
  getAll: sls-sample-dev-getAll
layers:
  None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.
✨  Done in 351.49s.
  • REST Clientでアクセスして確認します

スクリーンショット 2020-03-06 1.34.29.png

  • ステータスコード200で成功しました
  • これでhello関数を世の中に公開することができました

全ての関数を公開する

  • hello関数と同じように残りの関数も公開してみましょう

API Gatewayの設定追加

  • serverless.ymlを修正します
serverless.yml
# ...省略functions:hello:handler:handler.helloevents:-http:path:hellomethod:getput:handler:handler.putenvironment:tableName:${self:provider.environment.DYNAMODB_TABLE}-helloevents:-http:path:messagesmethod:postget:handler:handler.getenvironment:tableName:${self:provider.environment.DYNAMODB_TABLE}-helloevents:-http:path:messages/{id}method:getgetAll:handler:handler.getAllenvironment:tableName:${self:provider.environment.DYNAMODB_TABLE}-helloevents:-http:path:messagesmethod:get# ...省略
  • functionの項目にそれぞれURLとHTTPメソッドを追加しました
    • getのpathはmessages/{id}とすることでidをURLで指定するようにしました
  • 関数を一部修正します
    • get関数がURLに埋め込まれたidを取得できるようする
    • put関数が渡されたパラメータを取得できるようにする
handler.js
// ...省略module.exports.get=asyncevent=>{const{id}=event.pathParameters;// この行を修正constparams={TableName:tableName,Key:{id},};try{constresult=awaitdynamo.get(params).promise();return{statusCode:200,body:JSON.stringify(result.Item),};}catch(error){return{statusCode:error.statusCode,body:error.message,};}};// ...省略module.exports.put=asyncevent=>{constid=String(Date.now());const{message}=JSON.parse(event.body);// この行を修正constparams={TableName:tableName,Item:{id,message},};try{awaitdynamo.put(params).promise();return{statusCode:200,body:JSON.stringify({id,message}),};}catch(error){return{statusCode:error.statusCode,body:error.message,};}};// ...省略

ローカルで動作確認

  • ローカルサーバを立ち上げます
    • ローカルのDBを見に行くようにLOCAL=trueをつけて環境変数を設定しておきます
    • 詳しくは前回のDynamo編をご参照ください
LOCAL=true sls offline start
  • ログにURLが出力されます
┌────────────────────────────────────────────────────┐
   │                                                    │
   │   GET  | http://localhost:8083/dev/hello           │
   │   POST | http://localhost:8083/dev/messages        │
   │   GET  | http://localhost:8083/dev/messages/{id}   │
   │   GET  | http://localhost:8083/dev/messages        │
   │                                                    │
   └────────────────────────────────────────────────────┘
  • REST Clientでアクセスしてみましょう
  • それぞれ成功すると以下のようになるはずです

スクリーンショット 2020-03-06 2.05.46.png
スクリーンショット 2020-03-06 2.06.02.png
スクリーンショット 2020-03-06 2.06.22.png

AWSで動作確認

  • AWSにデプロイしてREST Clientでアクセスしてみましょう
sls deploy
  • 関数の文だけURLが表示されます
endpoints:
  GET  - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello
  POST - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/messages
  GET  - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/messages/{id}
  GET  - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/messages
functions:
  hello: sls-sample-dev-hello
  put: sls-sample-dev-put
  get: sls-sample-dev-get
  getAll: sls-sample-dev-getAll
  • REST Clientでアクセスしてみてください
  • キャプチャは省略しますがローカルのときと同じようにアクセスできていればOKです

まとめ

  • API Gatewayを使うことでHTTリクエストでLambda関数を実行できました
  • ServerlessFrameworkを使うと簡単にAPI Gatewayの設定をすることができます
  • ローカルで動作させるためのライブラリも整っているため開発もやりやすくていいですね

インストールしている Node.js のバージョンをダウングレードさせるために n を使ってみる

$
0
0

logo-light.svg

この記事について

本記事は、Node.js のバージョン管理が可能になる npm パッケージ nのインストールおよび n を利用した Node.js のバージョン変更方法について記載しています。
なお、npm パッケージ n は Windows ネイティブでは未サポートのため、対象となるのは Linux や macOS など、非 Windows 環境となります。

対象読者

  • Node.js を既にインストールしている方
  • Node.js を過去のバージョンにダウングレードしたい方
  • OS が Linux や macOS の方

環境

  • OS: macOS Catalina Version 10.15.2
  • Node.js: v12.16.1

はじめに

普段、自分の開発 PC の Node.js は常に最新の LTS バージョンがインストールされているようにしています。(2020/03/06 時点では v12.16.1 でした)
ただ最近、そのことが原因で困った事が起こりました。

image.png

SharePoint Framework 用の NodeJS の現在サポートされている LTS バージョンは、Node.js v8.x および Node.js v10.x の両方です。
- Microsoft Docs

なんだと・・・という気がした (Microsoft さん早めのアップデートをお願いします...) のですが、ふと思い返すと、pyenv や goenv、exenv など他の言語だとバージョン管理パッケージを入れているのに、Node.js には入れていなかったということに気がつきました。

そこで、npm trendsを参照し、一番利用されている Node.js のバージョン管理パッケージ nを使って Node.jsのバージョンをダウングレードしてみることにしました。
※「Docker 使えばいいじゃん」という意見もあると思います。率直に言うと私も同意見なのですが、今回は諸事情により無視でお願いします。

インストール

npm リポジトリより、npm パッケージ n をグローバルインストールします。

sudo npm install-g n

Node.js バージョン変更

インストールが完了したら、実際に Node.js のバージョンを変更していきます。

グローバルインストール済みのパッケージ確認

npm パッケージ n の注意点として、以下の記載があります。

The global packages are not changed by the install, with the exception of itself which is part of the install.
(グローバル・パッケージは、インストールの一部であるそれ自体を除いて、インストールによって変更されません。)

つまり、n によってバージョン変更を行うと、グローバルインストールされた npm パッケージに対して、以下の変更がかかるということになります。

  • Node.js のバージョンは変更される
  • npm のバージョンも変更した Node.js のバージョンによっては変更される
  • Node.js および npm 以外については、一切バージョンは変わらない(影響を受けない)

そのため、実際に Node.js のバージョンを変更する前には、自分の環境に何がグローバルインストールされているのか確認した上で、変更による影響について考慮しておくことをお勧めします。

グローバルインストール済みパッケージの確認
npm list -g
実行結果
/usr/local/lib
├── n@6.3.1
├─┬ npm@6.13.4
...
└── yarn@1.22.0

私の場合だと、n と npm の他に、yarn がグローバルインストールされています。
※ご存知の方も多いと思いますが、yarn は npm と同じパッケージマネージャーなので今回は無視します。

ダウングレード

ダウングレード可能なバージョン情報を確認します。

切り替え可能なバージョン一覧の確認
n ls-remote -all
実行結果
13.10.1
13.10.0
13.9.0
...
11.0.0
10.19.0
10.18.
...
0.8.6

今回は、v10.x の最終版である v10.19.0 にダウングレードします。

ダウングレード
sudo n 10.19.0
実行結果
  installing : node-v10.19.0
       mkdir : /usr/local/n/versions/node/10.19.0
       fetch : https://nodejs.org/dist/v10.19.0/node-v10.19.0-darwin-x64.tar.xz
   installed : v10.19.0 (with npm 6.13.4)

実行後、Node.js のバージョンを確認すると、v10.19.0 に切り替わっているはずです。

バージョン確認
node -v
実行結果
v10.19.0

バージョン最新化

最新の LTS バージョンに Node.js のバージョンを戻す場合は、以下のコマンドを実行します。

sudo n lts

関連リンク

参考情報

公式情報

Node.js

n

Qiita

その他

LamdbaからS3内のファイルを操作したい

$
0
0

LamdbaからS3内を操作するメソッドで、よく使うものをメモ書き。

まずaws-sdkをrequireし、リージョンを指定します。
AWS SDKとは、AWSサービスをプログラムから操作できる開発キットです。

constAWS=require('aws-sdk');consts3=newAWS.S3({'region':'リージョン'});

ファイル作成・更新

ファイルを作成するには、putObjectメソッドを使用します。
S3に'test'というバケットがあり、その配下のsampleフォルダに'sample.json'というJSONファイルを作成します。

exports.handler=async(event)=>{constputData=awaits3.putObject({Bucket:'test',Key:'sample/sample1.json',Body:JSON.stringify({})}).promise()}

Bucket...バケット名
Kye...バッケト以下/ファイル名
Body...JSONファイル内に書きたい内容

実行すると、空のJSONファイルがS3に作成されます。

内容を記載したい場合はBodyのところに適当に入れます。

Body:JSON.stringify({name:'ai',age:20})

実行すると、sample.jsonの中に{"name":"ai","age":20}が追加されているのが確認できます。

ファイル取得

S3のファイルを取得するには、getObjectメソッドを使います。

exports.handler=async(event)=>{constdata=awaits3.getObject({Bucket:'test',Key:'test/sample1.json'}).promise()constobj=JSON.parse(data.Body)console.log(obj.name,obj.age)//ai 20}

先ほどと同様に、取得したいファイルの情報をBucket,Keyに指定します。
JSONをparseしてオブジェクトに変換することで、中身にアクセスすることができます。

ファイル削除

exports.handler=async(event)=>{constdeletes=awaits3.getObject({Bucket:'test',Key:'test/sample1.json'}).promise()}

こちらも同様に削除したいファイルの情報をBucket、Keyに指定します。

実行すると、削除できたことが確認できます。
ちなみに、存在しないファイル名を指定した場合でもエラーにはなりません。
なのでtry~catchで囲み、エラーを出してくれるようにしてあげる必要があります。

exports.handler=async(event)=>{try{constdeletes=awaits3.getObject({Bucket:'test',Key:'sample/sample1XXX.json'}).promise().catch(err=>{thrownewError(err)})}catch(err){console.log(err)}}//実行結果 Error: NoSuchKey: The specified key does not exist.

存在しないファイル名を指定して実行すると、エラーになることが確認できました。

データの一覧を取得する

S3にどんなファイルがあるか分からない時、listObjectsV2メソッドで確認することがきます。

exports.handler=async(event)=>{constparams={'Bucket':'test','Prefix':'sample',}constlists=awaits3.listObjectsV2(params).promise()lists.Contents.forEach((a)=>console.log(a.Key))}

実行すると、sampleフォルダ配下に何があるか確認できました。

実行結果
sample/
sample/sample1.json
sample/sample2.json
sample/sample3.json

Socket.ioの双方向通信について

$
0
0

はじめに

たくさんの方が書いてると思いますが,自分の知識をまとめたいので,つらつらと書いていきます...

双方向通信とは

双方向通信とは,単方向通信の対義語で,送信&受信が可能な通信の事です
全二重通信半二重通信の2種類があります

  • 全二重通信:送信と受信を同時に行える通信
  • 半二重通信:送信と受信を同時に行えない通信

全二重通信は半二重通信の上位互換になるのかなというイメージです

Socket.ioとは

まず,WebSocketについて

リアルタイムWeb技術の一種であり,双方向通信を可能とするプロトコルになります

Socket.ioとは,WebSocketを使用可能なNode.jsのライブラリの一種になります
実際に双方向通信を実装して,体験してみようと思います

ソースコードはここのサイトをみて作成しました.

今回,Node,Raspberrypi 3 Model B(サーバの代わり)を使用しました
RaspberrypiにNodeはOSインストール時にインストールされていると思います

各バージョンを以下にまとめます.
* OS:Raspbian GNU/Linux 9.11 (stretch)
* Node:v13.9.0
* npm:6.13.7
* Socket.io:2.3.0

サーバにするディレクトリに移動して,ディレクトリを作成し,その中に移動します

mkdir Socket_io_sample(ディレクトリ名)
cd Socket_io_sample(ディレクトリ名)

Nodeのライブラリをnpmコマンドでインストールします

node install socket.io

今回は以下の2つのファイルを作成します

  • socket.js
  • index.html

ファイルの位置は以下のようになります

Socket_io_sample(ディレクトリ名)
|ー package-lock.json
|ー node_modules
|ー index.html
┗ー socket.js

各ファイルに以下のソースコードを記述します

index.html
<!DOCTYPE html><!-- 言語は日本語を指定--><htmllang="ja"><head><!-- 文字コードはUTF-8を使用--><metacharset="utf-8"><!--タイトルを入力--><title>Websoketを使用したチャット</title><!--おまじないの一種です----------------------------------------------------------------><script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script><script type="text/javascript"src="/socket.io/socket.io.js"></script><!-------------------------------------------------------------------------------------></head><body><divclass="container"><!--見出しを入力する--><h1>Websocketを使用したチャット</h1><formclass="form-message"><divclass="form-group"><labelfor="message-form">メッセージ:</label><!--テキスト入力ボックスを設置する--><inputtype="text"class="form-control"id="message-form"></div><!--送信ボタンを設置--><buttontype="submit">メッセージを送信</button></form><!--入力ログが以下にに出力される--><divid="chatlogs"></div></div><script type="text/javascript">//サーバと接続(connect)するvarsocket=io.connect();//サーバからクライアントへのアクセスがあった際,dataを表示するsocket.on("server_to_client",function(data){appendMsg(data.value)});//textを表示する関数(function)を作成functionappendMsg(text){//idがチャットログの箇所に"<div>+text+</div>"を追加す$("#chatlogs").append("<div>"+text+"</div>");}$("form").submit(function(err){//messageにmessage-formの値を保存するvarmessage=$("#message-form").val();//message-formの値を空にする$("#message-form").val('');//全てのブラウザにmessageを表示する?socket.emit("client_to_server",{value:message});//イベントが終わらない場合,強制終了させる?err.preventDefault();});</script></body></html>
socket.js
varhttp=require('http');varsocketio=require('socket.io');varfs=require('fs');//サーバを建てる(ポートは3000番)varserver=http.createServer(function(req,res){//ヘッダー,コンテンツのタイプを入力res.writeHead(200,{'Content-Type':'text/html'});//index.htmlを読み込み表示し,レスポンス(res)を終了するres.end(fs.readFileSync(__dirname+'/index.html','utf-8'));}).listen(3000);//サーバでsocket.io(双方向通信)を可能にする?vario=socketio.listen(server);//サーバとクライアントの接続が起きた際の処理を記述io.sockets.on('connection',function(socket){//クライアント(ブラウザ)からサーバへの通信が行われた際の処理を記述socket.on('client_to_server',function(data){//サーバからクライアントへの通信が行われた際の処理を記述//data.value:入力した文字列?を各クライアントに分け与える(emitする)io.sockets.emit('server_to_client',{value:data.value});});});

これにて双方向通信を体験できるのかなと思います.

参考にしたサイト

双方向通信とは
いまさら聞けないWebSocketとSocket.IOの基礎知識&インストール

Viewing all 8812 articles
Browse latest View live