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

Node.js で Redis を使う場合、コネクションプールは必要ない

$
0
0

TL;DR

Node.js も Redis もシングルスレッドなので、 Node 1プロセスにつき接続ひとつでよい。
トップレベルで createClientをして、その先で使えばよい。

本文

Redisはよく使われるインメモリデータストアです。

ふつうの DB (Postgres とか)だとコネクションプールを普通に使うので、 Redis にもあるのかなーと思って node-redis のドキュメントを見に行くとコネクションプールに関する記載がない。
どういうこっちゃと思って GitHub の issue をあさりに行くと、その理由がありました。

connecting pool · Issue #1354 · NodeRedis/node-redis

Since Node and Redis are both (practically) single-threaded a single connection per Node process is the general pattern. Just use createClient at the top level of your script and move on.

(筆者訳)
Node も Redis も両方(実質)シングルスレッドだから、 Node のプロセスひとつにつき1コネクションとするのが一般的なパターン。たんにトップレベルで createClientを使って、その先に進めばよい。

なるほどなぁ。
例外もあるらしいので、気をつけて実装しましょう。

Exceptions would be blocking commands (BLPOP, XREAD, etc.) and SUBSCRIBE - those need their own connection.

(筆者訳)
ブロッキングコマンド (BLPOP とか XREAD とか……)と SUBSCRIBE は例外で、これらには独自のコネクションが必要。


node-fetchでeuc-jpのページを取得する場合

$
0
0

2020年もなると文字コード関連のノウハウなんてものはすっかりプログラミングサイト上からも消えています。世間は当たり前のようにutf-8化しています。いまだにutf-8化していないのはwindowsの一部くらいなものでしょう。

で、webの世界は当然utf-8で全部済むと思っていたのですが、最近、node-fetchでeuc-jpのページを拾ってくる作業にぶつかりました。node-fetchとはnode.jsでjavascriptのfetch関数を使うライブラリです。fetchとはXMLHttprequestのモダンなやつです。

こういう場合はfetchにURL渡して引っ張ってきてその文字列を何かのライブラリーで変換というのがセオリー中のセオリーですが、そんなのは20世紀のプログラミングらしいです。perlのJcodeとかの時代のセオリーのようです。

モダンなプログラミングというのは「誰かがどこかで勝手にutf-8に変換してくれてる」というもので、特にウェブの場合はhtmlに文字コードがcharsetとして記載されており、それを読んで変換、あるいはソースの文字コード自動判別して現代的標準であるutf-8に変換してくる、というのが当たり前のようです。ですが、そうはうまくは行きません。世界のプログラマはasciiからutf-8になったのであり、eucやらShiftJISやらCN、KRなんてものは頭の片隅にもありません。

というわけで、結局は生の文字列を現代のJcode、nkfで変換してやるという泥臭いが慣れている作業をやればいいのですが、これがうまく行かない。「誰かがどこかで中途半端にutf-8に変換してる」というトラブルが待っているのです。

詳しくはわかりませんが、「8bitだからLatin-1だろうからそれを強引にutf-8にしておいてあげましたよ」的なことをやられているのでしょう、生の文字列だと受け取った文字列は化けており、それをeuc-jpに変換してやっても化けているという、ありがちなにっちもさっちも行かない事態になります。

さてここからソースを交えた説明になります。
使用しているiconv-liteというライブラリは文字コード変換です。iconvでピンとくるかもしませんがlinuxやらglibc周りで聞いた名前ですね。

いまだにeuc-jpを使ってるページとして
https://www.rakuten.co.jp/
を参考例にしています。

まずはiconvを使わずにnode-fetchが自動変換してくれるという期待を込めたパターン。

constnode_fetch=require('node-fetch');consturl="https://www.rakuten.co.jp/"node_fetch(url).then(res=>res.body.text()).then(body=>console.log(body));

化けているのか、もとのeuc-jpのページがそのまま表示されているのこの段階では追いませんが、とにかくうまく行かなかったので次へ

次はtext()で生の文字列が得られると仮定してiconvで変換

constnode_fetch=require('node-fetch');consticonv=require("iconv-lite")consturl="https://www.rakuten.co.jp/"node_fetch(url).then(res=>res.text()).then(body=>console.log(iconv.decode(body)));

これもうまく行きません。おそらく生の文字列は取得できてないようです。つまりnode-fetch(かjavascriptのバッファー周り)が変換に失敗しているかと

途方に暮れているとエラーメッセージの中に公式ヘルプへのリンクが表示され
https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding
このページのサンプル通りにやったのがこれ

constnode_fetch=require('node-fetch');consticonv=require("iconv-lite")consturl="https://www.rakuten.co.jp/"node_fetch(url).then(res=>res.body).then(body=>body.pipe(iconv.decodeStream('euc-jp').collect((err,res)=>console.log(res))));

これでやっとうまく行きました。
node-fetchのソースを軽く見てみると、bufferからの型変換がいろいろ複雑なようです。bufferといってもいろいろあるようで、深く追うのは面倒なので諦めました。
javascriptは型が無い(弱い)ようでガッチガチに面倒なのでそのへんは名前の通りjavaだなーと。

というわけで2020年のバッドノウハウ(?)でした

JIRA REST APIで任意の課題(JQLで指定)にグループメンバーをウォッチャーとして割り当てる。

$
0
0

はじめに

  • JIRAの一括変更でグループメンバーへのウォッチャー割り当てができなかったためREST APIで実装してみました。

実施環境

作業の流れ

  1. getUsersInGroup関数を使ってメンバーのaccount_idの抽出
  2. searchJira関数を使って未ウォッチタスクの抽出
  3. addWatcher関数を使ってウォッチャーの割り当て
  4. mainでそれぞれの関数をasync/awaitで同期処理。

getUsersInGroup

  • グループ内のaccount_idの抽出
  • 同期処理をするためにPromiseを用意しておく。
index.js
// グループメンバーの抽出functiongetUsersInGroup(groupName){returnnewPromise((resolve,reject)=>{varwatcher=[];jira.getUsersInGroup(groupName)//getUsersInGroup(groupname: string, startAt: integer, maxResults: integer).then(function(issue){console.log("getUsersInGroup:")for(vari=0;i<issue.users.items.length;i++){if(issue.users.items[i].accountType=="atlassian"){watcher.push({id:issue.users.items[i].accountId,name:issue.users.items[i].displayName});}}console.log(watcher)returnresolve(watcher);}).catch(function(err){console.error(err);});});}

searchJira

  • 未ウォッチタスクの抽出
  • wwatcher not in (" + user_id + ")だけではウォッチャーなしのタスクを抽出できなかったためorで watcher is EMPTYも接続しました。
index.js
// 未ウォッチタスクの検索functionsearchNotWatch(issueKey,user_id){returnnewPromise((resolve,reject)=>{varjql_notWatch=jql+" and (watcher is EMPTY OR watcher not in ("+user_id+")) "jira.searchJira(jql_notWatch,{maxResults:1000})//searchJira(searchString: string, optional: object): .then(function(issue){for(vari=0;i<issue.issues.length;i++){issueKey[i]=issue.issues[i].keyconsole.dir(issueKey[i])}returnresolve(issueKey);})});}

addWatcher

  • ウォッチャーの割り当て
index.js
// ウォッチャー割り当てfunctionaddWatcher(issueKey,user_id){returnnewPromise((resolve,reject)=>{jira.addWatcher(issueKey,user_id).then(function(issue){console.dir(issueKey+"にウォッチャー割り当て")returnresolve();})});}

main

  • 各関数を同期処理
index.js
// mainasyncfunctionmain(){varwatcher=[];console.log('start');watcher=awaitgetUsersInGroup(group_name)console.log(watcher.length);for(vari=0;i<watcher.length;i++){varissue_key=[];console.log(watcher[i].name+"さんの未ウォッチタスクは")issue_key=awaitsearchNotWatch(issue_key,watcher[i].id);console.log(watcher[i].name+"さんにウォッチャー割り当て実行")for(varj=0;j<issue_key.length;j++){awaitaddWatcher(issue_key[j],watcher[i].id)}}console.log('done');}

つなげると

  • 一連の流れをつなげるとこんな感じになります。
index.js
//任意の課題(JQLで指定)にグループメンバーをウォッチャーとして割り当てる。constJiraApi=require('jira-client');constconfig=require('config');constjql="project = TEST_project";//任意のフィルターをJQLで指定。constgroup_name="TEST_group";//割り当てたいグループを指定// Settingconstjira=newJiraApi({protocol:config.protocol,host:config.host,username:config.username,password:config.password,apiVersion:config.apiVersion,strictSSL:config.strictSSL})// グループメンバーの抽出functiongetUsersInGroup(groupName){returnnewPromise((resolve,reject)=>{varwatcher=[];jira.getUsersInGroup(groupName)//getUsersInGroup(groupname: string, startAt: integer, maxResults: integer).then(function(issue){console.log("getUsersInGroup:")for(vari=0;i<issue.users.items.length;i++){if(issue.users.items[i].accountType=="atlassian"){watcher.push({id:issue.users.items[i].accountId,name:issue.users.items[i].displayName});}}console.log(watcher)returnresolve(watcher);}).catch(function(err){console.error(err);});});}// 未ウォッチタスクの検索functionsearchNotWatch(issueKey,user_id){returnnewPromise((resolve,reject)=>{varjql_notWatch=jql+" and (watcher is EMPTY OR watcher not in ("+user_id+")) "jira.searchJira(jql_notWatch,{maxResults:1000})//searchJira(searchString: string, optional: object): .then(function(issue){for(vari=0;i<issue.issues.length;i++){issueKey[i]=issue.issues[i].keyconsole.dir(issueKey[i])}returnresolve(issueKey);})});}// ウォッチャー割り当てfunctionaddWatcher(issueKey,user_id){returnnewPromise((resolve,reject)=>{jira.addWatcher(issueKey,user_id).then(function(issue){console.dir(issueKey+"にウォッチャー割り当て")returnresolve();})});}// mainasyncfunctionmain(){varwatcher=[];console.log('start');watcher=awaitgetUsersInGroup(group_name)console.log(watcher.length);for(vari=0;i<watcher.length;i++){varissue_key=[];console.log(watcher[i].name+"さんの未ウォッチタスクは")issue_key=awaitsearchNotWatch(issue_key,watcher[i].id);console.log(watcher[i].name+"さんにウォッチャー割り当て実行")for(varj=0;j<issue_key.length;j++){awaitaddWatcher(issue_key[j],watcher[i].id)}}console.log('done');}main();

終わりに

  • これで、タスクの検索 → 内容の変更 を一連の作業として行えるので、応用すればその他の修正作業も行えるはず。

TensorFlow.jsチュートリアルのnpm install @tensorflow/tfjs-nodeでつまづいた話 on Mac

$
0
0

背景

この記事みて、TensorFlow.jsめっちゃいいじゃんと思ってNode.jsも最近触ってるしチュートリアルやってみようとしたら初手でつまづいてキレそうになったので、メモ。
ちなみにWindows10とMacでやってWindowsはいまだに解決できないので、誰か教えてくらはい。

環境

PC: MacBook Air (Retina, 13-inch, 2018)
OS: macOS Catalina (ver 10.15.4)
プロセッサ: 1.6 GHz Intel Core i5
メモリ: 16 GB 2133 MHz LPDDR3

$ node -v

v10.16.0

$ nmp -v

6.9.0

つまづいたこと

Node.jsを使っていてnpmの人は下記をすればいいと書いてあったので、実行したらERR!がクソほどでた。
具体的にはこんなの↓

$ npm install @tensorflow/tfjs-node

de-pre-gyp install failed with error: Error: Command failed: node-pre-gyp install --fallback-to-build
node-pre-gyp WARN Using needle for node-pre-gyp https download
node-pre-gyp WARN Tried to download(404): https://storage.googleapis.com/tf-builds/pre-built-binary/napi-v5/1.7.3/
...
...
gyp: No Xcode or CLT version detected!
gyp ERR! configure error
gyp ERR! stack Error: gyp failed with exit code: 1
...
...
node-pre-gyp ERR! build error
node-pre-gyp ERR! stack Error: Failed to execute
...
...

解決した方法

ぐぐるとnode-gypのインストールでエラーが起きているらしく、同じく困っている人がたくさんいた。TensorFlow.js以外にも。

インストールは公式(手順はここを参照)に載っている方法でする。

$ npm install -g node-gyp

ただし、これでもうまくいかず。
よくみるとCatalinaの人は

If your Mac has been upgraded to macOS Catalina (10.15), please read macOS_Catalina.md

と書かれていたので、公式(手順はここを参照)に載っている対処法を実施してXcode Command Line Toolsというのをインストールする。

Acidテストとかいうのをしてテストに合格したら無事に先ほどのnpm install -g node-gypができるということらしい。

僕の場合は一度削除するコマンドを実行して再度Xcode Command Line Toolsをインストール(30分ほどでインストールが終わる)

やっとターミナルに戻って再度

$ npm install @tensorflow/tfjs-node

を実行すればエラー無くインストールできた。

チュートリアルにある下記コードをコピペして

sample.js
consttf=require('@tensorflow/tfjs');// オプションとしてバインディングを読み込み// GPU上で動作する場合は'@tensorflow/tfjs-node-gpu'を使用してくださいrequire('@tensorflow/tfjs-node');// 単純なモデルを訓練constmodel=tf.sequential();model.add(tf.layers.dense({units:100,activation:'relu',inputShape:[10]}));model.add(tf.layers.dense({units:1,activation:'linear'}));model.compile({optimizer:'sgd',loss:'meanSquaredError'});constxs=tf.randomNormal([100,10]);constys=tf.randomNormal([100,1]);model.fit(xs,ys,{epochs:100,callbacks:{onEpochEnd:(epoch,log)=>console.log(`Epoch ${epoch}: loss = ${log.loss}`)}});

実行する

$ node sample.js

Epoch 1 / 100
eta=0.0 ========================================================>
76ms 764us/step - loss=1.33
Epoch 0: loss = 1.332172155380249
...
...
Epoch 99 / 100
eta=0.0 ========================================================>
40ms 404us/step - loss=0.632
Epoch 98: loss = 0.6319155097007751
Epoch 100 / 100
eta=0.0 ========================================================>
26ms 255us/step - loss=0.628
Epoch 99: loss = 0.6275108456611633

なんか機械学習ぽい単語が出てるので多分ヨシッ!

その他

どうやら、nodejsのバージョンとかでも影響するぽいので、nodebrewでバージョンを変えてみるのもあり

今入っているバージョン羅列

$ nodebrew list

v10.16.0
v11.1.0
v12.16.1

current: v12.16.1

バージョンを変える

$ nodebrew use v10.16.0

use v10.16.0

確認

$ node -v

v10.16.0

Windows

ちなみにWindowsでもいろいろ探したがうまく実行できていない。
とりあえず、よくあるのはPowersherllを管理者権限で起動して下記を実行するやつ。

$ npm install --global --production windows-build-tools

やったけど結局同じエラー。

あとは一応、ERRという表記がなくなったのはこれ。

$ npm install @tensorflow/tfjs-node@1.2.7

ただし、sample.js を実行させたところ下記のように出てしまったので、思考停止してMacに浮気。

internal/modules/cjs/loader.js:1206
  return process.dlopen(module, path.toNamespacedPath(filename));
                 ^

Error: The specified module could not be found.
....

nodejsやpythonのファイルの最初の行にシェルスクリプトで使われる「あれ」を付けて実行ファイル化してみよう

$
0
0

どうもこんばんは、cedです。
今回はlinux系で使われるシェルスクリプトの「あれ」をnodejsやpythonのファイルにつけて実行してみようっていう記事です。

※これはwindowsでする場合、WSLが必要になります。

実行環境

os: ubuntu 19.10 / arm64(Android)
python: 3.7.5
nodejs: 12.16.2

さっそくHello Worldを書いてみる

の前にファイルに権限与える

nodejs
touch index.js
chmod +x index.js
python
touch index.py
chmod +x index.py

nodejsの場合

index.js
#!/usr/local/bin/node
//nでインストールしていない場合はlocalは要らないconsole.log("Hello Woeld!");

pythonの場合

index.py
#!/usr/bin/python3
print("Hello Woeld!");

結果

Screenshot_20200424-194424_Termux.jpg

終わりに

個人的にこの方法出来るのが嬉しくて記事にしました。
noteでも同じようなやつを書きましたが、qiitaにも描きたくなったので書きました
見てくださりありがとうございます。

Node.js(Express)を使ってJEPG画像をアップロードして表示するというシンプルなWebアプリケーションを作ってHerokuにデプロイしたい話 on Windows

$
0
0

背景

Node.jsという最近キテそうな言語を使いこなして、高級寿司を食えるかっこいいエンジニアになりたいので勉強がてらシンプルなWebアプリケーションを作った。あと、WebアプリケーションらしくHerokuにもデプロイしたいと思った。

JEPG画像のアップロードなんで余裕でしょって思っていたらめちゃくちゃ躓いたので、すべての記憶を失っているであろう未来の僕に対して作り方を残す。ごちゃごちゃ書きます。

環境

OS:Windows 10 Pro (64bit)
プロセッサ: Core i5 3.20 GHz
メモリ: 24.0 GB

$ node -v
v12.16.2
$ npm -v
4.0.5
$ express --version
4.16.1

作り方

1. express-generatorを使う

「巨人の肩に乗っていけぇ~」ということで
こちらの通りにやると大体できました。
[Node.js] express + Multer を使用してファイルアップロード API を作成する

ほんと神、ありがとうございます....

はじめます。
まずは、express-generator をインストールします。

$ npm install express-generator -g

express-generatorというのは、Node.jsで開発するときのフォルダ構成とかjsファイルとかのテンプレート(スケルトンともいうらしい)を作ってくるようなもののようです。

任意のディレクトリで下記を実行することで実際のフォルダやファイルを作ってくれます。

$ express -e testproject

ここでは"testproject"という名前でフォルダを作成し、オプションの「-e」でEJSのテンプレートエンジンを指定します。
テンプレートエンジンにはEJS以外にもJadeとかいろいろあるみたいですが、それぞれHTML形式で記述できるそうです。HTMLぽくかけてJavascriptのコードを入れたり、変数の受け渡しとかも簡単に書けちゃうやつみたいな認識です。てか、ここではHTMLファイルの変わりとして使います。詳しくやるとGlupとかいうのも出てくるみたいです。

$cd testproject
$ls
> bin/ public/ routes/ views/ app.js package.json

上記のようなフォルダができているはずです。

また、npm install を実行し、アプリケーション実行に必要な各種パッケージをインストールします。

$npm install
$ls
> bin/ node_modules/ public/ routes/ views/ app.js package.json

おなじみのnode_modulesフォルダができたことを確認します。

この時点で

$ npm start

をしてブラウザからhttp://localhost:3000/
にアクセスすれば下記のような表示が出るはずです。

image.png

ちなみにここで表示されているのは
views/index.ejsのHTMLとroutes/index.jsで"title"という変数で渡した"Express"という文字列です。
なので、routes/index.jsで"Express"という文字列を"ぱんぱかぱーん"とかにすれば、ブラウザでも"ぱんぱかぱーん"と表示されるはずです。

routes/index.js
.../* GET home page. */router.get('/',function(req,res,next){res.render('index',{title:'Express'});//←ここに指定したindex.ejsと変数titleに指定した文字列がブラウザに返され表示});...
views/index.ejs
...
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
...

ちょっとしたWebサイトならこれでできるね。ぱんぱかぱーん。

2. Multerを使う

続いて、アップロードされた画像を受け取る際に使うMulterというモジュールをインストールします。
(正確に言うとmultipart/form-data というデータ形式を扱うためのようですが、詳しいことは知りません)

$ npm install --save multer

package.json の dependencies の中に multer が追加されていることを確認。

$cat package.json
>{
  "name": "testproject",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "ejs": "~2.6.1",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1",
    "multer": "^1.4.2"
  }
}

3. routes/index.js を修正

ここは参考記事から少し変更。

routes/index.js
xpress=require('express');varrouter=express.Router();//*** 追加1 ここから***//varmulter=require('multer');varstorage=multer.diskStorage({//ファイルの保存先を指定(ここでは保存先は./public/images) //Express4の仕様かなんかで画像staticなファイルを保存するときはpublic/以下のフォルダに置かないとダメらしい//詳しくは express.static public でググろう!destination:function(req,file,cb){cb(null,'./public/images/')},//ファイル名を指定//ここでは image.jpg という名前で保存filename:function(req,file,cb){cb(null,'image.jpg')}})varupload=multer({storage:storage})//*** 追加1 ここまで ***///* GET home page. */router.get('/',function(req,res,next){res.render('index',{title:'Express'});});//*** 追加2 ここから ***////ルート (/) に対する POST リクエスト//name タグにfileを指定したもののみ受け付けるrouter.post('/',upload.single('file'),function(req,res){res.json({'result':'success!'});});//*** 追加2 ここまで ***//module.exports=router;

参考記事からの変更点
- 保存先を'/'(ルート)から'./public/images/'に変更。
- 保存名をfile.originalnameからimage.jpgに変更。

一応、ブラウザからもう一度確認しましょう。

$ npm start

ちょろめからhttp://localhost:3000/にアクセス!

3. 画像をPOSTする(アップロードする)

POSTするのはmultipart/form-data というデータ形式なら何でもヨシッ!

「POST?multipart/form-data?何それおいしいの?」状態で、僕にはよく分からなかったので、そういう人向けにツールを使う方法とindex.ejsを書き換えて送信する方法を書きます。

3.1, Fiddlerを使う(とりあえずテストするには、簡単なほう)

FiddlerというツールでPOSTだったりGETを送ることができます。
(本来はパケットキャプチャとかするのかな?)

とりあえず、インストールして起動したら、次の順で画像の赤枠部分を変更してきます。

0.npm startでサーバーを立ち上げる
1.Composerタブを開く
2.リクエストを"POST"に変更
3.送信先を"localhost:3000"に書き換え
4.RequestBody枠の右上にある青字"Upload file..."から送信する画像を選択する
5.RequestBodyのnameを"file"に変更する
 (routes/index.jsでname="file"を受け付けるように指定しているため)
6.ExecuteでPOSTリクエストをサーバーに送る。
7.localhost:3000 にアクセスして{ 'result': 'success!' }が表示されているのを確認する。
8.public\images 配下にimage.jpg が保存されていることを確認する。
9.image.jpgを開いて無事に送った画像が表示されていればOK!

無題.png

3.2. 送信用にindex.ejsを書き換える(実際にWebアプリにするなら必要なほう)

views/index.ejsを下記のように書き換えます。
特に注意なのはname=fileになっていることだけ注意してください。
fileになっていないとサーバー側で受け取れません。
(route/index.jsでname=fileを受け付けるように記述しているため)

views/index.ejs
<!DOCTYPE html>
<html>

<head>
  <title><%= title %></title>
  <link rel='stylesheet' href='/stylesheets/style.css' />
</head>

<body>
  <h1><%= title %></h1>
  <p>Welcome to <%= title %></p>
  <form action="/" method="post" enctype="multipart/form-data">
    画像ファイル選択<br>
    <input type="file" name="file">
    <br>
    <input type="submit" name="botan" value="送信">
  </form>
</body>

</html>

$ npm start

1.ちょろめからhttp://localhost:3000/にアクセス!
2.画像ファイルを選択して、「送信」ボタンを押す。
3.public\images 配下にimage.jpg が保存されていることを確認する。
4.image.jpgを開いて無事に送った画像が表示されていればOK!

4. アップロード後に表示させる

アップロードした画像をその場で表示するようにします。
具体的にはviewsの配下にimage.ejsという表示用のejsファイルを新規で作成し、それをレスポンスするようにします。

といってもこれだけです。
参照する画像名は固定でimage.jpgとしているのでそれを参照して表示するだけです。

views/image.ejs
<!DOCTYPE html>
<html>

<body>
    <h1>画像アップロードしました</h1>
    <img src="/images/image.jpg" />
</body>

</html>

また、routes/index.jsを少し修正します。

routes/index.js
xpress=require('express');varrouter=express.Router();//*** 追加1 ここから***//varmulter=require('multer');varstorage=multer.diskStorage({//ファイルの保存先を指定(ここでは保存先は./public/images) //Express4の仕様かなんかで画像staticなファイルを保存するときはpublic/以下のフォルダに置かないとダメらしい//詳しくは express.static public でググろう!destination:function(req,file,cb){cb(null,'./public/images/')},//ファイル名を指定//ここでは image.jpg という名前で保存filename:function(req,file,cb){cb(null,'image.jpg')}})varupload=multer({storage:storage})//*** 追加1 ここまで ***///* GET home page. */router.get('/',function(req,res,next){res.render('index',{title:'Express'});});//*** 追加2 ここから ***////ルート (/) に対する POST リクエスト//name タグにfileを指定したもののみ受け付けるrouter.post('/',upload.single('file'),function(req,res){res.render('image'); //*** 修正 ***//});//*** 追加2 ここまで ***//module.exports=router;

res.json({ 'result': 'success!' });

res.render('image');
にして、image.ejsを返すだけです。

5. Herokuにアップロードする

Herokuにアップロードするときはルートディレクトリ(testproject配下)にProcfileというファイルを作って中に

web: node ./bin/www

を記述して、git push heroku masterすればいいはず。
具体的なHerokuの登録とかアップロード方法はぐぐってくだされば・・・
僕はこの記事を参考にしました

以上、間違っていることとか定石から外れていることも多いかもしれないですが、動いたのでいいという気持ちです。なにかあれば気軽にコメントください。

疲れたので寝ます。。。

参考記事

Vue MEVN Stack Tutorial – Build Full Stack Vue.js CRUD App

暇を持て余した高校生による給料計算npmモジュール

$
0
0

はじめに

ざっと自己紹介をすると

きっかけ

現在僕は給料計算とカレンダーのwebアプリを開発中で、カレンダーについてはFullCalendarで行けたんですが給料計算に関しては国ごとに法律が違うこともあってライブラリやモジュールがありませんでした。モジュールが無いなら自分で作るしかなくて、どうせならnpmパッケージにして公開しようと思い立ったので開発しました。

沼ったところ

そもそも僕はnpmパッケージとかモジュールなんて一度も作ったことがなく、どうやってパッケージにするかも全くわからなかったので、ローカルでinitするところからGoogle先生の力を借りなければならなかった訳です。参考にしたサイトは以下のような感じです。

これからモジュール作ろうかなぁと思っている方は是非参考にしてください。

労働基準法を読む

モジュールを作るには僕自身が日本の労働システムについて理解しなくてはいけないんでですね、とにかく1時間くらいかけて労働基準法を読破しました。実際役に立ったのは三十六,三十七条くらいなんですが、そもそもどこを読めば良いかもわからなかったのでとりあえず全部読みました。アホですね。労働基準法以外に役に立ったサイトはこんな感じ

割増賃金を理解する

上のような記事を読んで、3種類ある割増賃金の該当ケースと重複した場合の割増%を理解するのもかなりキツかったです。

  • 時間外労働による割増賃金

    • 1.25倍
      • 実働時間が1日8時間を超えた場合
      • 実働時間が週40時間を超えた場合
      • 上記の時間外労働が月45時間or年360時間を超えた場合
    • 1.5倍
      • 上記の時間外労働が月60時間を超えた場合(2023年に廃止)
  • 法定休日出勤による割増賃金

    • 1.35倍
      • 週1日or月4日に定められた法定休日に出勤した場合
  • 深夜勤務による割増賃金

    • 1.25倍
      • 22:00~05:00の間
  • 時間外労働と深夜勤務の重複...可能。1.5倍or1.75倍

  • 法定休日出勤と深夜勤務の重複...可能。1.6倍

  • 時間外労働と法定休日出勤の重複...不可。時間外労働による割増が1.5倍の時を除いて法定休日出勤割増が優先される。1.35倍or1.5倍

  • 時間外労働、法定休日出勤、深夜勤務全てが重複した場合...法定休日出勤と深夜勤務の割増が加算され、時間外労働は考慮されない。1.6倍

これを理解すればあとはコードを書くだけ...

スパゲッティコードになる

僕のスキルの問題なのかJSの限界なのか(多分前者)このシステムをコードに変えるとめっちゃ理想的なスパゲッティコードになります。

index.js
"use strict";//overwork...年360時間、月45時間、週40時間を超えている場合は1と記入、月60時間を超えている場合は2と記入//overworktime...overworkで超過している時間を記入//holiday...決められている法定休日(通常は日曜日)や月ごとに決められている法定休日に出勤する場合はtrueを記入//midnight...実働時間のうち22~5時に働いている時間を記入//overworkとmidnight重複は1.5倍、holidayとmidnightの重複は1.6倍でoverworkとholidayはholiday優先。3つ重複した場合はholidayとmidnightfunctionpayrox(wage,workinghours,overwork,overworktime,holiday,midnight){letresult=0;//深夜勤務している上に休日出勤の場合if(midnight!==0&&holiday==true){//実働時間から深夜勤務時間を引いた値に時給*1.35result+=(workinghours-midnight)*wage*1.35;//深夜勤務時間に時給*1.6result+=midnight*wage*1.6;//深夜勤務しているが休日出勤はしていない場合}elseif(midnight!==0&&holiday==false){//時間外労働1で時間外労働が深夜勤務時間より長い場合if(overwork==1&&overworktime>=midnight){//実働時間から時間外労働を引いた値に時給をかけるresult+=(workinghours-overworktime)*wage;//時間外労働から深夜勤務時間を引いた値に時給*1.25result+=(overworktime-midnight)*wage*1.25;//深夜勤務時間に時給*1.5result+=midnight*wage*1.5;//時間外労働1で時間外労働が深夜勤務時間より短い場合}elseif(overwork==1&&overworktime<midnight){//実働時間から深夜勤務時間を引いた値に時給をかけるresult+=(workinghours-midnight)*wage;//深夜勤務時間から時間外労働を引いた値に時給*1.25result+=(midnight-overworktime)*wage*1.25;//時間外労働に時給*1.5result+=overworktime*wage*1.5;//時間外労働2で時間外労働が深夜勤務時間より長い場合}elseif(overwork==2&&overworktime>=midnight){//実働時間から時間外労働を引いた値に時給をかけるresult+=(workinghours-overworktime)*wage;//時間外労働から深夜勤務時間を引いた値に時給*1.5result+=(overworktime-midnight)*wage*1.5;//深夜勤務時間に時給*1.75result+=midnight*wage*1.75;//時間外労働2で時間外労働が深夜勤務時間より短い場合}elseif(overwork==2&&overworktime<midnight){//実働時間から深夜勤務時間を引いた値に時給をかけるresult+=(workinghours-midnight)*wage;//深夜勤務時間から時間外労働を引いた値に時給*1.25result+=(midnight-overworktime)*wage*1.25;//時間外労働に時給*1.75result+=overworktime*wage*1.75;//深夜勤務のみの場合}elseif(overwork===0){//実働時間から深夜勤務時間を引いた値に時給をかけるresult+=(workinghours-midnight)*wage;//深夜勤務時間に時給*1.25result+=midnight*wage*1.25;}//深夜勤務はしていないが休日出勤をしている場合}elseif(midnight===0&&holiday==true){//休日出勤で時間外労働2の場合if(overwork==2){//実働時間から時間外労働を引いた値に時給*1.35result+=(workinghours-overworktime)*wage*1.35;//時間外労働に時給*1.5result+=overworktime*wage*1.5;//休日出勤で時間外労働1または時間外労働していない場合}else{//実働時間に時給*1.35result+=workinghours*wage*1.35}//深夜勤務も休日出勤もしていない上で時間外労働1の場合}elseif(holiday==false&&overwork==1){result+=(workinghours-overworktime)*wage;result+=overworktime*wage*1.25;//深夜勤務も休日出勤もしていない上で時間外労働2の場合}elseif(holiday==false&&overwork==2){result+=(workinghours-overworktime)*wage;result+=overworktime*wage*1.5;}else{result+=workinghours*wage;}letanswer=Math.round(result);returnanswer;}module.exports={add:payrox}

コメントアウトを除けば大した量ではありませんがとにかくif文の主張が凄いので、もっと良いコードが書けるという方はプルリク待ってます。プルリクカモン

テスト童貞

僕は今までWebサイトだったりWebアプリの開発しかしてこなかったのでnode.jsのテストに触れたことがありませんでした。今回テストするにあたってmochaとかchaiとかmocha-sinonとか初耳のパッケージばかり使ったので色々勉強になりました。参考にしたサイトはこんな感じ

この二つを見ながらちょいちょいアレンジすれば初心者でも簡単なテストはできます。

npmアカウント作成

gitでpushして

$npm publish ./

でパッケージ公開完了!
と思いきや、npmのアカウントを作っておらず、しかもそれに気付かず30分くらい沼るという初心者プレイをかましました。僕のnpmアカウント

README.mdのUsageがめんどい

僕が作ったモジュールは

constpayrox=require("payrox-cal");//payrox-calをインポートpayrox.add(1000,9,1,4,false,0);//payrox.add(時給(整数),実働時間(数値),時間外労働(0~2),時間外労働の時間(数値),法定休日出勤(真偽値),深夜勤務時間(数値))//数値が小数点以下になる場合は小数第一位で四捨五入(4.2343=>4.2)

こんな感じで6個も引数を指定しなければいけないので、Usageが命なんです。そんな訳で、Usageにまた1時間くらいかけてわかりやすく書いたのでご覧ください。(https://github.com/kota-yata/Payrox-cal#usage)

完成品

終わりに

労働基準法関係以外の沼りに関しては完全に初心者ならではなので、僕と同じく初めてnpmパッケージを作る方は、貼りまくった参考webサイトも一緒に見ながら作ってみてください!

ツイッターフォローオナシャス (https://twitter.com/AlGoRiT94422608)


課題管理表をNode.jsとSQLiteで作る①

$
0
0

初めての投稿なので、至らぬ点があるかと思いますが、温かい目で見て頂ければ幸いです。

プロジェクトの管理の中で、気がついたときに課題管理表にメンバーが書き込みを行うといった事をやっていましたが、Excelだと誰かが開きっぱなしだったりすると編集出来なくてちょっと面倒です。

プロジェクト管理ツールを利用していればそちらを使うのも手ですが、今回はNode.jsとSQLiteを使って誰でもいつでも気がついたときに課題の登録が出来るようなものを目指して作ってみました。

環境はWindows10で行っています。

使うもの

私が動作を確認した構成は以下の通りです

  • npm:6.4
  • Node.js:8.xx
  • Express:4.16
  • SQLite:3.22

SQLiteのインストール

公式サイトに行ってダウンロード&インストールを行ってください。

DBとテーブルの作成

今回はToBe方式での課題管理を行うことを想定し、以下のカラムを用意します。

  • 課題
  • 理想の姿
  • 理想とのギャップ
  • 解決策

インストールフォルダをカレントディレクトリにします。

まずDBを作りたいので、以下のコマンドを発行します。

sqlite3kadai.db

次に課題の記録用にテーブルを作ります

createtablekadaitable(idintegerPRIMARYKEYAUTOINCREMENT,kadaitext,risoutext,gyapputext,kaiketsutext)

カラムは以下のような定義にしました。

  • id (キー項目&オートインクリメントで自動採番にしました)
  • kadai(課題)
  • risou(理想の姿)
  • gyappu(理想とのギャップ)
  • kaiketu(解決策)

実際はかっこいいカラム名とかにして貰えればと思います。

作り終わったらCtrl+cでSQLiteから抜けて、コマンドプロンプトを閉じてください。

Node.jsで課題管理表の雛形を作る(Express)

今回は「kadai」というフォルダを作成して、そこに作っていく想定で説明します。

kadaiフォルダをカレントに設定し、Nodeコマンドプロンプトで必要な以下のパッケージをインストールします

# Express-Generator
npm i express-generator
# SQLiteのモジュール
npm i sqlite3

トップページを作る

トップページには以下の物を作ります

  • タイトル
  • 課題登録画面遷移リンク
  • 課題表

レイアウト等は自分であまり考えたくないので、CDNのBootstrapを利用しました。

viewsフォルダのindex.ejsを以下のように編集します。

<!DOCTYPE html><html><!-- Bootstrap CSS --><linkrel="stylesheet"href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS"crossorigin="anonymous"><head><title>課題管理表</title><linkrel='stylesheet'href='/stylesheets/style.css'/></head><body><divclass="container"><divclass="row"><divclass="col"><h1>課題管理票</h1></div></br><divclass="col-md-12"><ahref="/write">新規追加</a><br></div><br><tableclass="table table-bordered"><theadclass="thead-dark"><tr><thscope="col">No</th><thscope="col">課題</th><thscope="col">理想の姿</th><thscope="col">理想の姿とのギャップ</th><thscope="col">解決策</th></tr></thead><tbody><%for(vari=0;i<posts.length;i++){%><tr><td><%=posts[i].id%></td><td><%=posts[i].kadai%></td><td><%=posts[i].risou%></td><td><%=posts[i].gyappu%></td><td><%=posts[i].kaiketsu%></td></tr><%}%></tbody></table></div></div><script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.bundle.min.js"integrity="sha384-zDnhMsjVZfS3hiP7oCBRmfjkQC4fzxVxFhBx8Hkz2aZX8gEvA/jsP3eXRCvzTofP"crossorigin="anonymous"></script></body></html>

続いてindex.ejsで利用するためのroutesの中のindex.jsを修正します。

varexpress=require('express');varsqlite3=require('sqlite3');varrouter=express.Router();vardb=newsqlite3.Database('./kadai.db');/* GET home page. */router.get('/',(req,res,next)=>{db.serialize(()=>{db.all('select * from kadaitable',(err,rows)=>{if(!err&&rows){constnewRows=rows.map(row=>{if(row.content){row.content=row.content.replace(/\r?\n/g,'<br>');}returnrow;});console.log(newRows);res.render('index',{posts:newRows});}});});});module.exports=router;

とりあえずトップ画面はこれで完成しました。

Nodeのコマンドプロンプトから以下を実行します

npm start

こんな感じのメニューが表示されたらOKです。

課題管理表.png

その2では登録画面を作って、実際にSQLiteのテーブルに書き込み、トップ画面に反映されるまでを解説します。

Redisを使ってExpressとWebSocketのセッションを共有する

$
0
0

先日、WEBサイト制作者向けのウェブサービスをリリースしたのですが、その制作過程で得た知見をシリーズで発信していく記事の第3弾になります。
個人開発でウェブサービスにトライしてみたいと考えている方の参考になりましたら嬉しいです。

Node.jsサーバでWebSocketを使用する際、httpセッションを共有したいという場面が出てくると思います。
今回の記事では、httpセッションとWebSocketセッションを共有する方法について書いてみたいと思います。

Redisをセッションストアとして使う

実はRedisを使わなくても実現できるのですが、以下の理由によりRedisを使うこととします。

  • Nodoサーバで全部受け止めるのではなく負荷を分散させておきたい
  • 開発時にNodeサーバを起動し直してもセッションを維持できて便利
  • 複数のNodeサーバでセッションを共有できるので、機能によってサーバ(コンテナ)を分けられる

Redisをひとことで説明すると、ネットワーク接続されたインメモリデータストアで、主にキー・バリュー型のデータを扱うNoSQLデータベースのひとつというところでしょうか。

セッションデータをRedisに書き込むことにより、複数のプログラムから参照してセッションを共有しようという作戦です。

httpセッションをWebSocketで呼び出す

ちょっと長いですが、コードを提示します。

constexpress=require('express')constWebSocket=require('ws')constRedis=require('ioredis')constSession=require('express-session')constRedisStore=require('connect-redis')(Session)// ---------------------------------------------------// sessionデータを保管するstoreを用意する// ---------------------------------------------------// Redisのhostとportは環境変数から読み込むconst{REDIS_HOST,REDIS_PORT}=process.env// Redis用インスタンスを生成する// セッション&通知バッファリング用constredis=newRedis({host:REDIS_HOST||'redis',// ※Dockerで使用する前提でこんな風に設定してますport:REDIS_PORT||6379})// セッションストアを生成するconststore=newRedisStore({client:redis})// ---------------------------------------------------// ここからexpressの設定(httpセッション)// ---------------------------------------------------// expressを生成するconstapp=express()app.use(express.json())// Redisに紐づいたセッションプロバイダーを作るconstsession=Session({store,secret:'hogehoge',// お好きにsecretを指定してくださいresave:false,saveUninitialized:false,cookie:{secure:'auto'}})// expressにセッションプロバイダーを紐づけるapp.use(session)// port 3000 でlinten開始consthttpServer=app.listen(3000)// expressを初期化する段階で、Redisに紐づいたセッションプロバイダーと差し替えるだけなので、// すでに稼働しているプログラムの改変も最小限の作業で済む(かも)// あとは普通にexpressの処理を記述すればOK!.........// ログイン処理app.post('login',(req,res,next)=>{// ログインチェック......// ログイン完了時の処理// DBなどから取得したユーザ情報をセッションに書き込むイメージ user = ユーザ情報req.session.user=user// ログイン成功時にセッションID(req.sessionID)をクライアントに返す!  // これを後でWebSocketを通して返却してもらい紐づけに使用するべしres.json({login:true,sessId:req.sessionID})})// ---------------------------------------------------// ここからWebSocketの設定//// WebSocketサーバを生成するconstwsServer=newWebSocket.Server({server:httpServer.server})wsServer.on('connection',(ws)=>{ws.on('open',()=>{// ここでは、redisSessionId というパラメータにセッションIdを保管することにしますws.redisSessionId=''})ws.on('message',(message)=>{// データを復号するconstmsg=JSON.parse(message)// セッションidをwsに紐づける処理// この例では、クライアントから次のようなデータを受け取る想定// { cmd: 'connect_session', sessionID: 'xxxxxxxxxxxxx' }if(msg.cmd==='connect_session'){ws.redisSessionId=msg.sessionID// クライアントにセッションidを受け取ったことを報告しておくreturnws.send(JSON.stringify({result:'received_session_id'}))}......})......})// ---------------------------------------------------// 何らかのイベント処理(WebHookなど)//consthogeEvent=(params)=>{// wsServerには複数のsocketが接続されている// wsServer.clientsをループで処理して個々のWebSocketに対して処理を行うwsServer.clients.forEach(async(ws)=>{// Sessionを取得するconstsession=!ws.redisSessionId?awaitgetSession(ws.redisSessionId)// セッションデータ読込関数を呼ぶ:{}console.log(session.user)......})}// ---------------------------------------------------// セッションデータの読み込み関数//constgetSession=(sid)=>{returnnewPromise((resolve,reject)=>{// Redisに紐づいたセッションストアからセッションデータを取得するstore.get(sid,(err,session)=>{if(err){returnreject(err)}resolve(session?session:{})})})}

言葉で説明するとこんな感じです

  1. httpサーバ:セッションをRedisで管理するよう設定する
  2. httpサーバ:ログインが完了したらセッションIDをクライアントに返す
  3. クライアント:WebSocketでセッションIDを送信する
  4. WebSocketサーバ:受け取ったセッションIDをソケットに紐づけて記録しておく
  5. WebSocketサーバ:セッションIDを使ってRedisストアからセッションデータを読み出す

クライアント側のコーディングに関して注意が必要なのは、WebSocketのコネクションの状況(未接続・接続・切断)に応じて適切に処理を切り分けてやる点です。

このあたりは、要件に応じて正解が異なって来ますので、悩みどころではないかと思います。

宣伝です。。。

この記事の冒頭で触れたウェブサービスにも、この手法(異なる部分も多いですが)を利用しています。

WEBサイト制作者さん向けのサービスですので、お気軽にお試しいただけると幸いです。(無料で使えます)

aws-sdk-mockを使ってもS3がモックに差し替わらずに困りました

$
0
0

aws-sdk-mockを使ってもS3(にアクセスするAWS SDK)がモックに挿し変わらずに困り果ててました・・。

明示的にaws-sdkのパスを指定することで解決しました。

jestのテストコード
constawsMocker=require('aws-sdk-mock');// 【ここがポイント】 以下のようにaws-sdkのパスを明示的に指定する必要がありました。constpath=require('path');awsMocker.setSDK(path.resolve('node_modules/aws-sdk'));consttestee=require('テスト対象ファイルへのパス');// その他 略describe('S3の画像保存テスト',()=>{describe('正常時',()=>{it('putObjectを1回だけ実行する',asyncdone=>{letcalledCount=0;awsMocker.mock('S3','putObject',(params,callback)=>{calledCount++;callback(null,{"ETag":"00000000000000000000000000000000"});});awaittestee.putImage({"filename":"test.txt","file_base64":"YWJjZGVmZw=="});expect(calledCount).toBe(1);done();});});});

おそらく、aws-sdkへのパスが明示的に指定されないと、aws-sdk-mockさんは差し替え対象のaws-sdkを正しく特定できないのでしょう。

その結果、本物のS3のSDKが動いてしまったのだと思います。

地味なところで時間を使ってしまったのでした・・。

Cypressで日本語が文字化けするとき

$
0
0

下記をcypress.jsに追加すると解決する。

{"modifyObstructiveCode":false,}

詳しくは読んでないが、下記issueに書いてあった。
https://github.com/cypress-io/cypress/issues/1543

経緯

試しにCypress使ってお願いされたサイトのテストを書いていたら、特定の文字のみ文字化けする。
怪しいところがないか調べたところ、<meta charset="Shift_JIS">が指定されている。
超怪しい。
ということで、Charset周りのことを調べてたら上に行き着いて解決しました。

JSのモジュール機能とbabelとwebpackと

$
0
0

CommonJSとECMAScript

JavaScriptにはサーバサイドのNodeJS(CommonJS)とブラウザのJavaScript(ECMAScript)の二つの言語仕様がある.

二つはモジュール機能(JavaScriptファイルを外部参照する機能)の記述に関して大きな違いがある.

CommonJSのモジュール機能

CommonJSでモジュールを外部参照できるようにするためには,主にmodule.exportsを使う.

abc.js
module.exports=変数1, 変数2, ...

CommonJSでモジュールを参照するためには,requireを使う.
requireで参照するモジュールのファイル名を指定することで,module.exportsした変数そのものを参照することができる.

constabc=require('abc')

ECMAScriptのモジュール機能

ECMAScriptでモジュールを外部参照できるようにするには,主にexportもしくはexport defaultを使う.

abc.js
export変数exportdefault変数

ECMAScriptでモジュール参照するためには,importを使う.
※import分はトッブレベルでないと使えない(ブロック中では使用不可).

import{変数}from'abc'//export変数を参照する場合importabcfrom'abc'//export default を参照する場合

CommonJSでもimport/exportが使用可能に

package.jsonに"type":"module"の設定を追加することでimport/exportが使用可能.

babel

フロントエンドの実装には,ブラウザ間でHTML,CSS,JSの実装レベルが異なるという大きな問題がある.
この問題に関して新しいJS文法を古いJS文法に変換して古いブラウザでも使えるようにするためのツールがbabelである.
babelの役割は非常に多く,様々なことが実現可能である.

  • 新しいJS文法から古いJS文法への変換
  • NodeJSでECMAScriptのimport/exportの実装
  • ReactなどのJSX文法の変換
  • TSからJSへの変換

babelの設定ファイル

.babelrc, babel.config.js,babel.config.cjs,babel.config.jsonのいずれかの設定ファイルに設定を書くか
webpackの場合,babel-loaderプラグインのoptionsに指定する方法が存在.

webpack

webpackは,JavaScriptアプリケーション用の静的モジュールバンドルである.
webpackがアプリケーションを処理するとき,プロジェクトが必要とする全てのモジュールをマップし,1つ以上のバンドルを生成する依存関係グラフを内部で構築する

課題管理表をNode.jsとSQLiteで作る②

$
0
0

①に続いて今度はDBへの登録画面を作っていきます。

登録画面の作成

まずviewsにDB書き込みフォームとなるwrite.ejsを新規作成します。

中身はこんな感じで必要最低限の項目とDBへの登録ボタンを用意します。

<!DOCTYPE html><html><head><title>登録</title><linkrel='stylesheet'href='/stylesheets/style.css'/></head><body><h1>新規課題登録</h1><formaction="/write"method="POST"><table><tr><th>課題</th><td><textareaname="kadai"></textarea></td></tr><tr><th>理想の姿</th><td><textareaname="risou"></textarea></td></tr><tr><th>理想の姿とのギャップ</th><td><textareaname="gyappu"></textarea></td></tr><tr><th>解決策</th><td><textareaname="kaiketsu"></textarea></td></tr><th></th><td><buttontype="submit">投稿</button></td></tr></table></form></body></html>

テキストエリアのサイズ等はお好みでCSSかタグに記述して調整してください。

なおテーブル定義で作成したIDについては自動採番されるので、登録項目には不要です。

次にwrite.ejsで利用するDB登録用のjavascriptをroutesの中にwrite.jsとして新規作成します。

varexpress=require('express');varsqlite3=require('sqlite3');varrouter=express.Router();vardb=newsqlite3.Database('./kadai.db');router.get('/',(req,res,next)=>res.render('write'));router.post('/',(req,res,next)=>{// 登録内容をフォームから引っこ抜くconstkadai=req.body.kadai;constrisou=req.body.risou;constgyappu=req.body.gyappu;constkaiketsu=req.body.kaiketsu;// DBに登録するdb.run('insert into kadaitable (kadai,risou,gyappu,kaiketsu) values (?, ?, ?, ?)',kadai,risou,gyappu,kaiketsu,);// 登録したら一覧に戻るres.redirect('/');});module.exports=router;

最後に作成したDBをkadaiフォルダに設置したら作成完了です。

完成

npm startを実行してみてください。

http://localhost:3000/
にアクセスし、トップ画面が表示されるはずです。
ここで新規追加をクリックしてみましょう。
kadai1.png

登録フォームに遷移出来たら、課題を入力してみましょう。
kadai2.png

なお項目のNULLチェックなどは実装していませんので、そのあたり作り込みたい人は是非挑戦してみてください。

試しにこんな内容で登録してみます。

kadai3.png

登録を実行してトップページに遷移し、登録内容が表示されれば成功です。

kadai4.png

あとはガンガン追加していくだけです。

kadai5.png

この状態だとシンプルでちょっとさみしいですが、bootstrapを利用しているので、デザインに凝ったり、色々カスタマイズしてみてはいかがでしょうか?

普段ExcelやWordで使っている簡単なものからWEB化してみると楽しいですよ。

お役に立てば幸いです。

HerokuのPostgreSQLにSSL接続する

$
0
0

  
短いですが備忘録として残しておきます!

エラー

Node.jsのORM(Object Relation Mapping)であるSequelizeを利用して、
ローカルからHerokuのPostgreSQLに接続する際に下記のエラーが出て接続できませんでした。。

error: no pg_hba.conf entry for host "DB_HOST", user "DB_USER", database "DB", SSL off

これはSSL接続がオフになっているためオプションでオンにしてあげる必要があります。

解決

constSequelize=require('sequelize')constsequelize=newSequelize('postgres://~~',// DB情報{// DBにSSL接続するdialectOptions:{ssl:true,},})

上記のようにSSL接続をtrueとすることで解決しました!


代替手段の探し方

$
0
0

新しいものを勉強する時や久しぶりに触るミドルウェアとかアプリケーションについて、もっといいものあるんじゃないかなって時にどうしてます?

自分は
アプリケーション名 alternative
って検索してみてます。

例)
nvm alternative
node.jsのversion managerの代替アプリを探した場合。
nveって製品が見つかりました。

あとは出てきたものと、元の製品とあわせて
nvm nve
とか調べてみたりします。

ubuntu18.04にnode12系とnpm6系をインストールする

$
0
0

実行環境

  • OS:ubuntu 18.04

前提条件

  • 特に無し

実施手順

公式のREADME.mdに従ってコマンドを実行するだけです。

node12系を指定し、インストール。

curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs

以上です。

結果確認

Node.js のバージョン

$ node -v
v12.16.2

npm のバージョン

$ npm -v
6.14.4

まとめ

公式のREADME.mdの手順をそのまま実行しただけでした。

タイムリーに is-promise 2.2.0 破損に巻き込まれた話

$
0
0

Node.jsの環境をSSDに引越しして動作を確認していたところ、yoがインストールできない・動かないという謎の現象に見舞われた。実は自分の環境が悪いのではなく、タイムリーにyoが依存するたった1行の関数を提供するis-promiseが壊れた直後にインストールしたことが原因と分かった。

オープンソースの怖さを思い知るとともに、何か起きたらエラーの発生元を確認しにいかないといけないなと思った事件だった。(あとはバージョン違いを考慮したテストの自動化も大事)

起きた現象

yoをインストールすると、ちゃんと動くかをチェックするYeoman Doctorが実行される。ここで、yo --versionでエラーが起きてしまっている。この後yoを実行しても、同じエラーで起動すらしない状況となった。

> yo@3.1.1 postinstall K:\nodejs\npm_global\node_modules\yo
> yodoctor


Yeoman Doctor
Running sanity checks on your system

√ No .bowerrc file in home directory
√ Global configuration file is valid
√ NODE_PATH matches the npm root
√ No .yo-rc.json file in home directory
√ Node.js version
Error: Command failed: C:\WINDOWS\system32\cmd.exe /s /c "yo "--version""
internal/modules/cjs/loader.js:584
            if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e;
                                                            ^

Error [ERR_INVALID_PACKAGE_TARGET]: Invalid "exports" main target "index.js" defined in the package config /K:/nodejs/npm_global/node_modules/yo/node_modules/is-promise\package.json
    at resolveExportsTarget (internal/modules/cjs/loader.js:542:13)
    at resolveExportsTarget (internal/modules/cjs/loader.js:581:20)
    at applyExports (internal/modules/cjs/loader.js:455:14)
    at resolveExports (internal/modules/cjs/loader.js:508:23)
    at Function.Module._findPath (internal/modules/cjs/loader.js:632:31)
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:1001:27)
    at Function.Module._load (internal/modules/cjs/loader.js:884:27)
    at Module.require (internal/modules/cjs/loader.js:1074:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at Object.<anonymous> (K:\nodejs\npm_global\node_modules\yo\node_modules\run-async\index.js:3:17) {
  code: 'ERR_INVALID_PACKAGE_TARGET'
}

エラーの原因

エラーメッセージにある通り、run-asyncが利用するis-promiseで、ERR_INVALID_PACKAGE_TARGETが起きてしまっているのが原因。npmのis-promiseのページを見てみると、以下のようにビルドエラーが起きたと表示されている。30分ほど前にPublishされたバージョンで何が起きたのか…
image.png

is-promiseのGitHubにはIssueが既に投稿されていて、同じエラーが起きていることが分かった。(ここで初めて、自分の環境が問題ではないかも…と分かった)
image.png

原因はPackage.json incorrectly formatted #12に投稿されていた(ejs/cjsといった、Javascriptのモジュールの形式の指定が間違っていたとのこと…あまり理解できていないが、ejsをサポートするNode.jsのバージョンでNGになるそう)。今回のバージョンで追加された機能が悪さをしてしまった模様。
image.png

その後、[BUGFIX] Use correct paths for CJS and ESM compatibility #15で修正があり、プルリクエストが発行されていた。
image.png

とりあえず、少し待てば反映されるかもしれないので待ってみる。Weekly download 1000万超の定番モジュールがほんの少しのミスで色々なモジュールに影響を与えてしまうとは…

暫定対策

とりあえず問題が起きているのはyoのグローバルインストールが使っているrun-asyncなので、yoのnode_modules以下のrun-asyncpackage.jsonを変更して、is-promiseのバージョンを2.1.0に固定する。package.jsonを変更したらrun-asyncのフォルダでnpm installを実行し、is-promiseの2.1.0をインストールする。

package.json
"dependencies":{"is-promise":"2.1.0"元々は"^2.1.0"だった},

この処置の後、yoを実行したら動くようになった。

>yo
? 'Allo! What would you like to do? (Use arrow keys)
  Run a generator
> Code
  ──────────────
  Update your generators
  Install a generator
  Find some help
  Get me out of here!
(Move up and down to reveal more choices)

Google Analytics, Google SpreadSheet, Big Query, Google Ad ManagerのAPIをNode.jsで触ってみる

$
0
0

はじめに

データの可視化を行う当たってGoogleの各種APIを触ってみましたが、最初は認証の仕方とか基本的な書き方とかでつまづく所もあるので、ハンズオンとして触り方をなるべくわかりやすく紹介したいと思います。
今回紹介するのは

  • Google Analyticsのレポート
  • Google SpreadSheet
  • Big Query
  • Google Ad Managerのレポート

のAPIになります。
サンプルコードはこちらに上げています。

Google Analytics の レポートAPI

概要

  • GAのAPIドキュメントを検索すると、Reporting API v4 と Core Reporting API が出てきますが、最新は Reporting API v4 です。
  • ガイドをみると、Node.js用のクライアントは無い様なので、HTTP POSTリクエストを自分で生成する必要があります。
  • 認証に関してはサービスアカウントを作る方法が固いと思います。こちらからサービスアカウントを新規作成し、登録完了ページからクレデンシャルjsonをダウンロードしておいてください。
  • サービスアカウントの権限でクエリーを実行するので、作成したサービスアカウントをGAにユーザとして追加し、レポートを実行するための権限を与えておいてください。
  • 実際にAPIリクエストを行う際の認証はOAuthトークンを使用します。なにやら難しそうな感じですが、google-auth-libraryを使用すると簡単に取得ができる様になっています。
  • クエリの書き方はこちらが参考になります。

実装

パッケージのインストール

npm i --save google-auth-library axios

コード

// GAのAPIの利用例です// トークンの取得のためconst{auth}=require('google-auth-library');// HTTPリクエストにはaxiosを使用します。constaxios=require('axios');constpath=require('path');(async()=>{// --- start OAuthアクセストークンを手に入れる ---// credentialファイルを読み込むconstcredentialJson=require(path.resolve(`${__dirname}/../credentials/ga.json`));constclient=auth.fromJSON(credentialJson);// 許可するスコープを定義client.scopes=['https://www.googleapis.com/auth/analytics.readonly'];awaitclient.authorize();consttoken=client.credentials.access_token;// --- end OAuthアクセストークンを手に入れる ---// --- start Queryを作成する ---// ブラウザに対するページビューを取得するconstquery={reportRequests:[{viewId:'{{ GAのビューIDを入力 }}',// todo// レポートの期間dateRanges:[{startDate:'2020-04-01',endDate:'2020-04-02',}],// ディメンジョンdimensions:[{name:'ga:browser'}],// 指標metrics:[{expression:'ga:pageviews'},],// 指標のフィルターをかけたい場合は以下の様に記述する// dimensionFilterClauses: [//   {//     operator: 'OR',//     filters: [//       {//         dimensionName: 'ga:pagePath',//         operator: 'REGEXP', // or EXACT//         expressions: ['hoge']//       }//     ]//   }// ],// GAのレポートはコンソールからの操作と同様にサンプリングされる可能性があることに注意するsamplingLevel:'DEFAULT'}]};// --- end Queryを作成する ---// --- start リクエストを実行する ---constresponse=awaitaxios.post('https://analyticsreporting.googleapis.com/v4/reports:batchGet',query,{headers:{// アクセストークンを設定Authorization:`Bearer ${token}`}});// --- start リクエストを実行する ---// 出力console.table(response.data.reports[0].data.rows.map(row=>{return[...row.dimensions,...row.metrics[0].values];}));})().catch(e=>console.log(e));

補足

BigQueryのクエリをAPIで実行

概要

  • BigQueryのAPIにはNode.jsのクライアントライブラリがありますのでこちらを利用します。
  • 自身のGCPアカウントのプロジェクトでAPIを有効化します。
  • 認証はGAと同じくサービスアカウントで行うのがいいでしょう。サービスアカウントにBigQuery ジョブユーザーなどのAPIが実行可能な権限をつけてください。
    • サンプルコードでは credentials/bq.jsonという名前で保存する想定です。
  • こちらのページにサンプルがあるので簡単に記述ができますが、サービスアカウントで認証を行う部分だけ以下のコードに追記しておきます。

実装

パッケージのインストール

npm i --save @google-cloud/bigquery

コード

constpath=require('path');(async()=>{// クライアントを初期化const{BigQuery}=require('@google-cloud/bigquery');constbigquery=newBigQuery({// 認証はクレデンシャルファイルのパスを渡すだけでいいkeyFilename:path.resolve(`${__dirname}/../credentials/bq.json`),projectId:'{{ プロジェクト名を入力 }}'});// クエリを作成constquery=`SELECT name
      FROM \`bigquery-public-data.usa_names.usa_1910_2013\`
      WHERE state = 'TX'
      LIMIT 100`;// 全てのオプションは -> https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/queryconstoptions={query:query,location:'US',};// ジョブを実行const[job]=awaitbigquery.createQueryJob(options);// ジョブの完了を待つconst[rows]=awaitjob.getQueryResults();// 結果を表示console.log('Rows:');console.table(rows);})();

Google Spread Sheet の API

はじめに

  • 今回はシートにデータを吐き出してみます。
  • Google Sheets APIを使用します。
  • Node.jsのクライアントがあるので、使用します。
  • こちらも上記サンプルページの通りなのですが、認証の通し方を少し変えます。
  • 自身のGCPのプロジェクトで Google Sheets APIを有効にします。
  • マイドライブから操作したいSpreadSheetを任意のサービスアカウントに対して編集権限で共有します。
    • サンプルコードでは credentials/sheets.jsonという名前でサービスアカウントのクレデンシャルjsonを保存する想定です。

実装

実装

パッケージのインストール

npm i --save googleapis

コード

constpath=require('path');const{google}=require('googleapis');(async()=>{// 認証するconstauthClient=awaitgoogle.auth.getClient({keyFile:path.resolve(`${__dirname}/../credentials/sheets.json`),scopes:['https://www.googleapis.com/auth/spreadsheets']});constsheets=google.sheets({version:'v4',auth:authClient});// 保存するデータconstcells=[[1,2,3],[4,5,6]];// 書き込みconstreq={// スプレッドシートのID, URLの一部// https://docs.google.com/spreadsheets/d/{{ここの部分}}/edit#gid=0spreadsheetId:'{{SPREAD_SHEET_ID}}',range:`シート1!A:AZ`,valueInputOption:'USER_ENTERED',resource:{values:cells}};awaitsheets.spreadsheets.values.append(req);})();

実装

Google Ad Manager のレポートAPI

はじめに

  • 一番の曲者でした。
  • Node.jsのクライアントライブラリは無いので、HTTPリクエストを自分で作る必要があります。
  • クエリの方法ですが、SOAPという古いAPIの仕様が採用されているので、XMLでクエリを記述してPOSTのBODYに埋めこみます。
  • JSでXMLを扱うために xml-jsというライブラリを使用します。クエリはXMLに変換するので、xml-jsに合わせた形式で記述しています。
  • 執筆時点で最新バージョンはv202002
  • 認証は同様にサービスアカウントで行う。GAMの管理画面からGCPコンソールで作成したサービスアカウントを追加し、レポート用の権限をつけてください。
  • サンプルコードでは credentials/gam.jsonにサービスアカウントのクレデンシャルが保存されている想定です。
  • GAMはレポートの条件によって、APIからは実行できないレポート条件が存在します。API経由で保存済みのレポートのクエリを取得する機能がありますので、GAMの管理画面からサービスアカウントのユーザに「成り代わる」をして、レポートを保存しておくとAPI経由でクエリの内容とAPIに対応しているかを取得できます。(こちらはサンプルコードに書いていません。)

レポート取得の流れ

こちらの通り
https://developers.google.com/ad-manager/api/reference/v202002/ReportService

  1. ReportService.runReportJobでジョブを作成
  2. ReportService.getReportJobでジョブの状態を取得し、完了するまでポーリングする
  3. 完了後、ReportService.getReportDownloadURLを使用して、レポートのダウンロード用URLを取得する
  4. ダウンロードする

実装

パッケージのインストール

npm i --save google-auth-library request axios xml-js

コード

constpath=require('path');const{auth}=require('google-auth-library');constaxios=require('axios');const{js2xml,xml2js}=require('xml-js');// SOAPのベース// soapenv:Body部分を色々変えるconstsoapJsonBase={_declaration:{_attributes:{version:'1.0',encoding:'UTF-8'}},'soapenv:Envelope':{_attributes:{'xmlns:soapenv':'http://schemas.xmlsoap.org/soap/envelope/','xmlns:xsd':'http://www.w3.org/2001/XMLSchema','xmlns:xsi':'http://www.w3.org/2001/XMLSchema-instance'},'soapenv:Header':{'ns1:RequestHeader':{_attributes:{'soapenv:actor':'http://schemas.xmlsoap.org/soap/actor/next','soapenv:mustUnderstand':'0',// APIのバージョンはここで指定'xmlns:ns1':`https://www.google.com/apis/ads/publisher/v202002`},'ns1:networkCode':{_text:'{{ GAMのネットワークIDを指定 }}'},'ns1:applicationName':{_text:'{{ GCPのプロジェクト名を指定 }}'}}},'soapenv:Body':undefined}};(async()=>{// --- クエリの作成 ---// 日時と広告ユニットごとの合計のインプレッションを取得constquery={// ディメンジョンdimensions:[{_text:'DATE'},{_text:'AD_UNIT_NAME'},// 広告ユニット],adUnitView:{_text:'FLAT'},// 指標columns:[// 全体系{_text:'TOTAL_LINE_ITEM_LEVEL_IMPRESSIONS'},// 合計のインプレッション],// レポートの期間startDate:{year:{_text:'2020'},month:{_text:'4'},day:{_text:'1'}},endDate:{year:{_text:'2020'},month:{_text:'4'},day:{_text:'2'}},// 日時範囲の期間は「固定」dateRangeType:{_text:'CUSTOM_DATE'},// フィルターをかける場合は以下の様に記述//statement: {//  query: {//    _text: ` where PARENT_AD_UNIT_ID = {{広告ユニットID}}`//  }//},// 管理画面とタイムゾーンを合わせるtimeZoneType:{_text:'PUBLISHER'}};// --- クエリの作成 ---// --- アクセストークンを取得 ---constkeys=require(path.resolve(`${__dirname}/../credentials/gam.json`));constclient=auth.fromJSON(keys);client.scopes=['https://www.googleapis.com/auth/dfp','https://www.googleapis.com/auth/analytics.readonly'];awaitclient.authorize();consttoken=client.credentials.access_token;// --- クエリの作成 ---// SOAPヘッダーに変換するオブジェクトを作成// xml-jsでXMLに変換する// 変換後↓// <runReportJob xmlns="https://www.google.com/apis/ads/publisher/v202002">//   <reportJob>//     <reportQuery> クエリー </reportQuery>//   </reportJob>// <runReportJob>letsoapJson={...soapJsonBase};letbody={// ここで実行するコマンドを指定runReportJob:{// 中身はコマンドの引数_attributes:{xmlns:`https://www.google.com/apis/ads/publisher/v202002`},reportJob:{reportQuery:query}}};soapJson['soapenv:Envelope']['soapenv:Body']=body;// APIリクエストを実行letsoapXML=js2xml(soapJson,{compact:true});letresponse=awaitaxios.post(`https://ads.google.com/apis/ads/publisher/v202002/ReportService`,soapXML,{headers:{Authorization:`Bearer ${token}`}});// レスポンスもXMLなのでjsonに変換するletdata=xml2js(response.data,{compact:true});constjobId=data['soap:Envelope']['soap:Body']['runReportJobResponse']['rval']['id']['_text'];console.log('ジョブID',jobId);// ジョブの完了をポーリングするconstsleep=()=>newPromise(resolve=>setTimeout(()=>resolve(),1000));while(true){console.log('ポーリング');soapJson={...soapJsonBase};body={getReportJobStatus:{_attributes:{xmlns:`https://www.google.com/apis/ads/publisher/v202002`},reportJobId:{_text:jobId}}};soapJson['soapenv:Envelope']['soapenv:Body']=body;// APIリクエスト実行soapXML=js2xml(soapJson,{compact:true});response=awaitaxios.post(`https://ads.google.com/apis/ads/publisher/v202002/ReportService`,soapXML,{headers:{Authorization:`Bearer ${token}`}});data=xml2js(response.data,{compact:true});// ステータスを取得conststatus=data['soap:Envelope']['soap:Body']['getReportJobStatusResponse']['rval']['_text'];console.log('ステータス',status);if(['COMPLETED','FAILED'].includes(status)){console.log('完了');break;}awaitsleep();}// レポートのダウンロード用URLを取得するconsole.log('URLを取得');soapJson={...soapJsonBase};body={getReportDownloadURL:{_attributes:{xmlns:`https://www.google.com/apis/ads/publisher/v202002`},reportJobId:{_text:jobId},exportFormat:{_text:'CSV_DUMP'},}};soapJson['soapenv:Envelope']['soapenv:Body']=body;// APIリクエスト実行soapXML=js2xml(soapJson,{compact:true});response=awaitaxios.post(`https://ads.google.com/apis/ads/publisher/v202002/ReportService`,soapXML,{headers:{Authorization:`Bearer ${token}`}});data=xml2js(response.data,{compact:true});constdownloadURL=data['soap:Envelope']['soap:Body']['getReportDownloadURLResponse']['rval']['_text'];console.log(downloadURL);// ダウンロードconstfs=require('fs');constrequest=require('request');awaitnewPromise((resolve)=>{request(downloadURL).pipe(fs.createWriteStream('report.csv.gz')).on('close',async()=>{resolve();});});// gzipなので、解答するconstchild_process=require('child_process');awaitchild_process.execSync(`gunzip -f ./report.csv.gz`);})().catch(e=>console.log(e));

補足

試して見ると結構エラーする条件があると思います。特に多いのはフィルター条件です。例えば定義済みのkey-valueでレポートを作成したい場合があるかと思いますが、GAMの管理画面のレポートと違い文字列でkay-valueを指定することはできません。この場合CUSTOM_CRITERIAと言うディメンジョンをしてすると、CUSTOM_TARGETING_VALUE_IDと言うカラムが追加されるので、これでフィルターをかけます。何でフィルターできて、できないのかはドキュメントにほとんどのディメンジョンごとに記載されています。
使用可能なディメンジョン、指標、フィルター条件などはこちらを参考ください。
https://developers.google.com/ad-manager/api/reference/v202002/ReportService.ReportQuery

以上

参考になれば幸いです。

ImageMagickをJSから呼び出す

$
0
0

概要

ImageMagickを使って画像生成をしようと思っていて、簡単な処理ならターミナル等でコマンドを打って実行するのだが複数の画像を順番に合成するなどシーケンシャルな処理をする場合にそれだとしんどいので何かスクリプトを使いたいなと思って検討した結果JavaScript(以下JS)にしようと思い検討理由と実行記録を残しておきました。

なぜJSにしたのか

本当に簡単な処理ならShellscriptでもいいかなと思いますが今回はif文もいくつかケースがわかれたりJSONの読み込み等が発生するため除外。もちろんShellscriptでもできますが不慣れだし大抵の人にとっては見にくいケースもあるので。
次にPythonを検討した。PythonMagickなどのライブラリが用意されていて使いやすいということもあるし最近は画像処理で多く使われていたり人気の言語だったりするので。
迷った結果最終的にはElectronでGUI作る必要があるかもしれないというのがあったのでJSにしました。

実行環境

  • macOS Mojave 10.14.6
  • node v12.16.1
  • シェルは bin/bash

方法

上記で実行環境書いてますがやり方としてはnode.jsを使ってコマンドラインでJSを呼び出し、JS内でシェルコマンドを実行するものとなっています。JSにもnode-imagemagickなどの直接ImageMagickを使うライブラリがあるのですがライブラリ自体のバグがあった場合に追うのが面倒だなと思ったのと情報量は直接コマンド実行したものの方が多いだろうと思ったため今回はJS内から直接コマンドを実行するようにしています。
node.jsはその時の最新の安定板を使いました。nodeの最新版の入れ方は参考にある記事等を見ていただければと思います。
JSからシェルコマンドを実行する方法としてはexecexecSyncというものがあって前者は非同期実行で後者は同期実行です。今回の例ではexecSyncを使います。
以下がプログラム。1行目でexecSyncを読み込み2行目でexecSyncを用いてImageMagickのコマンドを実行。
ちなみに今回のImageMagickのコマンドはconvertを用いて100x100の赤い画像を生成しています。

convertExample.js
const{execSync}=require('child_process');execSync('convert -size 100x100 xc:red red.jpg');

red.jpg

変数を用いて画像の大きさを変更したといった場合もあるのでその場合はutil.formatを使います。
以下は先ほどと同様に100x100の赤い画像を作成した後に青文字で"hoge"という文字を描いています。
また、util.formatを用いてフォントサイズや色や文字の内容を変数で渡すようにしています。

convertUseUtilExample.js
const{execSync}=require('child_process');constutil=require('util');constfontSize=24;constcolor='blue';constcontent='hoge';execSync('convert -size 100x100 xc:red red.jpg');execSync(util.format('convert -font Bookman-DemiItalic -gravity center -pointsize %d -fill %s -draw "text 0,0 %s" red.jpg red.jpg',fontSize,color,content));

red.jpg

上記のようにutil.formatを使ってコマンドをプログラムによって柔軟に変更することができるので組み合わせることで複雑な画像も生成できるかと思います。
画像を使う業務をしている方は使いこなすと思わぬところで業務効率もあがると思うのでで色々試してみましょう!

参考

NodebrewでNodeをインストールする
シェルコマンドを実行する方法(child_process)

Viewing all 8814 articles
Browse latest View live