- 投稿日:2021-04-19T22:09:47+09:00
dotenvはダブルクォーテーションで囲まないと改行がエスケープされる
.env ファイルに「\」を含む文字列を記載する場合注意が必要 シェル変数にセットする時と同様に、クォーテーションの有無でエスケープの有無が変わります。 ⇒改行を含む文字列(証明書など)を.envファイルにセットする場合は「ダブルクォーテーション」で囲いましょう。 証明書(.PEM)ファイルをシングルクォーテーションで囲うと、改行がなくなり検証で落ちます。 Error: error:0909006C:PEM routines:get_name:no start line # クォーテーションなし ⇒ 改行を含まない文字列(エスケープされる) value1=abc\ndef #abc\\ndef # シングルクォーテーション ⇒ 改行を含まない文字列(エスケープされる) value2='abc\ndef' #abc\\ndef # ダブルクォーテーション ⇒ 改行を含む文字列 value3="abc\ndef" #abc\ndef
- 投稿日:2021-04-19T19:26:26+09:00
nvmを使用してNode.jsのバージョンを変更する方法
初めに 今回は、CentOSに導入したNode.jsのバージョンをnvmで変更します。 nvmとは、Node.jsのバージョンマネージャーです。 私の環境のNode.jsはバージョン12.21.0ですが、バージョン14.16.1に変更したいと思います。 # node -v v12.21.0 手順は、nvm公式のGitHubを参考にしました。 ↓ https://github.com/nvm-sh/nvm では、始めます。 環境 ■OS CentOS 8 nvmのインストール curlコマンドを使用してインストールスクリプトを実行します。 curlコマンドは、データの転送やダウンロードができるコマンドです。 # curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 13527 100 13527 0 0 40621 0 --:--:-- --:--:-- --:--:-- 40621 => Downloading nvm as script to '/root/.nvm' => Appending nvm source string to /root/.bashrc => Appending bash_completion source string to /root/.bashrc => You currently have modules installed globally with `npm`. These will no => longer be linked to the active version of Node when you install a new node => with `nvm`; and they may (depending on how you construct your `$PATH`) => override the binaries of modules installed with `nvm`: /usr/local/lib └── n@7.1.0 => If you wish to uninstall them at a later point (or re-install them under your => `nvm` Nodes), you can remove them from the system Node as follows: $ nvm use system $ npm uninstall -g a_module => Close and reopen your terminal to start using nvm or run the following to use it now: export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion インストールスクリプトを実行後、「~/.bashrc」に設定が入っていることを確認します。 # vi ~/.bashrc 〜略〜 export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion 設定が入っていることを確認しました。 sourceコマンドで、「~/.bashrc」を実行します。 # source ~/.bashrc Node.jsバージョンの切替 インストール可能なNode.jsを表示します。 # nvm list-remote ~略~ v14.16.1 (Latest LTS: Fermium) v15.0.0 v15.0.1 v15.1.0 v15.2.0 v15.2.1 v15.3.0 v15.4.0 v15.5.0 v15.5.1 v15.6.0 v15.7.0 v15.8.0 v15.9.0 v15.10.0 v15.11.0 v15.12.0 v15.13.0 v15.14.0 それでは、インストールを行います。 # nvm install 14.16.1 Downloading and installing node v14.16.1... Downloading https://nodejs.org/dist/v14.16.1/node-v14.16.1-linux-x64.tar.xz... ################################################################################################## 100.0% Computing checksum with sha256sum Checksums matched! Now using node v14.16.1 (npm v6.14.12) Creating default alias: default -> 14.16.1 (-> v14.16.1) インストールが成功し、デフォルトがバージョン14.16.1になっていることを確認できました。 最後にNode.jsのバージョンを確認します。 # node -v v14.16.1 以上になります。 ありがとうございました。
- 投稿日:2021-04-19T17:20:28+09:00
Firebase の REST API 向けエラークラスが優れているので、パクった。
Firebase Functions の HttpsError こいつです!! こいつを、 Firebase 非依存プロジェクトでも使いたいと思ったわけです。 どうして優れているの すげー使いやすいAPIなんですよね。。。 app.get("/items/:id", async (req, res, next) => { const item = await Item.find(id); if (item == null) { // ⭐ こいつ! throw new HttpsError("not-found", "Invalid Item ID", detail); } ... }); 第1引数が 404 といったhttpエラー番号でなく、直感的なtext.もちろん型定義されているのでサジェストされる。 第2引数に概要を書くことができる 第3引数に詳細をつめることができる シンプルかつ洗練されたインターフェース。 express との連携例 こんな感じの middleware 定義して運用してるんですけどね。 middlewares.ts export class APIMiddlewares { static errorHandler( err: Error | HttpsError, req: Request, res: Response, next: NextFunction ) { // ⭐ HttpsError はサービスで想定されたエラーとして処理 if (err.constructor.name === HttpsError.name) { const httpsError = err as HttpsError; res.status(httpsError.httpErrorCode.status).send({ status: httpsError.httpErrorCode.canonicalName, msg: httpsError.message, }); ... return; } // ⭐ HttpsError でないなら想定外のエラーとして処理 res.status(500).send({ status: "INTERNAL_SERVER_ERROR" }); ... } } 想定されたものかどうか、色分けができる(そしてそれが優れたインターフェースのErrorを介してできる)だけでもめっちゃ便利。 パクってみた https://github.com/firebase/firebase-functions/blob/98bf467aa436f7e27969a8ad3887abe0345f66d3/src/providers/https.ts 冒頭にも参照したソースから丸パクリ。 MITライセンスであることに注意 http-error.ts export type FunctionsErrorCode = | "ok" | "cancelled" | "unknown" | "invalid-argument" | "deadline-exceeded" | "not-found" | "already-exists" | "permission-denied" | "resource-exhausted" | "failed-precondition" | "aborted" | "out-of-range" | "unimplemented" | "internal" | "unavailable" | "data-loss" | "unauthenticated"; /** @hidden */ export type CanonicalErrorCodeName = | "OK" | "CANCELLED" | "UNKNOWN" | "INVALID_ARGUMENT" | "DEADLINE_EXCEEDED" | "NOT_FOUND" | "ALREADY_EXISTS" | "PERMISSION_DENIED" | "UNAUTHENTICATED" | "RESOURCE_EXHAUSTED" | "FAILED_PRECONDITION" | "ABORTED" | "OUT_OF_RANGE" | "UNIMPLEMENTED" | "INTERNAL" | "UNAVAILABLE" | "DATA_LOSS"; /** @hidden */ interface HttpErrorCode { canonicalName: CanonicalErrorCodeName; status: number; } /** * Standard error codes and HTTP statuses for different ways a request can fail, * as defined by: * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto * * This map is used primarily to convert from a client error code string to * to the HTTP format error code string and status, and make sure it's in the * supported set. */ const errorCodeMap: { [name in FunctionsErrorCode]: HttpErrorCode } = { ok: { canonicalName: "OK", status: 200 }, cancelled: { canonicalName: "CANCELLED", status: 499 }, unknown: { canonicalName: "UNKNOWN", status: 500 }, "invalid-argument": { canonicalName: "INVALID_ARGUMENT", status: 400 }, "deadline-exceeded": { canonicalName: "DEADLINE_EXCEEDED", status: 504 }, "not-found": { canonicalName: "NOT_FOUND", status: 404 }, "already-exists": { canonicalName: "ALREADY_EXISTS", status: 409 }, "permission-denied": { canonicalName: "PERMISSION_DENIED", status: 403 }, unauthenticated: { canonicalName: "UNAUTHENTICATED", status: 401 }, "resource-exhausted": { canonicalName: "RESOURCE_EXHAUSTED", status: 429 }, "failed-precondition": { canonicalName: "FAILED_PRECONDITION", status: 400 }, aborted: { canonicalName: "ABORTED", status: 409 }, "out-of-range": { canonicalName: "OUT_OF_RANGE", status: 400 }, unimplemented: { canonicalName: "UNIMPLEMENTED", status: 501 }, internal: { canonicalName: "INTERNAL", status: 500 }, unavailable: { canonicalName: "UNAVAILABLE", status: 503 }, "data-loss": { canonicalName: "DATA_LOSS", status: 500 }, }; /** @hidden */ interface HttpErrorWireFormat { details?: unknown; message: string; status: CanonicalErrorCodeName; } /** * An explicit error that can be thrown from a handler to send an error to the * client that called the function. */ export class HttpsError extends Error { /** * A standard error code that will be returned to the client. This also * determines the HTTP status code of the response, as defined in code.proto. */ public readonly code: FunctionsErrorCode; /** * Extra data to be converted to JSON and included in the error response. */ public readonly details: unknown; /** * A wire format representation of a provided error code. * * @hidden */ public readonly httpErrorCode: HttpErrorCode; constructor(code: FunctionsErrorCode, message: string, details?: unknown) { super(message); // A sanity check for non-TypeScript consumers. if (code in errorCodeMap === false) { throw new Error(`Unknown error code: ${code}.`); } this.code = code; this.details = details; this.httpErrorCode = errorCodeMap[code]; } /** @hidden */ public toJSON(): HttpErrorWireFormat { const { details, httpErrorCode: { canonicalName: status }, message, } = this; return { ...(details === undefined ? {} : { details }), message, status, }; } } ありがとう Google, ありがとう Firebase.
- 投稿日:2021-04-19T15:41:29+09:00
ZoomのVideo SDK登録とサンプルアプリケーション実行
概要 Zoomがビデオ通話機能を切り出してSDKとして提供を始めたので、サンプルアプリケーションを動かすところまで実施。 Developer登録 公式ページから、Buy Nowを選択。 支払いプランを選択。 Pay As You Goだと、10,000時間まで無料で使用できて、それ以降は1分あたり0.48ドルの支払いになる。 もしくは年間134,400ドル払って1分あたり0.41ドルの支払いだが、お試しならばPay As You Goだろう。 ここで登録するメールアドレスは、通常のZoomの招待や参加などには使えないようなので、普段使っているZoom用アカウントがある場合は他のメールアドレスを用意したほうが良さそう。 そこからは、名前や住所、クレジットカード登録などを行ってアカウントを作成する。 サンプルアプリケーション実行 アプリケーションの登録 App Marketplaceから、右上のDevelop -> Build Appを選択。 App TypeはVideo SDKなので、Video SDKのCreateを押下。 アプリケーション名などは任意の名前を入力する。 ダウンロード 登録が終わったら、Downloadをクリックしてサンプルアプリケーションのダウンロード実施。 依存パッケージのインストール ダウンロードしたzipを任意のディレクトリに解凍し、Sampleディレクトリに移動した後に依存パッケージのinstallを実行。 $ npm install その後、@zoomusをnode_modulesにコピーする。 $ cp -R @zoomus node_modules SdkKeyの登録 src/config/dev.tsの中身を書き換える。 import { getExploreName } from '../utils/platform'; export const devConfig = { sdkKey: '', sdkSecret: '', topic: '', name: `${getExploreName()}-${Math.floor(Math.random() * 1000)}`, password: '', signature: '', }; 以下画面のApp Credentialsタブを押下して、SDK KeyとSDK Credentialを上記コードのsdkKeyとsdkSecretに貼り付ける。 また、topicは任意の文字を設定する。(testtopicとか) 実行 Sampleディレクトリで以下コマンドを実行。 $ npm run start 勝手にブラウザが開いて、少し待つとアプリケーションが開始される。 Audio, video and shareをクリックすると、audio, video, screen sharingが行える。
- 投稿日:2021-04-19T00:01:54+09:00
ExpressとMongoDBで即席RestAPI
やること Node.jsのMVCフレームワークExpressとNoSQLのMongoDBを使って、ユーザ情報の登録・変更・削除ができる簡単なRestAPIを作る。 リクエストやレスポンスはjsonで(NoSQLとの相性抜群)。 MongoDBはDockerコンテナ上で起動させる。 超適当ですが全体像はこんな感じ。 準備 HomebrewとNode.jsを入れておく。 Docker上でMongoDB起動 MongoDBをDockerコンテナ上で起動する。 直に入れてもいいが、せっかくなのでDocker使ってみる。 # Dockerインストール $ brew install docker --cask # MongoDBイメージ取得 $ docker pull mongo # コンテナを作成して起動 # ホストOS側は27018ポート、コンテナ側は27017ポート(MongoDBのデフォルト)を使用 $ docker run -p 27018:27017 --name mongo-test -d mongo Node.jsプロジェクト作成 ベースとなるプロジェクトを作って環境構築する。 Docker起動中かと思うので、別のターミナルを起動する。 # ディレクトリ作成 $ mkdir test-app # 初期化処理(問合せ無しでpackage.json作成) $ npm init -y # 必要nodeモジュール(expressとmongoDB操作用モジュール)をインストール $ npm install express mongoose Model作成 スキーマとなるUserModelを定義する。 # ディレクトリ作成 $ mkdir server $ cd server $ mkdir models # Model定義JavaScript作成 $ touch models/user.js user.jsをVSCodeとかで開いて編集する。 肝心のModelは今回、ユニークなID、名前、年齢の3つのフィールドを持たせる。 user.js const mongoose = require('mongoose'); const Schema = mongoose.Schema; const userSchema = new Schema({ id: { type: String, required: true, unique: true }, name: { type: String }, age: { type: Number} }); module.exports = mongoose.model('users', userSchema); Router&Controller作成 Modelを利用して実際にHTTPリクエストが来た際にどう振舞うかを定義するRouterと、全体を制御するControllerを定義する。 今回は小規模なので、分けずにまとめてserver.jsとする。 # ディレクトリ移動 $ cd .. # Router&Controller定義JavaScript作成 $ touch server.js server.jsをVSCodeとかで開いて編集する。 httpリクエストは3001番ポートで待ち受けるとする。 コードを少なくしたかったので、登録以外はエラー処理を端折ってます。 server.js const express = require('express'); const mongoose = require('mongoose'); const User = require('./models/user'); // DB接続 mongoose.connect('mongodb://localhost:27018/sampledb'); const app = express(); app.use(express.json({extended: true})); // ユーザ情報登録 app.post('/users', (req, res) => { const user = new User(req.body); user.save((err, addedUser) => { if (err) return res.status(400).json({ errorMessage: 'failed to add the user.' }); res.send(addedUser); }); }); // ユーザ情報取得(全件) app.get('/users', (req, res) => { User.find({}, (err, users) => { res.send(users); }); }); // ユーザ情報取得(ID指定) app.get('/users/:id', (req, res) => { const params = req.params.id; User.find({id: params}, (err, user) => { res.send(user); }); }); // ユーザ情報削除(全件) app.delete('/users', (req, res) => { User.remove({}, err => { res.send(true); }); }); // ユーザ情報削除(ID指定) app.delete('/users/:id', (req, res) => { const params = req.params.id; User.remove({id: params}, (err, deletedUser) => { res.send(deletedUser); }); }); // ポート3001番でlisten app.listen(3001, () => { console.log(`Server up on 3001`); }); サーバ起動 $ node server.js 動作確認(curlコマンド) curlコマンドでユーザ情報の登録・取得・削除をやってみる。 _idと__vはMongoDBが自動付与するフィールドで、それぞれオブジェクトID、バージョン管理用として使われる。 # Allen登録 $ curl -X POST -H 'Content-Type:application/json' -d '{"id":"001","name":"Allen","age":20}' http://localhost:3001/users # Allen登録結果 {"_id":"607bce198776e444bd55422b","id":"001","name":"Allen","age":20,"__v":0}% # Yuki登録 $ curl -X POST -H 'Content-Type:application/json' -d '{"id":"002","name":"Yuki","age":26}' http://localhost:3001/users # Yuki登録結果 {"_id":"607c3bf502ac68477a41ee10","id":"002","name":"Yuki","age":26,"__v":0}% # 全件取得 $ curl -X GET http://localhost:3001/users # 取得結果 [{"_id":"607bce198776e444bd55422b","id":"001","name":"Allen","age":20,"__v":0},{"_id":"607c3bf502ac68477a41ee10","id":"002","name":"Yuki","age":26,"__v":0}]% # IDを指定して取得 $ curl -X GET http://localhost:3001/users/001 # 取得結果 [{"_id":"607bce198776e444bd55422b","id":"001","name":"Allen","age":20,"__v":0}]% # IDを指定して削除 $ curl -X DELETE http://localhost:3001/users/001 # 削除結果 {"n":1,"ok":1,"deletedCount":1}% # 削除確認のため全件取得 $ curl -X GET http://localhost:3001/users # 残ってるのはYukiだけ [{"_id":"607c3bf502ac68477a41ee10","id":"002","name":"Yuki","age":26,"__v":0}]% # 全件削除 $ curl -X DELETE http://localhost:3001/users # 削除結果 true% # 削除確認のため全件取得 $ curl -X GET http://localhost:3001/users # 何も残っていない []% 動作確認(API Tester) curlコマンドだと視覚的に分かりづらいので、Chrome拡張のTalend API Testerを使って、視覚的に確認する。 こちらでは登録と全件取得だけ確認してみる。 まずは登録。 この後、同じ要領でYukiも登録したとして、次は全件取得。 こんな感じで確認できた。 もちろん、METHODを変えることでDELETEも可能。 これで一通り、DBを操作する簡単なRestAPIを作成できた。 応用としてやってみたいこと フロントエンドをReactとかで作りたい。 今回DockerはMongoDBでしか使わなかったが、Node.jsそのものからDocker上で環境構築したい。VSCodeでDockerコンテナ上のワークスペースも編集できるらしい(参考)。 TypeScriptで書けるようにしたい(参考)。 参考サイト 初級JavascriptフルスタックエンジニアのためのReactとExpressの同時開発チュートリアル MongoDB(Mongoose)上級者への道 curlコマンドを使った操作