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

Jest実行時にserverless.ymlの環境変数を読み込む

$
0
0

jest-environment-serverlessを利用してserverless.ymlから環境変数を読み込みます。
.envを読み込む以外の方法を試してみたかった)

手順

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

必要なパッケージをインストールします。

$ npm install serverless jest jest-environment-serverless

プロジェクトの作成

$ npx serverless create --template aws-nodejs

設定

serverless.ymlに読み込む環境変数の名称と値を記述します。

serverless.yml
service:sampleprovider:name:awsruntime:nodejs12.xfunctions:hello:handler:handler.helloenvironment:# これを読み込みますSAMPLE_VALUE:Sample

package.jsonにjestの設定を追加します。

package.json
{:"jest":{"testEnvironment":"jest-environment-serverless",}}

テストの作成

環境変数を console.log で出力する処理を記述します。

__test__/handler.spec.js
describe('Sample',()=>{it('check env',()=>{// 表示console.log(process.env.SAMPLE_VALUE);expect('');});});

テストの実行

$ npx jest
 PASS  __test__/handler.spec.js (10.71s)
  Sample
    ✓ check env(67ms)

  console.log __test__/handler.spec.js:5
    Sample

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        16.159s
Ran all test suites.

環境変数の値(Sample)を読み込むことができました。

おまけ

環境変数の名称と値をAPIから取得することもできます。

__test__/handler.spec.js
describe('Sample',()=>{it('check env',()=>{// serverless.yml の functions.hello.environment の読み込みconstenvVars=ServerlessWrapper.getEnv('hello');// 出力console.log(envVars);expect('');});});
$ npx jest
 PASS  __test__/handler.spec.js (8.714s)
  Sample
    ✓ check env(68ms)

  console.log __test__/handler.spec.js:8
    { SAMPLE_VALUE: 'Sample'}

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        13.556s
Ran all test suites.

環境変数の名称と値({ SAMPLE_VALUE: 'Sample' })を出力することができました。


Golang, Node.js, Kotlin, Swift で ECDSA

$
0
0

はじめに

楕円曲線DSA (ECDSA) は楕円曲線暗号を利用した電子署名方式で、通信のセキュリティ確保のために広く使われています。様々なプログラミング言語の標準的なライブラリでサポートされているため、言語をまたいで利用することができます。

ただし、実際に言語をまたいで署名生成 & 検証をしようとしてみると API の違いやフォーマット方式によりハマること多々あったため、書き方をまとめておくことにしました。

この記事に書かれていること

Golang, Node.js, Kotlin, Swift での(できる限り)標準ライブラリを利用した キーペア生成、署名生成・検証方法を記載します。

以降の記述は基本的に ECDSA を前提に書かれています。

全体的に、エラーハンドリングは省略しているので注意してください。

この記事に登場するキーワード

キーワード概要
EC楕円曲線、もしくは楕円曲線暗号のこと。
P-256利用する楕円曲線の種類。キーペア生成、署名生成・検証時にパラメータとして指定します。NIST で規定されているものとしては他には P-384 などがあります。
キーペア秘密鍵とそれに対応する公開鍵のペアのこと。 ECDSA では秘密鍵は 1 つの整数, 公開鍵は楕円曲線上の点を表す 2 つの整数で構成されます。
SHA256256 ビット(32 バイト)のハッシュ値を生成するハッシュ関数。
r, sECDSA の署名値。 r, s のどちらも整数値です。
ASN.1データ構造を定義するための標準インターフェイス記述言語(wikipedia のグーグル翻訳まま)
DER エンコードASN.1 の標準符号化規則の一つ。秘密鍵・公開鍵や署名データのシリアライズに利用する。
PEM 形式DER エンコードの結果のバイナリデータを base64 エンコードして -----BEGIN [TYPE]-----, -----END [TYPE]-----で囲ったもの。

Golang, Node.js, Kotlin, Swift での ECDSA

それぞれの言語でキーペア生成、署名生成、署名検証をおこないます。

公開鍵は PEM 形式, 署名データは ASN.1 エンコードした結果のバイナリデータを Base64 形式で出力します。

公開鍵を PEM 形式としたのはただ単によく見かけるから、という理由からです。(検証をおこなうという意味では DER エンコード されたバイナリデータを Base64 もしくは Hex 形式で出力するだけで十分だと、後から気づきました...)

Golang

Golang は標準ライブラリが充実しているため、さほど苦労することなく扱うことができます。

参考: golang ecdsa パッケージ

以下のコードは version: 1.13.3 で動作を確認しています。

packagemainimport("crypto/ecdsa""crypto/elliptic""crypto/rand""crypto/sha256""crypto/x509""encoding/asn1""encoding/base64""encoding/pem""fmt""math/big""os")const(msg="Hello, ECDSA!"targetPublicKeyPEM=`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExW7riGWvlxmRofxQNuRhsF9anb+8
F/1NRGzZziCC/utzFMXSg9YwzaRb0Yw+K2n0+1IkWH7lQT9j4DZhF6Npfg==
-----END PUBLIC KEY-----`targetSignature="MEUCICzZzFaPemBrWBLNlbbEG+CyXEdAbum9YnOe7lK0rNonAiEA8p1QN/1VcuWRvrPSDnELXedMfiP1FPtk/dmP3Sf/7gA=")typerawSignaturestruct{R,S*big.Int}funcmain(){sign()verify()}funcsign(){fmt.Println("================================ start signing ================================\n")// P-256 をパラメータに指定してキーペアを生成privateKey,_:=ecdsa.GenerateKey(elliptic.P256(),rand.Reader)publicKey:=privateKey.PublicKey// 秘密鍵の整数値を出力fmt.Printf("private key is %d\n",privateKey.D)fmt.Println()// 秘密鍵を SEC 1, ASN.1 DER エンコードsec1FormPrivateKey,_:=x509.MarshalECPrivateKey(privateKey)// PEM 形式で出力_=pem.Encode(os.Stdout,&pem.Block{Type:"EC PRIVATE KEY",Headers:nil,Bytes:sec1FormPrivateKey,})fmt.Println()// 公開鍵の整数値のペアを出力fmt.Printf("public key is (x: %d, y: %d)\n",publicKey.X,publicKey.Y)fmt.Println()// 公開鍵を PKIX, ASN.1 DER エンコードpkiFormPublicKey,_:=x509.MarshalPKIXPublicKey(&publicKey)// PEM 形式で出力_=pem.Encode(os.Stdout,&pem.Block{Type:"PUBLIC KEY",Bytes:pkiFormPublicKey,})fmt.Println()// メッセージのハッシュ値を取得hash:=sha256.Sum256([]byte(msg))// 署名生成r,s,_:=ecdsa.Sign(rand.Reader,privateKey,hash[:])fmt.Printf("signature: (r: %d, s: %d)\n",r,s)// 署名を ASN.1 エンコードasn1Signature,_:=asn1.Marshal(rawSignature{r,s})// Base64 形式で出力fmt.Printf("asn1 base64 encoded signature: %s\n\n",base64.StdEncoding.EncodeToString(asn1Signature))}funcverify(){fmt.Println("================================ start verification ================================\n")// PEM ブロックを取得block,_:=pem.Decode([]byte(targetPublicKeyPEM))ifblock==nil||block.Type!="PUBLIC KEY"{panic("invalid public key pem data")}publicKey,_:=x509.ParsePKIXPublicKey(block.Bytes)asn1Signature,_:=base64.StdEncoding.DecodeString(targetSignature)varsigrawSignatureasn1.Unmarshal(asn1Signature,&sig)// メッセージのハッシュ値を取得hash:=sha256.Sum256([]byte(msg))// 署名検証valid:=ecdsa.Verify(publicKey.(*ecdsa.PublicKey),hash[:],sig.R,sig.S)fmt.Printf("signature was verified: %t\n",valid)}

出力結果:

================================ start signing ================================

private key is 86406366532313532520773863615456167011096149492537621067924417740068666801996

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIL8IRTYAiQtNKvAMDxMtucbcrF40K9lPEJr1eFG3JP9MoAoGCCqGSM49
AwEHoUQDQgAECLCYIbdaHGU4phHj28OXTy04YcKD2wsL0fqbSCP4pMQIghdIGvCd
jwZ9nntlLfpdY/d6Wnp/GcwEosAYSCQFjg==
-----END EC PRIVATE KEY-----

public key is (x: 3930517846499297788187286115327721111010190045004457380847771725537278993604, y: 3848353591206560331525005698968473002174715783271413427180613072969827353998)

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECLCYIbdaHGU4phHj28OXTy04YcKD
2wsL0fqbSCP4pMQIghdIGvCdjwZ9nntlLfpdY/d6Wnp/GcwEosAYSCQFjg==
-----END PUBLIC KEY-----

signature: (r: 47681670912106433244589806297495994583210073185120436994285114290076291204903, s: 93735733074916934648422947032629918680486834787857816571963967793396929295074)
asn1 base64 encoded signature: MEUCIGlq3o447llhyWn8G/p9GN3e1NMDC7zZm21OUIj+RIcnAiEAzzyLeJtUyecBmFvxA/bV0uXEuZ5B1fN4xyEcilv8cuI=

================================ start verification ================================

signature was verified: true

Node.js

Node.js も標準ライブラリを利用できますが、秘密鍵・公開鍵や署名はエンコードされた情報はとれるものの、整数値を直接取得することはできないようです。
(DER, PEM デコードするライブラリは数多く存在していたので、必要があれば簡単に取得はできそうです)

参考: Node.js crypto モジュール

以下のコードは version: 12.14.1 で動作を確認しています。

constcrypto=require("crypto");constmsg="Hello, ECDSA!";consttargetPublicKeyPEM=`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExW7riGWvlxmRofxQNuRhsF9anb+8
F/1NRGzZziCC/utzFMXSg9YwzaRb0Yw+K2n0+1IkWH7lQT9j4DZhF6Npfg==
-----END PUBLIC KEY-----`consttargetSignature="MEUCICzZzFaPemBrWBLNlbbEG+CyXEdAbum9YnOe7lK0rNonAiEA8p1QN/1VcuWRvrPSDnELXedMfiP1FPtk/dmP3Sf/7gA="main();functionmain(){sign();verify();}functionsign(){console.info("================================ start signing ================================\n")// P-256 をパラメータに指定してキーペアの生成const{privateKey,publicKey}=crypto.generateKeyPairSync("ec",{namedCurve:"P-256",});// 秘密鍵を SEC 1, ASN.1 DER エンコード & PEM 形式で出力console.info(privateKey.export({type:"sec1",format:"pem",}));// 公開鍵を PKIX, ASN.1 DER エンコード & PEM 形式で出力console.info(publicKey.export({type:"spki",format:"pem",}));// 署名生成constsigner=crypto.createSign("SHA256");// ハッシュ関数を指定signer.update(msg);signer.end();constsignature=signer.sign(privateKey,"base64");// 署名は ASN.1 エンコード され、 Base64 形式で出力されているconsole.info(`asn1 base64 encoded signature: ${signature}\n`);}functionverify(){console.info("================================ start verification ================================\n")constpublicKey=crypto.createPublicKey(targetPublicKeyPEM)// 署名検証constverifier=crypto.createVerify("SHA256");// ハッシュ関数を指定verifier.update(msg);verifier.end();constvalid=verifier.verify(publicKey,targetSignature,"base64");console.info(`signature was verified: ${valid}`);}

出力結果:

================================ start signing ================================

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILtZOGwW/gh1geY6yu4bfEuzSrwa4BJnuE37gwAsZb/IoAoGCCqGSM49
AwEHoUQDQgAEPu/QDDiV4ry2T4Ki9r9VIXgvLH09x/4J32HVdOXUlnVQegD52191
DQJ3Q2H41MTnD+uZdlGnQAUkgYSRt1A7jw==
-----END EC PRIVATE KEY-----

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPu/QDDiV4ry2T4Ki9r9VIXgvLH09
x/4J32HVdOXUlnVQegD52191DQJ3Q2H41MTnD+uZdlGnQAUkgYSRt1A7jw==
-----END PUBLIC KEY-----

asn1 base64 encoded signature: MEQCIBdoySVlQAjUSVb61H+7FzPI3+b4m4Agy62MO6/vVFkEAiAWPRjje4g/6/LpY/dUg+4dteQRK/qMI/kn3s0zIJbrTQ==

================================ start verification ================================

signature was verified: true

Kotlin (Android)

Android 開発においては Keystore システムでサポートされる API を利用することができます。
鍵データの管理を委譲できるのは大きなメリットである反面、秘密鍵の情報にアクセスする API が用意されていないようです。
例えば、独自にバックアップを取るなど、特殊なことをする場合は工夫が必要そうです。

署名については Node.js 同様に ASN.1 エンコード後のバイナリデータが返されます。

参考: Android Keystore システム

以下のコードは android SDK version: 29, kotlin version: 1.3.61 で動作を確認しています。

(PEM 形式を扱う箇所はかなり強引な書き方をしています。適切なライブラリを使ったほうが良いです)

packagecom.example.ecdsaimportandroidx.appcompat.app.AppCompatActivityimportandroid.os.Bundleimportandroid.security.keystore.KeyGenParameterSpecimportandroid.security.keystore.KeyPropertiesimportandroid.util.Base64importjava.security.KeyFactoryimportjava.security.KeyPairGeneratorimportjava.security.Signatureimportjava.security.interfaces.ECPublicKeyimportjava.security.spec.X509EncodedKeySpecclassMainActivity:AppCompatActivity(){companionobject{constvalmsg="Hello, ECDSA!"constvaltargetPublicKeyPEM="""-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExW7riGWvlxmRofxQNuRhsF9anb+8\
F/1NRGzZziCC/utzFMXSg9YwzaRb0Yw+K2n0+1IkWH7lQT9j4DZhF6Npfg==
-----END PUBLIC KEY-----"""constvaltargetSignature="MEUCICzZzFaPemBrWBLNlbbEG+CyXEdAbum9YnOe7lK0rNonAiEA8p1QN/1VcuWRvrPSDnELXedMfiP1FPtk/dmP3Sf/7gA="}overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)sign()verify()}privatefunsign(){println("================================ start signing ================================")valparameterSpec:KeyGenParameterSpec=KeyGenParameterSpec.Builder("ECPrivateKey",KeyProperties.PURPOSE_SIGNorKeyProperties.PURPOSE_VERIFY).run{setDigests(KeyProperties.DIGEST_SHA256)// ハッシュ関数を指定build()}// キーペア生成valkeyPair=KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC,"AndroidKeyStore").let{it.initialize(parameterSpec)it.generateKeyPair()}valpublicKey=keyPair.publicasECPublicKey// 秘密鍵は KeyStore 内で管理される前提であるためか、内部のデータにアクセスする API が見当たらなかった// 公開鍵の整数値のペアを出力println("public key is (x: ${publicKey.w.affineX}, y: ${publicKey.w.affineY})")// 公開鍵を PKIX, ASN.1 DER エンコード & PEM 形式で出力 (かなり強引...)println("-----BEGIN PUBLIC KEY-----")Base64.encodeToString(publicKey.encoded,Base64.DEFAULT).trim().chunked(64).forEach{println(it.replace("\n","\\n"))}println("-----END PUBLIC KEY-----")// 署名生成valsignature=Signature.getInstance("SHA256withECDSA").run{initSign(keyPair.private)update(msg.toByteArray())sign()}// 署名は ASN.1 エンコード されているため、Base64 形式で出力println(String.format("asn1 base64 encoded signature: %s",Base64.encodeToString(signature,Base64.DEFAULT).trim().replace("\n","\\n")))}privatefunverify(){println("================================ start verification ================================\n")valsignature=Base64.decode(targetSignature,Base64.DEFAULT)valspec=X509EncodedKeySpec(Base64.decode(targetPublicKeyPEM.trim().replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----",""),Base64.DEFAULT))valpubKey=KeyFactory.getInstance("EC").generatePublic(spec)valvalid:Boolean=Signature.getInstance("SHA256withECDSA").run{initVerify(pubKey)update(msg.toByteArray())verify(signature)}println("signature was verified: $valid")}}

出力結果:

I/System.out: ================================ start signing ================================
I/System.out: public key is (x: 82167081552335602286200448410416710140443532428724715799809599812531686098238, y: 51175083263095894836653222459656189252260595373682081626878418494739276551753)
I/System.out: -----BEGIN PUBLIC KEY-----
I/System.out: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtajriseSquTJ0f2EQZQli7czMp6v
I/System.out: pHAHTW2Tq25e\nRT5xJBIYA6AgPrEdKuPtVcgamRFSKE82w1YEdxMBQCrWSQ==
I/System.out: -----END PUBLIC KEY-----
I/System.out: asn1 base64 encoded signature: MEQCIBeZSNHoN3VD7laNSDl0CGGgjrqGp50RCG6azqXmjrR/AiBKUHXJyXNLmIUCPwv33zvRfwfr\n83mfi5cJOV5Zf2QVgQ==
I/System.out: ================================ start verification ================================
I/System.out: signature was verified: true

Swift

Swift ではネイティブ API の扱いが煩雑だったため、外部ライブラリを利用しました...。

利用ライブラリ: BlueECC

ネイティブ API の扱いについてはこちらの記事が参考になります。

以下のコードは iOS: 13.1, Swift: 5.1.3 で動作を確認しています。

letmsg="Hello, ECDSA!"lettargetPublicKeyPEM="""
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV4ZTwqTk5Sd5no5ibjjTXSTZCHQV
vpe4qdp2rodC\nMdgCmdl/ZyuCpg/6PH6arDviA2HYVR13/ssin6/Etp93RQ==
-----END PUBLIC KEY-----
"""lettargetSignature="MEUCIAU0/hEz2+RRIwzXkau64jfmUSbFoFMltXEGtl3LHlZHAiEAqak5H/QdRlheYpSpfTGTInQs\nWOUq0mDavgif8+X5uAM="funcecdsa(){sign()verify()}funcsign(){print("================================ start signing ================================\n")// P-256 をパラメータに指定して秘密鍵を生成letprivateKey=try!ECPrivateKey.make(for:.prime256v1)// 秘密鍵を SEC 1, ASN.1 DER エンコード & PEM 形式で出力print(privateKey.pemString)print()// 公開鍵を PKIX, ASN.1 DER エンコード & PEM 形式で出力letpublicKey=try!privateKey.extractPublicKey()print(publicKey.pemString)print()// 署名生成letsignature=try!msg.sign(with:privateKey)// 署名を ASN.1 エンコードしたものを Base64 形式で出力print("asn1 base64 encoded signature: \(signature.asn1.base64EncodedString())\n")}funcverify(){print("================================ start verification ================================\n")letpublicKey=try!ECPublicKey(key:targetPublicKeyPEM)letsignature=try!ECSignature.init(asn1:Data(base64Encoded:targetSignature,options:Data.Base64DecodingOptions.ignoreUnknownCharacters)!)letvalid=signature.verify(plaintext:msg,using:publicKey)print("signature was verified: \(valid)");}

出力結果:

================================ start signing ================================

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILu54xIBwH3Vd45Fgx9yCCgOTynjxvIMh+PnL86qOx7roAoGCCqGSM49
AwEHoUQDQgAENa6T19s23zEVLBvUYyVbZjRGPqhUkYJcv7SA8J05F8Vql7Aw9GR+
G/uxgYFqe6j1MYQ2tPF9MN32cc+xG2OCUw==
-----END EC PRIVATE KEY-----

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENa6T19s23zEVLBvUYyVbZjRGPqhU
kYJcv7SA8J05F8Vql7Aw9GR+G/uxgYFqe6j1MYQ2tPF9MN32cc+xG2OCUw==
-----END PUBLIC KEY-----

asn1 base64 encoded signature: MEYCIQD+fGwKEVX8aTzdbRgpEy9/nWHAsAw0JQXAKH4IJo4uEgIhAJKfFkN1Akl18rrnyfwwsqMa2dWwWXLbX1yRaHLZwdRG

================================ start verification ================================

signature was verified: true

まとめ

標準化をされている技術ではあるものの、各言語ごとに書き方の癖があってハマりがちな処理を並べました。デバッグのしづらさはデジタル署名のセキュリティの高さの裏返しではあるものの、ハマってしまった方にこの記事が少しでも役にたてば嬉しいです。

記載・認識ミス、もっと良い書き方などありましたら、ご指摘お願いします。

参考

CLI のテンプレートプロジェクト by node and TypeScript

$
0
0

node で CLI(Command Line Interface) を開発する機会が数回あって、せっかくなのでテンプレートプロジェクトとしてまとめてみた。

テンプレートプロジェクト

必要なモノ

  • nodejs: v11.13.0+
  • typescript: v3.7.3+

試し方

  1. 上記のリポジトリを Clone する
  2. リポジトリのディレクトリに cd して npm ciする
  3. npm run buildする
  4. npm linkする
  5. source ~/.bash_profileを行うかまたはターミナルを再起動する

これでどのディレクトリでも my-greatコマンドが使用できるようになる。

$my-great hello -f Echizen -s Ooka -a 42

Hello Echizen Ooka.
You're 42 years old.
$my-great something wrong param

Command Line Interface for My great service

  Sample for CLI.

Commands

  my-great hello -f <first_name>-s<second_name>   Say Hello.
  my-great version                                  Show version.

アンインストール

npm uninstall -g @amay/my-great-cli

要点

コマンドライン引数の解析と使用方法の表示

yargsとかいろいろあったけど、自分的に使いやすかったのでこれを選択。

プログラム構成

my-great hello -f <first_name> -s <second_name>   Say Hello.
my-great version                                  Show version.

のように、第一引数を「コマンド」とし、第2引数以降をそのコマンド専用の引数群としたかったので、index.tsで第一引数のみを parse して取得し、コマンド毎に command-xxxx.tsへ委譲している。

command-line-args では commandLineArgs(this.paramDef, { partial: true })partial:trueを設定すると、引数定義(paramDef) に存在しない引数があっても無視する。

cli コマンド名

cliコマンド名 my-greatpackage.jsonbin:で指定している。

package.json

{"name":"@amay/my-great-cli",<省略>"bin":{"my-great":"build/index.js"},<省略>

ビルドされた ./build/index.jsを指すように設定している。
ちなみに npm run 経由で node を実行する場合は、引数の前に --を付ける(例: node ./build/index.js -- version)。

必須引数のチェック

command-line-args では 引数の必須チェックを自力で行わなければならないようなので、定義体の paramDefrequire: booleanを追加し、パースした実際の引数である XxxxConfigrequire = trueな項目が含まれているかをチェックするようにした。

// Valid require paramsconstrequiresNotSetted=this.paramDef.filter(x=>x.require).filter(x=>cfg[x.name]==null).map(x=>`--${x.name}`);if(requiresNotSetted.length>0){console.log(`Param: ${requiresNotSetted.join('')} is required.`);console.log(`------------------------------------`);this.usage[1].optionList=this.paramDef;constusg=commandLineUsage(this.usage)console.log(usg);return-1;}

kebab-case VS camelCase VS snake_case

コマンドの引数は kebab-case がデファクトスタンダードの模様。
command-line-args では commandLineArgs(this.paramDef, { camelCase: true })とすると、--first-nameに渡された引数を、firstName変数に格納してくれる。

が、前述の必須引数のチェックが(定義体と実体の変数名が異なるため)正しく機能しなくなるので妥協案として snake_case の --first_nameを採用している。

コマンドを追加するには

  1. index.tsCommandTypexxxxを増やす
  2. command-xxx.ts(CommandXxxxクラス) を作る
  3. index.tscommandMapに追加する
  4. mainUsageになんか書く

参考

Node.js(Express) + Pug(Jade)でDBにtextareaから保存された改行を表示する

$
0
0

コード

index.js
text.replace(/\r?\n/g,'<br>')
index.pug
 p !{text}

注意

#{text}で出力するのではなく、!{text}でないと表示されない。

備考

変数である textは、DBにtextareaから保存されたテキストのデータ

例えば、テキストエリアに、

ファッキンホット
ファッキンコールド

と入力した場合

上記の処理をしないと、
ファッキンホット ファッキンコールド

と表示される。

Node.jsでAWS ElasticSearchへのHTTP リクエストの署名

$
0
0

はじめに

情報保護を難しくなっている現代社会では、セキュリティ対応はますます重要になってきています。
Cloud技術の進化によって、セキュリティ対応しやすくなる部分もあります。

AWSのElasticeSearchサービスへのHTTP リクエストの署名方法を簡単にまとめてみます。

1. AWS SDKのライブラリを使う

AWSのドキュメントにある通り、署名したリクエストを送信できますが、検索のクエリなどはちょっと手間ですね。

参考URL: https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-request-signing.html

node.js
varcredentials=newAWS.EnvironmentCredentials('AWS');varsigner=newAWS.Signers.V4(request,'es');signer.addAuthorization(credentials,newDate());varclient=newAWS.HttpClient();client.handleRequest(request,null,function(response){// ....}

2. aws-elasticsearch-connectorモジュールを使う

Node.jsからElasticSearchへアクセスするには、ElasticSearchクライアントを使うと検索などに便利です。
aws-elasticsearch-connectorを利用して署名も簡単にできます。

2.1 aws-elasticsearch-connectorインストール

npm install --save aws-elasticsearch-connector @elastic/elasticsearch aws-sdk

2.2 profile利用例

node.js
constAWS=require('aws-sdk');const{Client}=require('@elastic/elasticsearch');const{AmazonConnection}=require('aws-elasticsearch-connector');// Load AWS profile credentialsAWS.config.update({profile:'my-profile'});constclient=newClient({node:'my-elasticsearch-cluster.us-east-1.es.amazonaws.com',Connection:AmazonConnection});

2.3 .envにアクセスキー、シークレットキー利用例

AWS_ACCESS_KEY_ID=foo      # alias: AWS_ACCESS_KEYAWS_SECRET_ACCESS_KEY=bar  # alias: AWS_SECRET_KEYAWS_SESSION_TOKEN=xxx  //optional
node.js
const{Client}=require('@elastic/elasticsearch');const{AmazonConnection}=require('aws-elasticsearch-connector');constclient=newClient({node:'my-elasticsearch-cluster.us-east-1.es.amazonaws.com',Connection:AmazonConnection,});

参考URL:https://github.com/compwright/aws-elasticsearch-connector#readme

2.4 検索結果

node.js
letsearchResult=awaitclient.search({index:'xxx_index',body:{//...});// ヒットしたデータlethits=searchResult.body.hits.hits;// ヒットしたデータ数lethitsCount=searchResult.body.hits.total;

ElasticSearchのクライアントAPI:
https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-usage.html

以上

Node.js で Cloud Firestore のデータを作成 (Create)

$
0
0

ライブラリーのインストール

sudo npm install firebase-admin --save
firestore_create.js
#! /usr/bin/node
// ---------------------------------------------------------------//  firestore_create.js////                  Jan/25/2020//// ---------------------------------------------------------------functionadd_data_proc(db,key,name,population,date_mod){lettt_ref=db.collection('cities').doc(key)tt_ref.set({'name':name,'population':population,'date_mod':date_mod})}// ---------------------------------------------------------------console.error("*** 開始 ***")constadmin=require('firebase-admin');admin.initializeApp({credential:admin.credential.applicationDefault()})constdb=admin.firestore();add_data_proc(db,'t0921','宇都宮',81235,'1950-9-24')add_data_proc(db,'t0922','小山',94235,'1950-2-15')add_data_proc(db,'t0923','佐野',71395,'1950-5-28')add_data_proc(db,'t0924','足利',35421,'1950-7-8')add_data_proc(db,'t0925','日光',61948,'1950-1-11')console.error("*** 終了 ***")// ---------------------------------------------------------------

実行コマンド

export GOOGLE_APPLICATION_CREDENTIALS="***.json"
./firestore_create.js

次のバージョンで確認しました。

$ node --version
v13.7.0

Node.js で Cloud Firestore のデータを読む (Read)

$
0
0

こちらで作成したデータを読みます。
Node.js で Cloud Firestore のデータを作成 (Create)

firestore_read.js
#! /usr/bin/node
// ---------------------------------------------------------------//  firestore_read.js////                  Jan/25/2020//// ---------------------------------------------------------------console.error("*** 開始 ***")constadmin=require('firebase-admin')admin.initializeApp({credential:admin.credential.applicationDefault()})constdb=admin.firestore()db.collection('cities').get().then((snapshot)=>{snapshot.forEach((doc)=>{//  console.log(doc.id, '=>', doc.data())varunit_aa=doc.data()varout_str=doc.id+"\t"out_str+=unit_aa["name"]+"\t"out_str+=unit_aa["population"]+"\t"out_str+=unit_aa["date_mod"]console.log(out_str)})console.error("*** 終了 ***")}).catch((err)=>{console.log('Error getting documents',err);})// ---------------------------------------------------------------

実行コマンド

export GOOGLE_APPLICATION_CREDENTIALS="***.json"
./firestore_read.js

Node.js で Cloud Firestore のデータを更新 (Update)

$
0
0

こちらで作成したデータを更新します。
Node.js で Cloud Firestore のデータを作成 (Create)

firestore_update.js
#! /usr/bin/node
// ---------------------------------------------------------------//  firestore_update.js////                  Jan/25/2020//// ---------------------------------------------------------------console.error("*** 開始 ***")constkey_in=process.argv[2]constpopulation_in=process.argv[3]console.log(key_in+"\t"+population_in)consttoday=newDate()varddx=(1900+today.getYear())+"-"+(today.getMonth()+1)ddx+="-"+today.getDate()console.log(ddx)constadmin=require('firebase-admin')admin.initializeApp({credential:admin.credential.applicationDefault()})constdb=admin.firestore()try{letcityRef=db.collection('cities').doc(key_in)cityRef.update({population:population_in,date_mod:ddx})}catch(error){console.error("*** error *** update ***")console.error(error)}console.error("*** 終了 ***")// ---------------------------------------------------------------

実行コマンド

export GOOGLE_APPLICATION_CREDENTIALS="***.json"
./firestore_update.js t0923 91456200

Node.js で Cloud Firestore のデータを削除 (Delete)

$
0
0

こちらで作成したデータのひとつを削除します。
Node.js で Cloud Firestore のデータを作成 (Create)

firestore_delete.js
#! /usr/bin/node
// ---------------------------------------------------------------//  firestore_delete.js////                  Jan/25/2020//// ---------------------------------------------------------------console.error("*** 開始 ***")constkey_in=process.argv[2]console.log(key_in)constadmin=require('firebase-admin')admin.initializeApp({credential:admin.credential.applicationDefault()})constdb=admin.firestore()try{db.collection('cities').doc(key_in).delete()}catch(error){console.error("*** error *** delete ***")console.error(error)}console.error("*** 終了 ***")// ---------------------------------------------------------------

実行コマンド

export GOOGLE_APPLICATION_CREDENTIALS="***.json"
./firestore_delete.js t0924

npm で全ての package をアップデートする

$
0
0
$ npx npm-check-updates -u

これで package.jsonのみ更新される。
あとは、いつも通り npm i

以下、オプション例。

# "devDependencies" のみアップデートしたい
npx npm-check-updates -u--dep dev

# 特定の package はアップデートしたくない
npx npm-check-updates -u--reject typescript,@types/node

Ref.

https://www.npmjs.com/package/npm-check-updates

expressによるウェブページテンプレート化の手法(express-ejs-layoutsを使用)

$
0
0

概要

Expressにおけるwebページのテンプレート化の方法をまとめてみる。
使用するパッケージは以下の通り。
・express
・express-ejs-layouts

作成するファイルは以下の通り。
(1) /main.js
テンプレート化に必要なパッケージの読み込み、レスポンスのロジックを実装

(2) /views/layout.ejs
テンプレートエンジンが使用するレイアウトのウェブページのテンプレート。このファイルでページのレイアウトとコンテンツの埋め込み箇所を定義する。

(3) /views/index.ejs
テンプレートエンジンがレイアウトに埋め込むファイル。
ここではindexのページを定義しているが、ユーザーのアクセスするパスに応じて他のejsファイルも作成する。

Step1. main.jsの実装

まず、以下のようにmain.jsを実装する。
(1) パッケージのインポート
以下3つの定数を定義する。

const express =require(“express”)
const layouts = require(“express-ejs-layouts”)
const app = express()

(2) view engineの指定
appに対して、view engineの属性設定。

app.set(“view engine”,”ejs”)

https://expressjs.com/ja/guide/using-template-engines.html

(3) リクエストパスに対するレスポンスロジックの実装
以下のようにrenderに/view/index.ejsのファイルをテンプレートとして読み込むことを指定する。拡張子指定不要。

app.get(“/”, (req, res) => {
    res.render(“index”)
})

Step2. layout.ejsの定義

layout.ejsはページ間で共通のコンテンツを定義する。
その上で、各ページ固有のコンテンツを埋め込む場所を定義する。

<html>
<head> ... </head>
<body> 
レイアウトのコンテンツを記載

<%- body %>

</body>
</html>

上記でも記載のあるように、以下の記述が各ページのコンテンツの埋め込み先になる。

<%- body %>

Step3. index.ejsの定義

index.ejsには、laytout.ejsの<%- body %>に埋め込むコンテンツを定義することになる。

<h1>これがhttp://xxxxxx.xx.xx/にアクセスした際に表示されるページのコンテンツ部分です</h1>

ポイントは、HTMLを記載しているのだが、お決まりのから始まるタグの記載をするのではなく、layout.ejsに埋め込まれることを想定したhtmlの記述をする必要がある。

AppiumでFlutterアプリのテストを自動化する 実践編(JavaScript)

$
0
0

はじめに

AppiumでFlutterアプリのテストを自動化する 環境構築編 - Qiita
の続きになります。
実際にテストコードを書いて、それを実行し、レポートを出力するところまでやります。
今回は「JavaScript」を使います。

前提条件

AppiumでFlutterアプリのテストを自動化する 環境構築編 - Qiita
で、Appiumの環境構築が完了していること

なぜAppiumで自動化するのか

Flutterには、「Integration Test」という仕組みが存在します。

これは結合テストを行うための仕組みですが、この仕組みを利用することで、UIテストを自動化することも可能になります。また、各OSごとにUIテストを用意せずとも、ワンソースで実装することができます。
ですが、テストしたいWidgetに対して逐一Keyを設定していかなければならないのと、全て手動で実装しないといけないのが難点になります。
Appiumはワンソースで実装することが容易ではありませんが、OSごとのデバイス設定やWidgetの取得方法などをうまく共通化することができればワンソースでも実現可能ですし、何よりレコード機能があるため、Appiumで自動化する選択肢もありだと考えています。
ただ何を優先するかはプロジェクトによりけりではあるので、どちらが正解というわけでもないと思います。

プロジェクトの準備

appium/sample-code/javascript-wd at master · appium/appium · GitHub
をベースにプロジェクトを準備します。
なぜこのプロジェクトを流用したかというと、レコード機能で取得した内容をコピペするだけで実装が容易にできるのと、Mochaというテスティングフレームワークがなかなか良さげ(レポート機能ついてたりとか)だったので採用しました。
基本的にはほぼ流用している形になります。

package.json
{"name":"appium_test_js_wd","version":"1.0.0","description":"","scripts":{"test":"mocha test/**/*.test.js","clean":"rm -rf node_modules && rm -f package-lock.json && npm install"},"author":"","license":"ISC","engines":{"node":">=6","npm":">=6"},"devDependencies":{"@babel/register":"^7.0.0","@babel/core":"^7.0.0","@babel/preset-env":"^7.0.0","chai":"^4.1.2","mocha":"^6.0.0","wd":"^1.5.0"}}
.babelrc
{"presets":[["@babel/preset-env",{"targets":{"node":"6"}}]]}

mocha.optsでオプションを設定することができます。
--reporterオプションでレポートの出力形式を設定することができます。

test/mocha.opts
--require @babel/register
--timeout 1800000
--reporter spec
test/.eslintrc
{"rules":{"func-names":0}}

Appium Desktopでテストコードを記録

テストコードの記録方法については以下に記載していますので参照してください。
AppiumでFlutterアプリのテストを自動化する 実践編(Python) - Qiita
使用するアプリは上記ページと同様です。
記録する言語は、「JS(wd)」を選択します。

記録したテストコードを実行できるようにする

例えば以下のように記載します。

test/top/top.test.js
importwdfrom'wd';importchaifrom'chai';const{assert}=chai;describe('カウントアップアプリ',function(){letdriver;before(asyncfunction(){driver=awaitwd.promiseChainRemote("http://127.0.0.1:4723/wd/hub");constcaps={"platformName":"Android","automationName":"Appium","deviceName":"Android Emulator","app":"/Users/Hitoshi/AndroidStudioProjects/flutter_app_for_appium/build/app/outputs/apk/release/app-release.apk"};awaitdriver.init(caps);// ここで待たないと要素の取得に失敗してしまうので待つawaitdriver.setImplicitWaitTimeout(5000);});after(asyncfunction(){awaitdriver.quit();});it('初期状態',asyncfunction(){letel1=awaitdriver.elementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.view.View[3]");constcountText=awaitel1.text();assert.equal(countText,'0');});it('カウントアップされるか',asyncfunction(){letel1=awaitdriver.elementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.widget.Button");awaitel1.click();letel2=awaitdriver.elementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.view.View[3]");constcountText=awaitel2.text();assert.equal(countText,'1');});});

テストコードの実行

コマンドでテストコードを実行します。

npm test

すると、以下のように表示されるはずです。
端末側もアプリが起動し、ボタンが押されてカウントアップされるはずです。

> appium_test_js_wd@1.0.0 test /Users/Hitoshi/src/appium-test/appium_test_js_wd
> mocha test/**/*.test.js



  カウントアップアプリ
    ✓ 初期状態 (2195ms)
    ✓ カウントアップされるか (2199ms)


  2 passing (42s)

ソースコード

以下にアップしましたので参考にしてください。

Boltを使ったSlack BotをTypescriptで開発する方法

$
0
0

はじめに

今までslack用のbotを開発するのにhubotやerrbotを利用してきたのですが、昨年slackがリリースしたBoltフレームワークのチュートリアルをいくつかやってみて簡単にbot実装ができそうだなと思ったので、それをTypeScriptで実装する場合の方法を紹介します。

Bolt開発時に参考にしたサイト

Boltを使ったslack botアプリの作成方法や実装サンプルなどは、下記を参考にさせて頂きました。

開発環境

主に下記の開発環境で実装及び動作確認をしました。

Glitchは、ウェブ上で動くIDEを持っているのですが、ローカルでVSCodeを使うことでコード補完やESLintなども使えるようになるのでローカル環境でVSCodeを利用して実装します。

プロジェクトの作成

Bolt入門ガイドに記載のある通り、下記を実行しプロジェクトを作成します。

$mkdir first-bolt-app
$cd first-bolt-app
$npm init -y$npm install @slack/bolt

typescript実装のために必要なパッケージをインストールします。

$npm install-D typescript @types/node

その他ローカルで開発する上で利用するパッケージをインストールします。

$npm install dotenv # .envファイルを読み込ませる為に追加$npm install-D eslint eslint-config-prettier eslint-plugin-prettier prettier @types/eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser

各種設定ファイルの作成

slackアプリのbot tokenとsigning secretを記載した.envファイルを作成します。

.env
SLACK_SIGNING_SECRET=<your-signing-secret>
SLACK_BOT_TOKEN=xoxb-<your-bot-token>

Typescriptプロジェクトをコンパイルするのに必要なコンパイラのオプションを指定するtsconfig.jsonファイルを作成します。

tsconfig.json
{"compilerOptions":{"module":"commonjs","esModuleInterop":true,"allowSyntheticDefaultImports":true,"target":"es6","noImplicitAny":true,"moduleResolution":"node","sourceMap":true,"outDir":"dist","baseUrl":".","paths":{"*":["node_modules/*","src/types/*"]}},"include":["src/**/*"]}

ESLintの設定ファイル.eslintrc.eslintignoreを作成します。

.eslintrc
{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint"
  ],
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "rules": {
    "semi": [
      "error",
      "always"
    ],
    "quotes": [
      "error",
      "double"
    ],
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-explicit-any": 1,
    "@typescript-eslint/no-inferrable-types": [
      "warn",
      {
        "ignoreParameters": true
      }
    ],
    "@typescript-eslint/no-unused-vars": "warn"
  }
}
.eslintignore
# /node_modules/* in the project root is ignored by default
# build artefacts
dist/*
coverage/*
# data definition files
**/*.d.ts
# 3rd party libs
/src/public/
# custom definition files
/src/types/

tscコンパイルやbot実行の為に、package.jsonを下記の通りに変更します。

package.json
{〜省略〜"scripts":{"start":"npm run build && npm run serve","build":"npx tsc","serve":"node --require dotenv/config dist/app.js","test":"echo \"Error: no test specified\"&& exit 1"},〜省略〜}

コード作成と実行

ソースコードはsrc/以下に作成します。
下記はBolt入門ガイドの「メッセージのリスニングと応答」にあるコードをTypescriptコードにしたものです。
ファイル拡張子が.jsから.tsに変わったのとrequireをimport文に変えただけですね(笑)
もう少し複雑なサンプルコードを持ってくるとTypescriptの型エラーが発生し、適宜修正が必要になってきます。

src/app.ts
import{App}from"@slack/bolt";constapp=newApp({token:process.env.SLACK_BOT_TOKEN,signingSecret:process.env.SLACK_SIGNING_SECRET});// Listens to incoming messages that contain "hello"app.message("hello",({message,say})=>{// say() sends a message to the channel where the event was triggeredsay(`Hey there <@${message.user}>!`);});(async()=>{// Start your appawaitapp.start(process.env.PORT||3000);console.log("⚡️ Bolt app is running!");})();

Typescriptのコードをコンパイルします。

$npm run build
>first-bolt-app@1.0.0 build /Users/xxxx/first-bolt-app
>npx tsc

Javascriptに変換されたdist/app.jsが作成されます。

dist/app.js
"use strict";var__awaiter=(this&&this.__awaiter)||function(thisArg,_arguments,P,generator){functionadopt(value){returnvalueinstanceofP?value:newP(function(resolve){resolve(value);});}returnnew(P||(P=Promise))(function(resolve,reject){functionfulfilled(value){try{step(generator.next(value));}catch(e){reject(e);}}functionrejected(value){try{step(generator["throw"](value));}catch(e){reject(e);}}functionstep(result){result.done?resolve(result.value):adopt(result.value).then(fulfilled,rejected);}step((generator=generator.apply(thisArg,_arguments||[])).next());});};Object.defineProperty(exports,"__esModule",{value:true});constbolt_1=require("@slack/bolt");constapp=newbolt_1.App({token:process.env.SLACK_BOT_TOKEN,signingSecret:process.env.SLACK_SIGNING_SECRET});// Listens to incoming messages that contain "hello"app.message("hello",({message,say})=>{// say() sends a message to the channel where the event was triggeredsay(`Hey there <@${message.user}>!`);});(()=>__awaiter(void0,void0,void0,function*(){// Start your appyieldapp.start(process.env.PORT||3000);console.log("⚡️ Bolt app is running!");}))();//# sourceMappingURL=app.js.map

botを実行します。⚡️ Bolt app is running!が出力されれば成功です!

$npm run server
>first-bolt-app@1.0.0 serve /Users/xxxx/first-bolt-app
>node --require dotenv/config dist/app.js
⚡️ Bolt app is running!

コンパイルと実行を同時に行う場合は、npm run startを実行します。

$npm run start
>first-bolt-app@1.0.0 start /Users/xxxx/first-bolt-app
>npm run build && npm run serve
>first-bolt-app@1.0.0 build /Users/xxxx/first-bolt-app
>npx tsc
>first-bolt-app@1.0.0 serve /Users/xxxx/first-bolt-app
>node --require dotenv/config dist/app.js
⚡️ Bolt app is running!

ここまで動作確認ができたらGlitchに作成したファイルアップすることでローカルマシンまでのトンネルするアプリの公開URLを用意できないと確認ができないslackからのevent受信などの確認もできるようになります。

アップするファイル一覧
first-bolt-app/
├── .env
├── package.json
├── src
│   └── app.ts
└── tsconfig.json

Glitch上でbotが起動するときは、npm run startが実行されるのでコンパイル済みのdist/app.jsはアップしなくても動作します。

おまけ

Bolt フレームワークを使って Slack Bot を作ろうの「Learn More」の「もうちょっと複雑な Glitch サンプル」をTypescript実装したソースコードは下記においてますので参考になれば、幸いです。

コード内で環境名 (dev,stg,prod) を使用しないことのすすめ

$
0
0

サービスを運用する場合、開発、運用で複数の環境でサーバーを動かしている場合がよくあると思います。このとき、パラメータ設定の口が柔軟でないと設定値の変更がしにくかったり、サーバーのビルドを環境毎にしなければならなず(特に docker 使っている場合)、デプロイに時間がかかったりします。
そうならないために環境名 (dev,stg,prod) を使用して動作変更するのをやめることをおすすめします。記事内では以下のような環境があると想定し、Javascript (Node.js) でいくつかの例を示します。

  • 開発時ローカル PC
  • ステージングサーバー環境
  • 本番サーバー環境

変数を環境名に依存させるのをやめよう

サーバーでエラーが起きたときに管理者にメールを送るというケースで送り先を分ける場合で説明します。

悪い例(コード内で環境ごとのメールアドレスを決めている)

コード内で設定されているアドレスにしか送ることができません、送信するメールアドレスが変更になったときはコードの変更が必要。

// process.env.NODE_ENV に環境名が指定されているfunctionsendErrorMail(){letemail='default@example.com';switch(process.env.NODE_ENV){case'development':email='dev@admin.com';break;case'staging':email='stg@admin.com';break;case'production':email='prod@admin.com';break;default:break;}// sendMail は email アドレスとメール本文を指定してメールを送る関数sendMail(email,'Server Error Occurred');}

良い例(環境変数としてメールアドレス自体を指定している)

メールアドレスが変更になったときでも環境変数の指定を変更するだけで送信先が変更できる

// process.env.ADMIN_EMAIL_ON_ERROR にエラー時のメール送り先が指定されているfunctionsendErrorMail(){// sendMail は email アドレスとメール本文を指定してメールを送る関数sendMail(process.env.ADMIN_EMAIL_ON_ERROR,'Server Error Occurred');}

動作を環境名に依存させるのをやめよう

次も似た例ですが、ログのレベル設定を例に説明します。 logWarnという警告ログを出力する関数があるとします。開発環境だったら debug ログを出す、というパターンはよくあると思いますが、一つの環境変数としてログレベルを設定すると柔軟です。

悪い例(コード内で環境名でレベルを決めている)

// process.env.NODE_ENV に環境名が指定されているfunctionlogWarn(message){if(process.env.NODE_ENV=='development'){console.warn(message);}}

良い例(ログレベルを環境変数として設定する)

constLogLevel={DEBUG:0,INFO:1,WARN:2,ERROR:3,TRACE:4,};// process.env.LOG_LEVEL にログレベルが設定されているfunctionlogWarn(message){if(process.env.LOG_LEVEL<=LogLevel.WARN){console.warn(message);}}

最後に

この話は主流にありつつある(すでに主流?) Docker でのサーバーデプロイの背景が大きいです。Docker image を build して デプロイしてという流れで運用している場合、ちょっとした動作の変更によって Docker image の build し直ししなければならないとか、環境毎に image を build し直さなければけ無いとなると、build 時間はわりとかかるケース(色々なケースがありますが、数分から10分は最低でもかかると思います)が多いので、build し直ししなくてもいいように(実行時に環境変数として設定すればいいだけにするために)設定類は個別の設定毎に外出ししていくと便利だと考えています。

ajaxで取得したデータでrenderすると、Cannot read property xx of undefined エラーになる

$
0
0

問題点

例えば以下のように、バックグラウンドで取得したデータを表示させたいとします。

xxx.js
constructor(props){super(props);this.state={result:{}}fetch("http://localhost/api/test").then(response=>response.json()).then(json=>this.setState({result:json}));// /api/testは、以下のようなレスポンスを返す//  { //    test1 : "aaa",//    test2 : {//      test3 : "bbb"//    }//  }}render(){return(<div><p>{this.state.result.test1}</p>        //"aaa"が表示される<p>{this.state.result.test2.test3}</p>  //"bbb"が出てほしいがエラーになる</div>
)}

このとき、this.state.result.test2.test3の部分で、以下のようなエラーが出てしまいます。
Cannot read property 'test3' of undefined
test3 の親であるthis.state.result.test2がundefinedですよ、というエラーです。

原因

恐らく、this.state.resultの中身が全部セットされる前にrenderメソッドが動いてしまっているためだと思います。

解決法

1.描画前にチェックを行う

undefinedチェックを入れてあげると、エラーなく動くようになります。

xxx.js
render(){return(<div><p>{this.state.result.test1}</p>        //"aaa"が表示される<p>{this.state.result.test2?this.state.result.test2.test3:""}</p>  //"bbb"が表示される</div>
)}

JSX内ではif文が使えないので、三項演算子を使っています。

2.あらかじめ空のプロパティをセットしておく

どのような要素が返ってくるかが最初からわかっているのであれば、以下のように予めセットしておくことでも対応できます。

xxx.js
constructor(props){super(props);this.state={result:{test2:{}// あらかじめ空をセットしておく}}fetch("http://localhost/api/test").then(response=>response.json()).then(json=>this.setState({result:json}));}render(){return(<div><p>{this.state.result.test1}</p>        //"aaa"が表示される<p>{this.state.result.test2.test3}</p>  //"bbb"が表示される</div>
)}

最後に

もっといい方法がありそうな気もするので、何かわかったら追記します。


nodeをパッケージでインストールして、ほとんどのコマンドが "command not found"になったときの解決法

$
0
0

はじめに

新しいMacbook Proを買ったので、早速nodeの設定をしようと思いました。
そこでnodebrewを入れるつもりが、誤ってパッケージ版のnode.jsをインストールしてしまい、ターミナルからほとんどのコマンドが使えない事態になりました。
ex. rm, ls, which など
rmも使えないため、nodeもアンインストールできなくなる事態になりました。

原因

簡潔に書くと、nodeをインストールしたことにより、複数ファイルにパスが書かれてしまっているため、コマンドが使えなくなっていることがいえます。

nodeをパッケージ版でインストールした人ですとおそらく
/usr/local/bin/node -v
/usr/bin/vi ~/.bash_profile
などは通るのではないでしょうか。
試してみて、実行されるかどうか確認してみてください。もし実行されれば、筆者と同じ状況なので、以下の解決策が有効かもしれません。

解決策

~/.zshrcsource $HOME/.bashrcを追加してください。

よくわからないようであれば、以下コピペしてお使いください。
/usr/bin/vi ~/.zshrc //vimでファイルを弄る
source $HOME/.bashrc //追加

しかし

本来であればパスは一つに集約されるのが理想です。
筆者は上記の目的(nodeをアンインストール)を果たした後は、再度 ~/.zshrcの内容を削除しています。
あくまでも暫定的処置であることをご理解ください。

パッケージ版nodeをmacからアンインストールする。

$
0
0

はじめに

nodebrewをインストールするつもりが、pkg版nodeをインストールしてしまったため、削除したくなりました。

解決法

以下のコマンドで、まずnodeの存在を確認。

terminal
User$ /usr/local/bin/node -v
v12.14.1

バージョンが表示されたので、やはりパッケージ版がインストールされてしまっていることがわかります。
以下のコマンドを続けて打ってください。

terminal
User$ sudo rm-rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}

これでもう一度、冒頭で確認したコマンドを打ってみます。

terminal
User$ /usr/local/bin/node -v-bash: /usr/local/bin/node: No such file or directory

きちんとアンインストールできました。

削除したnode_modulesを再インストールする

$
0
0

削除したnode_modulesフォルダを再インストールしたいが、可能であるか?

結論
package.jsonにモジュールが記載されているので、再インストールは可能である。

入れたいプロジェクトフォルダ上でnpm installコマンドを打つことで、新しくnode_moduleフォルダを作成することができる。

*自分はPCを移行する際に、node_modulesフォルダだけ移行に時間がかかったので先に削除した。
新しいPC上で、再度インストールすることにした。時間の短縮です。

docker上nestjsとmysqlを接続する

$
0
0

docker上でnestjsとmysqlを接続する

table of contents

  1. nestjsのアプリケーションを作成する
  2. localhostで立ち上げる
  3. nestjsのアプリケーションをdockerniseする
  4. docker-containerでmysqlを利用する
  5. typeormを用いてapplicationを接続する

0. 環境

  • docker
  • nestjs

1. nestjsのアプリケーションを作成する

nest g をコマンドを用いてアプリケーションを作成するため、@nest/cliをインストールします。

$npm i -g @nestjs/cli

成功すると、

$nest --version6.12.9

でインストールされたバージョンが表示されます。

コマンドが利用できる状況になったところで新規のアプリケーションを作成します。

$nest g application my-app

おそらくこんな感じでログが表示されます。

CREATE /my-app/.prettierrc (51 bytes)
CREATE /my-app/README.md (3370 bytes)
CREATE /my-app/nest-cli.json (64 bytes)
CREATE /my-app/package.json (1689 bytes)
CREATE /my-app/tsconfig.build.json (97 bytes)
CREATE /my-app/tsconfig.json (336 bytes)
CREATE /my-app/tslint.json (426 bytes)
CREATE /my-app/src/app.controller.spec.ts (617 bytes)
CREATE /my-app/src/app.controller.ts (274 bytes)
CREATE /my-app/src/app.module.ts (249 bytes)
CREATE /my-app/src/app.service.ts (142 bytes)
CREATE /my-app/src/main.ts (208 bytes)
CREATE /my-app/test/app.e2e-spec.ts (630 bytes)
CREATE /my-app/test/jest-e2e.json (183 bytes)

一応プロジェクトができているかの確認も込めて、現状のディレクトリ構成を確認

$ tree -L 1
.
├── README.md
├── nest-cli.json
├── package.json
├── src
├── test
├── tsconfig.build.json
├── tsconfig.json
└── tslint.json

2. localhostで立ち上げる

1.で作成されたアプリケーションを用いて、依存packageのインストール、local serverの立ち上げを実施します。

プロジェクトディレクトリに移動して、packageのインストールを実施します。

$ cd my-app
$ npm install

次にlocalhostを立ち上げます。

$npm run start:dev
12:52:27 AM - Starting compilation in watch mode...


12:52:35 AM - Found 0 errors. Watching for file changes.
[Nest] 51819   - 2020-01-27 12:52:36 AM   [NestFactory] Starting Nest application...
[Nest] 51819   - 2020-01-27 12:52:37 AM   [InstanceLoader] AppModule dependencies initialized +19ms
[Nest] 51819   - 2020-01-27 12:52:37 AM   [RoutesResolver] AppController {/}: +5ms
[Nest] 51819   - 2020-01-27 12:52:37 AM   [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 51819   - 2020-01-27 12:52:37 AM   [NestApplication] Nest application successfully started +3ms

localhost:3000にアクセスすると以下のページが表示されてると思います。

localhost 3000.png

次にdockerniseします。

3. nestjsのアプリケーションをdockerniseする

最初にdokcer-compose fileとDockerfileを追加します。

$touch docker-compose.yml
$touch Dockerfile

特段初期装備レベルなので説明は割愛します。

docker-compose.yml
version:'3.7'services:api-server:build:.tty:truerestart:alwaysports:-'3000:3000'volumes:-type:bindsource:.target:/api-server
FROM node:13.7RUN npm i -g @nestjs/cli

WORKDIR /api-serverCOPY package*.json /api-server/RUN npm i
CMD [ "npm", "run", "start:dev"]

上記の対応で問題ないのですが、一応nodejsのdockerniseにあるので.dockerignoreファイルも追加します。

$touch .dockerignore
node_modules
dist

上記完成するとディレクトリは以下のようになります。

$tree -L 1
.
├── Dockerfile
├── README.md
├── dist
├── docker-compose.yml
├── nest-cli.json
├── node_modules
├── package-lock.json
├── package.json
├── src
├── test
├── tsconfig.build.json
├── tsconfig.json
└── tslint.json

次にdocker-composeでdocker-container内のnest serverを立ち上げます。

$docker-compose up --build

localhost:3000にアクセスすると以下のページが表示されてると思います。

localhost 3000.png

上記のように表示されたら、mysqlのdocker-containerを立ち上げます。

4. docker-containerでmysqlを利用する

docker-containerを追加するため、MySQLを追加していきます。

現状のdocker-compose.ymlはこの通り。

docker-compose.yml(旧)
version: '3.7'
services:
  api-server:
    build: .
    tty: true
    restart: always
    ports:
      - '3000:3000'
    volumes:
      - type: bind
        source: .
        target: /api-server

あたらしく、serviceにmysqlを追加します。(5.X系統なのはまだtypeormが新しいものに対応していなかったからorz)

docker-compose.yml(新)
version: '3.7'
services:
  api-server:
    container_name: api-server
    build: .
    tty: true
    restart: always
    ports:
      - '3000:3000'
    volumes:
      - type: bind
        source: .
        target: /api-server
#ここから追記
    depends_on:
      - db-server
  db-server:
    container_name: db-server
    image: mysql:5.7.29
    restart: always
    ports:
      - '3306:3306'
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: develop
      MYSQL_USER: develop
      MYSQL_PASSWORD: password
#ここまで追記

そして、立ち上げてdocker-container2つがたちあがるか確認します。

$docker ps -aCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
64ec79216641        my-app_api-server   "docker-entrypoint.s…"   3 minutes ago       Up 3 minutes        0.0.0.0:3000->3000/tcp              api-server
0bfe9c3bc2e1        mysql:5.7.29        "docker-entrypoint.s…"   3 minutes ago       Up 3 minutes        0.0.0.0:3306->3306/tcp, 33060/tcp   db-server

で上記2つのcontainerがたちあがっていることを確認します。
次は、container同士を接続します。

5. typeormを用いてapplicationを接続する

実施するのはこの章のため、公式ドキュメントで対応したい方はこっちの方がいいと思います。

やることは一緒で、
1. TypeORMのinstall
2. TypeORMの有効化

を実施していきます。

5-1. TypeORMのinstall

依存関係のmoduleをinstallします。

$npm install--save @nestjs/typeorm typeorm mysql

5-2. TypeORMの有効化

src/app.module.tsを編集して、TypeORMを有効化していきます。

現状のapp.module.tsは以下です。

src/app.module.ts
import{Module}from'@nestjs/common';import{AppController}from'./app.controller';import{AppService}from'./app.service';@Module({imports:[],controllers:[AppController],providers:[AppService],})exportclassAppModule{}

ここにTypeOrmの設定を追記します。

src/app.module.ts
import{Module}from'@nestjs/common';import{TypeOrmModule}from'@nestjs/typeorm';##ここを追記import{AppController}from'./app.controller';import{AppService}from'./app.service';@Module({imports:[##ここから追記TypeOrmModule.forRoot({type:'mysql',host:'db-server',port:3306,username:'develop',password:'password',database:'develop',entities:[],synchronize:true,}),##ここまで追記],controllers:[AppController],providers:[AppService],})exportclassAppModule{}

これでnestjsとmysqlのdockerniseは終わりです。
個人的にはentity系は毎回いらっとするのでいつかちゃんと書きます。

れぽ

【初学者向け】CodeSandboxでReact学習用の開発環境をサクっと作ろう【教える側にもオススメ】

$
0
0

本記事想定読者

  • React初学者で、これから勉強を始めたいと思っている方 。
  • 初学者にReactを教える立場の方。

前書き

最近Reactを初学者の方に教える機会が多かったです。

初学者、と言ってもどの程度『初学』なのか、具体的には下記の通りです。

何らかのプログラミング言語に触れた事はある
  →条件分岐や繰り返し等のプログラミングの基礎概念は把握出来ている
Windows以外のOSを触った事が無い
node.jsの存在を知らない
  →Reactの存在も知らない

数人に教えた時点で気づきましたが、教える際に一番難儀した部分は開発環境の構築でした。

というのも、Unix系のOS自体に触れた事が無い方にとって、npmを用いたインストールだったり、node.jsを利用した開発サーバーを構築する時点でハードルがかなり高くなってしまっている事に気づきました。

CodeSandbox

そんな中でCodeSandboxに出会い、学習に利用して頂いた所、上述の諸問題が解決し、非常にスムーズに学習を進めて頂く事が出来ましたので、この記事で改めてCodeSandboxの魅力、利用方法をお伝えさせて頂こうと思います。

CodeSandboxとは

Web上で利用するブラウザIDE、いわゆる統合開発環境で、利用出来る主な言語はjavascript(node.js)です。

統合開発環境という言葉に聞き覚えが無い方は、シンプルにeclipseやNetBeans(エディタ + 実行環境)の事を指していると思って頂いて差し支えありません。

Reactの開発環境をサクっとつくってみる

https://codesandbox.io/にアクセス。
・画面中央のCreate sandboxボタンを押下。
Screenshot_1.png

・画面中央のメニューにて、「Create Sandbox」「React」を選択。

Screenshot_2.png

Reactのソース(jsx)と確認用のブラウザが表示されました!
 試しにソースの一部分を変えて、保存してみましょう。

Screenshot_3.png

↓↓↓

Screenshot_4.png

・改修結果が反映されましたね!
 たったこれだけでReact開発環境の構築が出来てしまいました。

便利な点

ブラウザだけがあればよい

CodeSandboxはWebアプリケーションである為、ブラウザ一つあれば利用出来てしまいます。
その性質上、ブラウザソフトウェアさえインストールされていれば、基本的にはOS(Windows、Mac等)を問わずに動いてくれます。

基本無料で、会員登録すら不要

利用に関して、執筆時点(2020/01/26)では、ほぼ全ての機能を完全に無料で利用できます。
有償プランも存在し、プライベートプロジェクトの無制限作成等が可能になる様です。
 →プラン説明(英語)

実装結果がすぐさまパブリックなURLにデプロイされる

上記にもある通り、実装の結果が自動生成されたURLにすぐさまデプロイされます。
Screenshot_5.png

このURLはグローバルにホスティングされているため、画像左赤枠のURLさえ共有出来れば、改修の結果を自分の環境のみならず、他の環境でも確認してもらう事が可能です。

Screenshot_7.png

ソースの共有が簡単に出来る

改修中のエディタ画面すらもWebページですので、URLでの共有が可能です。
Screenshot_8.png

 →改修内容が正しいか確認して貰う為に、ソース改修中のプロジェクトのURLをそのまま講師に共有(教えて貰う側)
 →サンプルとなるプロジェクトのURLを生徒に共有し、実際にソースを触って貰って感覚を掴んでもらう(教える側)

  等の利用が可能です。
  個人的にはとても重宝している機能です。

Vue.js等、React以外のメジャーフレームワークも選択可能

利用出来るテンプレートはReactに限らず、VueやAngularと言った他のメジャーなフロントエンドフレームワークの選択も可能です。
(画像例 : Vue.js)
Screenshot_9.png
Screenshot_10.png

あとがき

以上、簡単にCodeSandboxを紹介させて頂きました。

GitHubとの連携や、自由にnpmのパッケージを追加、利用出来る等、他にも様々な機能がサポートされております。

学習用途に限らず、フロントエンド開発の様々なシーンで活かせる可能性を秘めておりますので、興味を持たれた方は是非一度お試し下さい。

それでは良いWebフロントエンドライフを!

Viewing all 9016 articles
Browse latest View live