20200317のNode.jsに関する記事は11件です。

Node.js/Expressのボディパーサーの仕様確認(エコーサーバー(echo-server))

背景/目的

クライアント側からのリクエストを各種パーサーがどのようなオブジェクトや配列にしてくれるのを確認することを目的にリクエストの情報をまとめてレスポンスにそのまま返却するようなエコーサーバーを作ってみました。
その他、HTTPクライアントのリクエストが想定通りになっているかを確認するのにも使えると思うので記録として残しておきます。

仕様

リクエストの以下の情報をレスポンスJSONにまとめて返す。

  • ヘッダー情報
  • パス
  • HTTPメソッド(Verb)
  • クエリパラメータ
  • リクエストボディ(ある場合のみ)

準備

npmとかnode.jsはインストールされている前提。

mkdir echo-server
cd echo-server
npm init -f
npm install -y express
touch index.js

ソースコード

↑で作ったindex.js

index.js
'use strict';

const express = require('express');
const app = express();
const bodyParser = require('body-parser');

// Set body parser
app.use(bodyParser.urlencoded({ extended: true, type: 'application/x-www-form-urlencoded' }));
app.use(bodyParser.json({ type: 'application/json' }));
app.use(bodyParser.raw({ type: '*/*' }));

// All request process.
app.use((req, res, next) => {
  res.json({
    path: req.originalUrl.indexOf('?') === -1 ? req.originalUrl : req.originalUrl.substring(0, req.originalUrl.indexOf('?')),
    method: req.method,
    query: req.query,
    headers: req.headers,
    body: req.body ? (Object.keys(req.body).length ? req.body : undefined) : undefined
  });
});

// Create HTTP server.
const http = require('http');
const server = http.createServer(app);
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
server.listen(port);
server.on('error', (err) => { console.error(err); });
server.on('listening', () => { console.log('listening on ' + port); });

実行

node index.js

実行結果確認

GETリクエスト

以下のようなクエリパラメータ名にすると配列にしてくれるみたいです。

request
$ curl -s -X GET -H "Authorization: Bearer dummmy" "http://localhost:3000/foo/bar?page=1&size=10&qarr[]=q1&qarr[]=q2&qarr[]=q3"  | jq .
response
{
  "path": "/foo/bar",
  "method": "GET",
  "query": {
    "page": "1",
    "size": "10",
    "qarr": [
      "q1",
      "q2",
      "q3"
    ]
  },
  "headers": {
    "host": "localhost:3000",
    "user-agent": "curl/7.68.0",
    "accept": "*/*",
    "authorization": "Bearer dummmy"
  }
}

POST(Json)

これはJSONなのでそのまま出る感じなのでさらっと。

request
$ curl -s -X POST -H "Authorization: Bearer dummmy" -H "Content-Type: application/json" -d '{"foo":"foo-value", "bar": "bar-value", "arr": [{"sub": "sub1"},{"sub": "
sub2"}]}' "http://localhost:3000/hogehoge"  | jq .
response
{
  "path": "/hogehoge",
  "method": "POST",
  "query": {},
  "headers": {
    "host": "localhost:3000",
    "user-agent": "curl/7.68.0",
    "accept": "*/*",
    "authorization": "Bearer dummmy",
    "content-type": "application/json",
    "content-length": "81"
  },
  "body": {
    "foo": "foo-value",
    "bar": "bar-value",
    "arr": [
      {
        "sub": "sub1"
      },
      {
        "sub": "sub2"
      }
    ]
  }
}

POST(フォーム)

Web画面(HTML)のフォーム送信(Content-Type: application/x-www-form-urlencoded)のケースです。
これは、 obj[sub]=obj-sub-value という名前の付け方をすると、以下のようなjsonにしてくれます。

"obj": {
    "sub": "sub-value"
}

ネストしたオブジェクトの場合は nested[sub1][sub2]=sub1-sub2 という名前の付け方をすると

"nested": {
  "sub1": {
    "sub2": "sub1-sub2"
  }
}

つまり、各フォーム要素のname属性を意識してつければ、例えばBackendのデータベースがMongoDBのようなオブジェクト形式だった場合にサーバー側でFormをオブジェクトの形に整形(組み立てる)処理を書かなくても済むわけです。もちろんJSonスキーマだったり、バリデーションのチェックは必要ですが。。

request
$ curl -s -X POST -H "Authorization: Bearer dummmy" -H "Content-Type: application/x-www-form-urlencoded" -d 'foo=foo-valoue&bar=bar-value&arr=arr1&arr=arr2&arr=arr3&
obj[sub]=sub-value&nested[sub1][sub2]=sub1-sub2' "http://localhost:3000/foo/bar" | jq .
response
{
  "path": "/foo/bar",
  "method": "POST",
  "query": {},
  "headers": {
    "host": "localhost:3000",
    "user-agent": "curl/7.68.0",
    "accept": "*/*",
    "authorization": "Bearer dummmy",
    "content-type": "application/x-www-form-urlencoded",
    "content-length": "103"
  },
  "body": {
    "foo": "foo-valoue",
    "bar": "bar-value",
    "arr": [
      "arr1",
      "arr2",
      "arr3"
    ],
    "obj": {
      "sub": "sub-value"
    },
    "nested": {
      "sub1": {
        "sub2": "sub1-sub2"
      }
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker-composeを使ってExpressの環境構築

Expressの環境構築

git clone https://github.com/Old-rever-brave/Express-Docker

cd Express-Docker

docker-compose up --build

npm install

npm start

http://localhost:3000/

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

JavascriptのPromiseを解説します

User.query({where: {name:nm}, andWhere: {password: pw}}).fetch()

これの戻り値が Promise オブジェクトが返ります。

(new Promise()).then(成功時の関数).catch(失敗時の関数)

みたいな

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

Node.jsでDeprecationWarningを出さずにencodeとdecode

環境

$ node --version
v12.16.1

経緯

Buffer を使って、encodeしたかった時にDeprecationWarningが出てしまったため対処
encode自体は問題なくできるが、nodejsのドキュメント的には対応した方が良さそう

test.js
const before = 'hogehoge'  // これをbase64でencodeしたい
const buffer = new Buffor(before)
const after = buffer.toString('base64')
console.log(after)

$ node test.js
aG9nZWhvZ2U=
(node:631) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.

やったこと

new Bufferの書き方を修正した

test.js
const before = 'hogehoge'
const after = Buffer.from(before).toString("base64");
console.log(after)

↓ 無事エラーが消えた!

$ node test.js
aG9nZWhvZ2U=

ちなみに、decodeはこちら

test.js
const after = 'aG9nZWhvZ2U'
const before = Buffer.from(after, 'base64').toString()
console.log(before)

$ node test.js
hogehoge

参考

https://stackoverflow.com/questions/23097928/node-js-throws-btoa-is-not-defined-error

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

ログインなしでコードを共有できるサイトつくた

成果物

https://code.itsumen.com/

リポジトリ

https://github.com/yuzuru2/code_site

開発環境

  • ubuntu 18.04
  • docker
  • docker-compose

使用ライブラリ周り

フロントエンド

  • parcel
  • bootstrap
  • highlight.js
  • react
  • nginx(静的ファイルを配信)

バックエンド

  • typescript
  • nodejs
  • pm2

データベース

  • mongodb

成果物を使うケース

  • ちょろっと書いたコードを誰かに見せたい時

UI

ホーム画面

無題.png

コード閲覧画面

無題.png

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

コード共有サイト作成 docker react node.js mongodb

成果物

https://code.itsumen.com/

リポジトリ

https://github.com/yuzuru2/code_site

開発環境

  • ubuntu 18.04
  • docker
  • docker-compose

使用ライブラリ周り

フロントエンド

  • parcel
  • bootstrap
  • highlight.js
  • react
  • nginx(静的ファイルを配信)

バックエンド

  • typescript
  • nodejs
  • pm2

データベース

  • mongodb

成果物を使うケース

  • ちょろっと書いたコードを誰かに見せたい時

UI

ホーム画面

無題.png

コード閲覧画面

無題.png

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

コード共有サイトつくた

成果物

https://code.itsumen.com/

リポジトリ

https://github.com/yuzuru2/code_site

開発環境

  • ubuntu 18.04
  • docker
  • docker-compose

使用ライブラリ周り

フロントエンド

  • parcel
  • bootstrap
  • highlight.js
  • react
  • nginx(静的ファイルを配信)

バックエンド

  • typescript
  • nodejs
  • pm2

データベース

  • mongodb

成果物を使うケース

  • ちょろっと書いたコードを誰かに見せたい時

UI

ホーム画面

無題.png

コード閲覧画面

無題.png

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

NFCシールを活用して自動打刻ツールを(個人的に)作ってみた話

はじめに

今回はNFCシールを使用して会社の自動打刻システムを、完全に自分用で作ります!

要件定義

なぜつくるか?

現在、弊社の勤怠は、エクセルで管理されています。
実際の打刻フローとしては、

  1. 出社したら出社時刻をエクセル開いて手動で打刻
  2. 保存
  3. 退社するときに退社時刻をエクセル開いて手動で打刻
  4. 保存

。。。
毎日エクセルポチポチするの面倒すぎる!!!!!!!!!

作業自体も面倒なのに、何日か打刻を忘れるとまあ面倒臭いことになります。

せっかくIT企業にいるんだからいろいろスマートにやりたい...
ということで、今回の自動打刻システムの開発を決意しました。

どう作るか?

今回開発する自動打刻システムでは、NFCシールを活用していきます。

処理の流れとしては、

  1. NFCシールにスマホをかざして専用のWEBサイトを表示する
  2. WEBサイトから自動打刻システムにリクエストを投げる
  3. 打刻する

といった感じにしようかと思います。

NFCシールにスマホかざすのとリクエストを投げるところにWEBサイト表示をはさんでいるのは、
意図しない打刻を防ぐためです。

クライアント側で打刻される時刻の確認、出社なのか退社なのかの選択できた方が、
手順は増えますが確実かなあということでワンクッションはさみました。

また、現状では個人用なのでユーザーの識別は行いません。

どう使うか?

想定される使用フローは下記のとおりです。

  1. 出社したらデスクのどこかしらに貼ったNFCシールにスマホをかざす
  2. 表示されるWEBサイトで打刻時間を確認、出社ボタンを押す
  3. (退社時も同じ)

かなりスマート...!(な気がする)

使用技術

インフラはAWSの各サービスを利用します。

メイン処理の部分にはLambda、エクセルファイル(勤怠管理)はS3に保存します。

サーバーサイドにnode.js、WEBフロントエンドにはVue.jsを使用しつつ、HTTP通信はaxiosを使用します。

なぜNFCシールを使うか?

ただ使ってみたかった。

実は今回の開発、個人的にNFCシールを使ったアプリを作ってみたかったので、NFCシールありきで考えていました。

スマホかざすだけで打刻できるのステキじゃん...

なぜnode.jsか?

一番の理由は、Lambdaがnode.jsで書けるからです笑

他にも
java, ruby, pythonなどなど、いろんな言語で書くことができます。

フロントエンド開発に興味があり、日頃からJavaScriptを勉強しているので、
サーバーサイドもJavaScriptで書こう!ということでnode.jsを選びました。

なぜLambdaか?

Lambdaとは、AWSが提供するサーバレスアーキテクチャを構築するためのサービスです。

通常は、EC2インスタンスは常時存在し、アプリケーションも常時起動されているのですが、Lambdaはリクエストが送られてきたときのみインスタンスを生成→アプリケーションを実行→インスタンスを破棄という挙動をします。

今回作成するアプリは常時起動している必要もないので、コストを抑える意味でもLambdaが適しているのではないかと考えました。

なぜVue.jsか?

個人的に使い慣れているのでフロントはVue.jsで書きます。

と言っても、現在時刻を表示するのと、ボタンを二つ配置するだけなので全く難しいことはしません笑

強いて言えば、ローディングのアニメーションを作り込むくらいでしょうか...。

HTTP通信はaxiosを使用します。

設計

アーキテクチャ図

アーキテクチャの全体像としては、下記の通りです。

Image from Gyazo

構成は至ってシンプルで、
HTTPリクエストをAPI Gatewayで受け付け、Lambdaに投げます。

勤怠を管理しているエクセルはS3においておき、Lambdaからそのエクセルファイルに書き込みをしていく感じです。

処理が完了すると、処理結果をSuccessかFailでクライアントに通知します。

アプリケーションの実装

自動打刻システム(node.js)実装

コードの全貌は下記のGitHubリポジトリを御覧ください。

GitHub リポジトリ

エクセルファイルの操作には、「xlsx-populate」というライブラリを使用しました。

最初は「xlsx」というライブラリを使って実装していましたが、このライブラリだと処理をして、保存するとマクロや書式が無効化された状態になってしまうのでつかえず...。

個人的にはドキュメントも「xlsx-populate」のほうが読みやすかったです!

処理としてはファイルを読み込んで、シートを指定して、セルを指定して値を書き込み、保存しているだけです。

弊社の勤怠表は月ごとにシートが分かれているので、処理の頭でDateオブジェクトを生成して、得られた各値でシートや記入するセルを判定しています。

また、弊社は30分ごとに勤務時間として打刻できるので、打刻する時刻を30分単位に変換する関数を用意しています。

今後もっと本格的に運用していくことになったら、このあたりで拡張の余地がありますね。

実装で苦労したのは非同期処理とAWS S3からファイルを取得して、書き込んだものをアップロードし直す処理のところ。

const params = {
  Bucket: 'バケット名',
  Key: 'キー'
}

s3.getObject(params, (err, data) => {
}

上記のように記述すれば、指定されたバケットのオブジェクト(ファイル)を取得できて、data変数に格納されます。

また、アップロードするときは、

const params = {
  Bucket: 'バケット名',
  Key: 'キー',
  Body: 'アップロードしたいファイル'
}

s3.putObject(params, (err, data) => {
})

でアップロードできます!

ここがnode.jsの情報がなかなか転がってなくて苦労しました。

取得も書き込みも注意点としては、取ってきたり送信するためには、データ形式に気をつけなければなりません。

今回僕は、これらの処理の前後にエクセルファイルをバッファーに変換する処理をはさみ、変換したものをparams変数のBodyとしています。

クライアントサイド(vue.js)実装

クライアントサイド(WEB)はVue.jsで作りました。

最終的には静的サイトとしてビルドして、Netlifyでホスティングします。

こちらは特に難しいことはしていません。

UIはVuetifyを使ったので適当に作った割には整っています。

スクショですが、下記のようになりました。

Image from Gyazo

打刻すると、vue-loading-templateを使用したアニメーションが流れて、レスポンスが帰ってくるとアラートが表示されます。

Image from Gyazo

(若干左によってるのはスクショが下手だからです...笑)

インフラ環境構築

構築したもの


いよいよインフラの構築に入ります。

今回は、メインのAPIをLambdaで動かします。

勤怠表(エクセルファイル)はS3にアップロードしておき、Lambdaから読み取り、書き込みを行います。

HTTPリクエストの受け口として、APIGatewayを配置します。

ここに想定されるリクエストがとんできたら、それをトリガーにLambda関数が動く仕組みにしていきます。

また、クライアントサイド(WEBアプリ)はNetlifyという静的サイトのホスティングサービスを利用します。

Netlifyに関しては後日別記事で言及します。

ハマったポイント


私はインフラ超初心者なので、インフラ構築でかなりつまづきました...。

LambdaからS3のファイルをとってこれない

作成したLambdaに正しくロールを付与していなかったため、アクセス権限 is 何の状態が1時間くらい続きました...。


Lambda関数(メインAPI)が非同期処理になっていて肝心の処理を行う前にLambdaが終了してしまっていた

今回使用した「xlsx-populate」は非同期処理をすることが前提のライブラリです。

恥ずかしながら、node.jsだけでなく非同期処理の知識も乏しく、Lambdaはエラーなく終了するのに肝心の処理が実行できてない...。

という状態で約5日間潰しました。

エラー箇所の切り出しが下手だったなあと反省しています。

いろんな記事や書籍を読み漁りつつ、async awaitを駆使してなんとか解決しました。

CROS

実際にクライアントサイドからリクエストを投げる時にはまりました。

今までなーんとなくしか理解してなかったですが、これを機にしっかり学べました。

CROSに関しては別記事でまとめます。

実際につかってみた


実際の動画がこちら

明日から出社と退社が楽しみになりそう。

(ちょっと処理は遅いですが)個人的にほぼノンストレスに打刻できるようになったので満足です。

ただ、本当にきちんと勤怠をつけるためには小難しい会社のルールがあるみたいなので、そのうちきちんとしたものも作りたいです。

今のところはほぼ毎日きっちり定時に退社しているので細かい調整はそんなに必要なさそう...だと思ってます笑

おわりに

NFCさいこう!!!!!たのしい!!!!!

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

2020年から始めるAzure Cosmos DB - JavaScript SDK (SQL API)を見てみる (Part.1)

th.jpeg

この記事について

本記事は、2020年3月6日 (米国時間) にて、Azure Cosmos DB に新しく Free Tier (無償利用枠) が登場したことに伴い、改めて Azure Cosmos DB を色々と触っていく試みの 3 回目です。
今回は、前回記事 にて作成した CRUD アプリ内で使用している Microsoft Azure Cosmos JavaScript SDK について見ていきたいと思います。

対象読者

  • Azure Cosmos DB について学習したい方
  • Node.js で Azure Cosmos DB への CRUD 操作を行いたい方
  • Microsoft Azure Cosmos JavaScript SDK の動作について理解したい方

Microsoft Azure Cosmos JavaScript SDK

実際に、Microsoft Docs の内容を元に、JavaScript SDK (SQL API) の中身を見ていきます。
今回は Azure Cosmos DB に接続する際に生成する、CosmosClient について確認します。

CosmosClient

TypeDoc の記載は、以下の通りです。

Provides a client-side logical representation of the Azure Cosmos DB database account.
This client is used to configure and execute requests in the Azure Cosmos DB database service.
Azure Cosmos DBデータベースアカウントのクライアント側の論理表現を提供します。
このクライアントは、Azure Cosmos DBデータベースサービスで要求を構成および実行するために使用されます。

const client: CosmosClient = new CosmosClient({ endpoint, key });

上にある通り、endpointkey を使用してインスタンスを作成しています。

  • endpoint: Azure Cosmos アカウント URI、
  • key: Azure Cosmos アカウントのプライマリキー or セカンダリキー

どのコンストラクタが動いているのかを確認すると

(alias) new CosmosClient(options: CosmosClientOptions): CosmosClient (+1 overload)
import CosmosClient

とあり、CosmosClient(options: CosmosClientOptions)が動いているようです。
中身を確認してみます。

Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/CosmosClient.ts
/**
 * Creates a new {@link CosmosClient} object. See {@link CosmosClientOptions} for more details on what options you can use.
 * @param options bag of options - require at least endpoint and auth to be configured
 */
constructor(options: CosmosClientOptions); // tslint:disable-line:unified-signatures

あれ、1行しかない?と思ったらすぐその下にconstructor(optionsOrConnectionString: string | CosmosClientOptions)があったので焦りました。。
このコンストラクタは長いので、部分ごとに見ていきます。

コンストラクタ処理 (1)

if (typeof optionsOrConnectionString === "string") {
  optionsOrConnectionString = parseConnectionString(optionsOrConnectionString);
}

これは単純に、コンストラクタの引数がstringCosmosClientOptionsかを判別しています。
引数が string の場合は、if 文の中で接続文字列を使って CosmosClientOptions を生成し、 optionsOrConnectionString に代入しています。
コンストラクタの引数が string と CosmosClientOptions と異なっているので、お決まりな感じの処理です。
コンストラクタの引数の型が違うからと言い、内容がほとんど重複するようなコンストラクタをしっかり分けて書く人をたまに見かけます。このコードは「そんな無駄なことしなくていいよ」と教えてくれるいい例ですね。

コンストラクタ処理 (2)

optionsOrConnectionString.connectionPolicy = Object.assign(
  {},
  defaultConnectionPolicy,
  optionsOrConnectionString.connectionPolicy
);

ここでは、CosmosClientOptions の connectionPolicyに値を設定しています。
その前に、CosmosClientOptions って何者だ?、という疑問があるので、先にこちらを見てみます。

Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/CosmosClientOptions.ts
export interface CosmosClientOptions {
  endpoint: string;
  key?: string;
  resourceTokens?: { [resourcePath: string]: string };
  tokenProvider?: TokenProvider;
  permissionFeed?: PermissionDefinition[];
  connectionPolicy?: ConnectionPolicy;
  consistencyLevel?: keyof typeof ConsistencyLevel;
  defaultHeaders?: CosmosHeaders;
  agent?: Agent;
  userAgentSuffix?: string;
  plugins?: PluginConfig[];
}

CosmosClientOptions には 11 個のプロパティが定義されています。どうも CosmosClientOptions の中に endpoint や key をはじめとした Azure Cosmos DB を利用するための各種設定値が格納されるようです。
ちなみに TypeScript では、他の言語ではメンバー変数やフィールドと呼ばれる、名前を持ち、指定された型のデータを保持するものをプロパティといいます。

この CosmosClientOptions のプロパティの 1 つに ConnectionPolicyがありますので、先ほどの処理はこのプロパティの値を設定している部分と理解しました。
ConnectionPolicy は何かというのを確認するために、さらに中身をみてみます。

Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/documents/ConnectionPolicy.ts
export interface ConnectionPolicy {
  connectionMode?: ConnectionMode;
  requestTimeout?: number;
  enableEndpointDiscovery?: boolean;
  preferredLocations?: string[];
  retryOptions?: RetryOptions;
  useMultipleWriteLocations?: boolean;

ConnectionPolicy の中には、connectionModerequestTimeoutなどの接続に関するポリシー設定があるようです。(これ以上、クラスを深くみると大変なので、一旦ここまでにします。)

一旦元に戻って、

(再掲)
optionsOrConnectionString.connectionPolicy = Object.assign(
  {},
  defaultConnectionPolicy,
  optionsOrConnectionString.connectionPolicy
);

をみると、Object.assignを使用しています。
Object.assign() が何かについては、こちら を参照してください。
つまり、CosmosClientOptions の ConnectionPolicy プロパティ内で未定義となっているものについて、デフォルト値を代入している感じです。

コンストラクタ処理 (3)

optionsOrConnectionString.defaultHeaders = optionsOrConnectionString.defaultHeaders || {};
optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.CacheControl] = "no-cache";
optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.Version] = Constants.CurrentVersion;
if (optionsOrConnectionString.consistencyLevel !== undefined) {
  optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.ConsistencyLevel] = optionsOrConnectionString.consistencyLevel;
}

optionsOrConnectionString.defaultHeaders[Constants.HttpHeaders.UserAgent] = getUserAgent(
  optionsOrConnectionString.userAgentSuffix
);

このoptionsOrConnectionString.defaultHeadersは、CosmosHeaders クラスです。
中身を見てみます。

Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/queryExecutionContext/CosmosHeaders.ts
export interface CosmosHeaders {
  [key: string]: string | boolean | number;
}

キー(key) と キーに紐づく値(value) を格納できるようにしています。いわゆる連想配列の部分です。
連想配列をインタフェースで定義するという使い方もあるんですね。勉強になります。

つまり、ここの処理は、HTTP リクエストの各種ヘッダー情報を CosmosHeaders クラス (連想配列) を使って設定しているという感じです。

コンストラクタ処理 (4)

const globalEndpointManager = new GlobalEndpointManager(
  optionsOrConnectionString,
  async (opts: RequestOptions) => this.getDatabaseAccount(opts)
);

新しいGlobalEndpointManagerというヤツが出てきました。
GlobalEndpointManage にある、どのコンストラクタが動いているのかを確認すると

(alias) new GlobalEndpointManager(options: CosmosClientOptions, readDatabaseAccount: (opts: RequestOptions) => Promise<ResourceResponse<DatabaseAccount>>): GlobalEndpointManager
import GlobalEndpointManager

が動いているようです。中身を確認します。

Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/globalEndpointManager.ts
export class GlobalEndpointManager {
  private defaultEndpoint: string;
  public enableEndpointDiscovery: boolean;
  private isRefreshing: boolean;
  private options: CosmosClientOptions;
  private preferredLocations: string[];

  constructor(
    options: CosmosClientOptions,
    private readDatabaseAccount: (opts: RequestOptions) => Promise<ResourceResponse<DatabaseAccount>>
  ) {
    this.options = options;
    this.defaultEndpoint = options.endpoint;
    this.enableEndpointDiscovery = options.connectionPolicy.enableEndpointDiscovery;
    this.isRefreshing = false;
    this.preferredLocations = this.options.connectionPolicy.preferredLocations;
  }
}

ナンダコレ、、、
となったので、TypeDoc を見てみます。

This internal class implements the logic for endpoint management for geo-replicated database accounts.
この内部クラスは、地理的に複製されたデータベースアカウントのエンドポイント管理のロジックを実装します。

ああ、そういうことか! とこの絵を思い出しました。
Azure Cosmos DB で、これ忘れたらダメなヤツやん。。

azure-cosmos-db.png

Microsoft Docs にある Azure Cosmos DB の概要 にも一番最初に

Azure Cosmos DB は、Microsoft によってグローバルに配布されるマルチモデル データベース サービスです。

とあります。
Cosmos DB では、Cosmos アカウントに複数リージョンを関連付けさせることができ、関連付けられたすべてのリージョンにデータがシームレスにレプリケートされるようになっています。これはそれに関連する設定まわりの処理と認識しました。

コンストラクタ処理 (5)

this.clientContext = new ClientContext(optionsOrConnectionString, globalEndpointManager);

クライアントコンテキストがやっと出てきました。
どのコンストラクタが動いているのかを確認すると、

(alias) new ClientContext(cosmosClientOptions: CosmosClientOptions, globalEndpointManager: GlobalEndpointManager): ClientContext
import ClientContext

が動いているようです。

Azure/azure-sdk-for-js/sdk/cosmosdb/cosmos/src/ClientContext.ts
export class ClientContext {
  private readonly sessionContainer: SessionContainer;
  private connectionPolicy: ConnectionPolicy;
  public partitionKeyDefinitionCache: { [containerUrl: string]: any };

  public constructor(
    private cosmosClientOptions: CosmosClientOptions,
    private globalEndpointManager: GlobalEndpointManager
  ) {
    this.connectionPolicy = cosmosClientOptions.connectionPolicy;
    this.sessionContainer = new SessionContainer();
    this.partitionKeyDefinitionCache = {};
  }
}

(クライアントコンテキストについては、説明するまでもないと思いますが) いわゆる、コンテキストの伝播 とか言われているモノです。
ざっくり言うと、クライアントに関連する情報を保持している場所と私は思っています。メソッドの引数にクライアント情報をいちいち与えずに、全部ここを見ましょうよ、的なやつです。(適当)
クライアントコンテキストについて、良い説明となる資料を見つけられなかったので、見つけたら追記したいです。

コンストラクタ処理 (6)

this.databases = new Databases(this, this.clientContext);
this.offers = new Offers(this, this.clientContext);

やっと最後の処理です。
Databasesは、新しいデータベースの作成、およびすべてのデータベースの読み取り/クエリの操作を行うクラスです。
Offersについては、聞きなれない単語だと思います。(筆者もちゃんと理解しているわけではありませんが) REST経由で、SQL APIを使用する際に登場するもののようです。
Offer resource というものがあるんですね。別途、学習を進めたいと思います。

さいごに

今回は、Azure Cosmos DB に JavaScript/Node.js で接続する際に最初に生成される、CosmosClient について、中身を確認してみました。
今回の内容は、前回、実際に CRUD アプリを作成 (前回記事) した時はたったの 1 行で終わってしまった内容です。

普段のアプリ開発では、あまり意識しない世界なのかもしれませんが、実際にコンストラクタの中で何が行われているのかを確認することは、Azure Cosmos DB の仕組みや使用するライブラリへの深い知見を得る ためには必要な事かな、と思いました。

次回は、Databaseクラスについて見ていこうと思います。

関連リンク

前回記事

参考情報

npm

GitHub

Microsoft Docs

developer.mozilla.org

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

mockyでリクエストの内容をレスポンスに反映しようとしてハマったメモ。

やりたいこと

node.jsで動くWebAPIモックサーバーのmockyを使って、

{ "name": "John Smith" }

をPOSTリクエストしたら

{
  "id": 1,
  "name": "John Smith"
}

が返却され、

{ "name": "Taro Yamada" }

をPOSTリクエストしたら

{
  "id": 1,
  "name": "Taro Yamada"
}

が返却されるように、リクエストの内容をレスポンスに反映したかったのだが、つまづいたのでメモを残す。
mockyのインストール方法、基本の使い方はGithubのREADMEを参考にしてください。

結論

基本の使い方は公式のREADMEといいつつ、mocky自体かなりメンテされていないみたいなので少し書き方は今風にしてる箇所もあるが基本的に一緒。

mock.js
const mocky = require('mocky');

mocky.createServer([
  {
    url: '/users',
    method: 'POST',
    res: (req, res, callback) => {
      const params = JSON.parse(req.body);
      callback(null, {
        status: 201,
        body: JSON.stringify(
          {
            "id": 1,
            "name": params.name
          }
        )
      });
    }
  }
]).listen(4321);

JSON.parseでリクエストのjsonをパースしてあげないとうまく変数を抽出できなかったり、JSON.stringifyでjson形式で書いたレスポンスをstring型に変換しないとうまく動作しない。(ここでつまづいた...?)
特にJSON.stringifyを忘れたために、

_http_outgoing.js:670
    throw new ERR_INVALID_ARG_TYPE('first argument',
    ^
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer. Received an instance of Object
    at write_ (_http_outgoing.js:670:11)
    at ServerResponse.write (_http_outgoing.js:638:15)
    at sendRes (/app/node_modules/mocky/lib/mocky.js:166:9)
    at /app/node_modules/mocky/lib/mocky.js:74:10
    at Timeout._onTimeout (/app/mocky.js:16:9)
    at listOnTimeout (internal/timers.js:549:17)
    at processTimers (internal/timers.js:492:7) {
  code: 'ERR_INVALID_ARG_TYPE'
}

というエラーが出続けて泣いた?

動作確認

これでcurlで確認すればうまくいくはず。

$ curl -X POST 'http://localhost:4321/users' -d '{ "name": "John Smith" }'
{"id":1,"name":"John Smith"}

みにくいのでpython -m json.toolをパイプで渡すと見やすくなる。

$ curl -X POST 'http://localhost:4321/users' -d '{ "name": "John Smith" }' | python -m json.tool
{
    "id": 1,
    "name": "John Smith"
}

Reference

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

入門の次のステップに進めないVue.js学習履歴(随時更新)

概要

ドットインストールの「Vue.js入門」を実施してある程度Vue.jsをわかった気になった。
https://dotinstall.com/lessons/basic_vuejs_v2

しかし、実際にリリースされているVue.jsのソースを見るとさっぱりわからなかった。
このため、疑問点と調査経緯を自分のメモ目的でこの記事に残していく。

疑問点

yarn run xxx

package.jsonのscriptsで定義されたxxxを実行する。

"scripts": {
    "xxx": "~~~~~~~~", ←これを実行
    "yyy": "~~~~~~~~",
    :
    :
  },

■yarn runのドキュメントはこちら。
https://classic.yarnpkg.com/ja/docs/cli/run

app.use(nuxt.render)

expressのミドルウェアとしてNuxt.jsを使う。

 const express = require('express')
 const app = express()
  :
 app.use(nuxt.render)
  :

Node.jsの「ミドルウェア」という概念がいまいちわかっていない・・・。

■API: nuxt.render(req, res)
https://ja.nuxtjs.org/api/nuxt-render/

■ExpressのミドルでNuxt.jsを利用
https://www.wakuwakubank.com/posts/666-nuxtjs-express-middle/

module.exports={}

外部(別ファイル)から参照できるようにする。

module.exports = {
  mode: 'xxx',
  router: {
    base: '/yyy/'
  },

上記の定義をしたjsファイルをrequireすると、jsファイルの中身を参照できる。

■module.exportsとは何か、どうもわからなかったので実験してみた〜Node.jsにて外部moduleをrequireする〜
http://karoten512.hatenablog.com/entry/2018/01/28/191928

this.$store.dispatch(~~~)

「$store」はどこにも宣言されていない。
Vuex(ビューックス?)のステートオブジェクトとのこと。
セッションみないたもの??セッションよりは奥が深そう。

■Vuexステート
https://vuex.vuejs.org/ja/guide/state.html

■Vue.js + Vuexでデータが循環する全体像を図解してみた
https://qiita.com/m_mitsuhide/items/f16d988ec491b7800ace

this.$store.dispatch(~~~)その② dispatchについて

「$store.dispatch」でステートのactionを実行(らしい)
actionは非同期処理(らしい)

actionの実行とは何か?

this.$store.dispatch(~~~)その③ actionについて

「$store.dispatch」でステートのactionを実行するとしてactionとは何をするのか?
実態が不明。

\store\index.jsexport const actionsexport const mutationsがあった。
つまり「$store.dispatch」を実行すると、\store\index.jsexport const actionsのような気がしてきた。

async function() {const response = await this.$store.dispatch(~~~)}

asyncで非同期関数。
awaitでpromiseのresolveを実行。
つまり、\store\index.jsのactionを非同期実行した結果がresponseに入る?
※このあたり、理解が曖昧。以下などを見て、なんとなく上記の理解。

 https://qiita.com/niusounds/items/37c1f9b021b62194e077

() => {~~~}

何もない()はJavaScriptで1,2を争う理解できない記述。
catchは例外をキャッチするんだろうな~という何となく想像ができるがその後の() =>は全く想像できない。具体的なソースだとこんな感じ。

const response = await this.$store
          .dispatch('xxx', { 
           key1: valu1,
           key2: value2
          })
          .catch(() => {    ←ここがわからない。
            return false
          })

こちらがわかりやすかった。
https://qiita.com/may88seiji/items/4a49c7c78b55d75d693b

(引数,...)=>{...関数の本体...}

つまり() => {~~~}は引数なしの無名関数を宣言しているだけ。
上記具体例だと、catchが呼ばれたらfalseをreturnする無名関数を実行する、ということか。

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