20210605のNode.jsに関する記事は4件です。

[メモ]Ruby・Rails環境構築

はじめに 完全自分用の記事になります といえども、わりとシンプルにまとめてますので、この通りにすれば、まずこけることはないかなと思います 環境構築 Homebrewのインストール terminal % cd # ホームディレクトリに移動 % pwd # ホームディレクトリにいるかどうか確認 % /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # コマンドを実行 処理に時間がかかる可能性のある操作ですので、慌てずに待つこと パスワードを求められたら自身のPCのパスワードを入れる 以下のようになればOK :terminal % brew -v Homebrew 2.5.1 # 数字は異なる場合があります。 以下のコマンドで最新 terminal % brew update Homebrewの権限を変更する terminal sudo chown -R `whoami`:admin /usr/local/bin Rubyのインストール Rubyの土台となる、rbenvとruby-buildを、Homebrewを用いてインストールします。以下のコマンドを実行。 terminal % brew install rbenv ruby-build rbenvはインストールされた。 PCにおけるどこの場所からも使用できるようにするため、以下のコマンドを実行。 terminal echo 'eval "$(rbenv init -)"' >> ~/.zshrc 設定ファイルであるzshrcを修正したので、以下のコマンドでzshrcを再読み込みし、変更を反映。 terminal % source ~/.zshrc readlineのインストール ターミナルのirb上で日本語入力を可能にする設定を行うために、以下のコマンドでインストール。 terminal % brew install readline rbenvの時と同様に、以下のコマンドでreadlineをどこからでも使用できるようにする。 terminal % brew link readline --force Rubyのインストール terminal % RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)" % rbenv install 2.6.5 使用のバージョンを以下で指定 terminal % rbenv global 2.6.5 Rubyのバージョンを指定できたので、以下のコマンドでrbenvを読み込み、変更を反映。 terminal % rbenv rehash ちゃんとインストールされたか確認! terminal % ruby -v MySQLの準備 terminal % brew install mysql@5.6 MySQLは本来であればPC再起動のたびに起動し直す必要があるが、それは面倒であるため、自動で起動するようにしておく。 terminal % mkdir ~/Library/LaunchAgents % ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents % launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist rbenvやreadlineの時と同様に、どこからでもMySQLを操作するためのコマンドmysqlを実行できるようにしておく。 terminal % echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc # mysqlのコマンドを実行できるようにする設定 % source ~/.zshrc # 設定を読み込むコマンド % which mysql # mysqlのコマンドが打てるか確認する # 以下のように表示されれば成功 /usr/local/opt/mysql@5.6/bin/mysql shade-mini-infoを用意 terminal % brew install shared-mime-info Railsの準備 Rubyの拡張機能(gem)を管理するためのbundler(バンドラー)をインストール terminal % gem install bundler --version='2.1.4' Railsをインストールします。 terminal % gem install rails --version='6.0.0' 一応確認っと、、 terminal % rails -v Rails 6.0.0 # 「Rails」のあとに続く数字は変わる可能性があります Node.jsのインストール Railsを動かすためにはNode.jsが必要となり、それをHomebrewを用いてインストールします。 terminal % brew install node@14 この時、最後にWarning: node@14 14.15.3 is already installed and up-to-dateと表示されても問題はない。 あとはパスを通して完了 terminal % echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.zshrc % source ~/.zshrc 一応確認っと。 terminal % node -v v14.15.3 # 数値は異なる場合があります yarnをインストール yarnは、プログラム同士の関係性を管理する役割などで用いられるもので,Node.jsを利用する際に、プログラム同士の関係性を管理する役割などを担う。 terminal % brew install yarn % yarn -v #確認 おわりに 完全自分用でした。 間違い等はご指定くださると幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NPM ライブラリ i18next を仕込んでおけば凡庸なアプリもみんなが多言語化してくれるという話

凡庸なアプリの事例 現在9カ国語に対応: 現在6カ国語に対応: やったこと GitHub の README.md は怪しげな英語で記述しておいた Electron アプリなので Electron Apps に掲載してもらった Discord の Electron チャンネルにも参加 Product Hunt で宣伝 OSDN で宣伝 審査がザルで通りやすいと言われる Microsoft Store へ出品 アプリに i18next を仕込んでお手軽ローカライズ機能を追加しておいた @sekitata_1214 さんの記事に感謝 ロケールファイルには誰にでも理解出来る JSON 形式を採用した src/locales/ja.json { "About": "バージョン情報", "Services": "サービス", "Hide": "非表示にする", "Hide Others": "その他を非表示にする", "Show All": "すべて表示", "Quit": "終了" } アプリ起動時などにソースの各所でロケール設定している箇所を一つの関数、一つのファイルに書き出し、機能追加しやすいようにした src/setLocales.ts import i18next from 'i18next'; import en from './locales/en.json'; import ja from './locales/ja.json'; export const setLocales = (locale: string): void => { i18next.init({ lng: locale, fallbackLng: 'en', resources: { en: { translation: en }, ja: { translation: ja }, }, }); }; テンプレートとして共通語(?)である英語のロケールファイルも作成しておいた src/locales/en.json { "About": "Version Information", "Services": "Services", "Hide": "Hide", "Hide Others": "Hide Others", "Show All": "Show All", "Quit": "Quit" } README.md には Contribution エントリを設けて怪しげな英語でプルリクのお願いを記述しておいた README.md ## :beers: Contribution I need more locale files. When you have translated the menu into your language, could you please send me the locale file as a [pull request](https://github.com/sprout2000/leafview/pulls)? 1. Create `{your_LANG}.json` in `src/locales`. 2. Then import the locale file into `src/setLocales.ts` as follows: ```diff import en from './locales/en.json'; import ja from './locales/ja.json'; + import cs from './locales/cs.json'; export const setLocales = (locale: string): void => { i18next.init({ lng: locale, fallbackLng: 'en', resources: { en: { translation: en }, ja: { translation: ja }, + cs: { translation: cs }, }, }); }; (当たり前だけど)プルリクエストを送ってくれた方へは感謝のメッセージをコミットし、README.md でもクレジットすることにした 結論 DeepLさん、スゲー!(えっ、そこ?)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【BTP】Node.jsとPostgreSQLでシンプルなアプリケーションを作成(3)Approuter編

はじめに この記事は、Node.jsとPostgreSQLでシンプルなアプリケーションを作成するシリーズの3回目です。 この記事のゴール Approuterを使用して認証を行う Approuterを介さないサービスへのアクセスをブロックする CRUD処理に必要な権限をチェックする ステップ Approuterを使用して認証を行う Approuterを介さないサービスへのアクセスをブロックする CRUD処理に必要な権限をチェックする 1. Approuterを使用して認証を行う 1.1. Approuterモジュールを追加 プロジェクト直下にapprouterというフォルダを追加します。 node-postgres-sample  └ approuter  └ srv  └ mta.yaml 1.2. approuter/package.jsonの設定 approuterフォルダにpackage.jsonを追加します。 approuter/package.json { "name": "approuter", "version": "1.0.0", "scripts": { "start": "node node_modules/@sap/approuter/approuter.js" }, "dependencies": { "@sap/approuter": "^10.4.0" } } 1.3. approuter/xs-app.jsonの設定 approuterフォルダにxs-app.jsonファイルを追加します。このファイルによって入ってきたリクエストをNode.jsアプリのURLにルーティングします。 approuter/xs-app.json { "welcomeFile": "index.html", "authenticationMethod": "route", "routes": [ { "source": "^/node-pg/", "target": "/", "authenticationType": "xsuaa", "destination": "srv-api" } ] } welcolmeFile(index.html)は、approuter/resourcesの中に作成します。これはApprouterのURLを叩いたときに最初に表示されるページです。 approuter/resources/index.html <body> Welcome to App Router default web page <br> </body> 1.4 mta.yamlの調整 mta.yamlにApprouterモジュールを追加します。 mta.yaml modules: - name: node-postgres-sample-approuter type: approuter.nodejs path: approuter parameters: disk-quota: 512M memory: 512M requires: - name: node-postgres-sample-uaa - name: srv-api group: destinations properties: name: srv-api url: "~{srv-url}" forwardAuthToken: true resourcesセクションにXSUAAサービスを追加します。 - name: node-postgres-sample-uaa type: org.cloudfoundry.managed-service parameters: path: ./xs-security.json service-plan: application service: xsuaa プロジェクト直下にxs-security.jsonを作成します。これはXSUAAの設定用のファイルです。 xs-security.json { "xsappname": "node-postgres-sample", "tenant-mode": "dedicated", "scopes": [ { "name": "uaa.user", "description": "UAA" } ], "role-templates": [ { "name": "Token_Exchange", "description": "UAA", "scope-references": [ "uaa.user" ] } ] } 1.5 ビルド、デプロイ ビルド、デプロイした後、シークレットモードでApprouterのURLにアクセスします。 ログインを求められるので、XSUAAを使用した認証が有効になっていることが確認できます。 ログインすると、Welcomeページが表示されます。 URLの末尾を/node-pg/productsとすると、productsのデータにアクセスできます。これでNode.jsのアプリケーションにリクエストが転送されていることが確認できました。 もともとのサービスのURLをシークレットモードで開いてみます。 こちらは認証なしでアクセスできます。現状、直接サービスのURLを叩いた場合は認証されていないユーザでもアクセスできる状態です。 2. Approuterを介さないサービスへのアクセスをブロックする 2.1. Dependencyを追加 srvフォルダに移動し、以下のDependencyを追加します。 npm i @sap/xssec passport @sap/xssec: HTTPヘッダに渡されたアクセストークンを検証したり、認証情報にアクセスするためのモジュール passport: 認証を行うためのミドルウェア。任意のStrategyを利用して認証を行うことができる package.jsonのdependenciesセクションは以下のようになります。 srv/package.json "dependencies": { "@sap/xsenv": "^3.1.0", "@sap/xssec": "^3.2.1", "body-parser": "^1.19.0", "express": "^4.17.1", "passport": "^0.4.1", "pg-promise": "^10.10.2" } 2.2. passportを使用したトークンチェックの追加 server.jsに以下のコードを追加します。 srv/server.js 'use strict'; const express = require('express') const bodyParser = require('body-parser') //追加-------------------------------------------------- const passport = require('passport') const JWTStrategy = require('@sap/xssec').JWTStrategy const xsenv = require('@sap/xsenv') //------------------------------------------------------ const dbConn = require('./db-conn') const dbOp = require('./db-op') var _db = undefined const app = express() app.use(bodyParser.json()) //追加-------------------------------------------------- passport.use(new JWTStrategy(xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa)); app.use(passport.initialize()); app.use(passport.authenticate('JWT', { session: false })); //------------------------------------------------------ リクエストヘッダにJWTトークンが存在し、認証が成功すると以下のオブジェクトにアクセス可能になります。これらのオブジェクトを追加の権限チェック(たとえば、ユーザが指定したスコープを持っているか)に使うことができます。(@sap/xssecの"Usage with Passport Strategy"セクションを参照) オブジェクト 説明 request.user ユーザのID(id)、名前(name)、メールアドレス(emails)などの情報 request.authInfo Security Contextのオブジェクト。利用可能なメソッドについてはリンク先の"API Description"のセクションを参照。 request.tokenInfo TokenInfoオブジェクト(※)。トークンの情報にアクセスできる。 ※ドキュメントのTokenInfoオブジェクトに関するリンクが切れていたので、持っているメソッドについて確認した。結果は以下の通り。 reset isDecoded isValid getErrorObject getTokenValue getHeader getPayload getExpirationDate getIssuedAt getIssuer getSubject getAudiencesArray getUserId getZoneId getClientId isTokenIssuedByXSUAA verify 2.3 mta.yamlの調整 node-postgres-sample-srvのrequresセクションにnode-postgres-sample-uaaを追加します。 mta.yaml - name: node-postgres-sample-srv type: nodejs path: srv provides: - name: srv-api properties: srv-url: ${default-url} build-parameters: ignore: ["node_modules/"] requires: - name: cap-posgre-sample-db - name: node-postgres-sample-uaa 2.3 ビルド、デプロイ ビルド、デプロイした後、シークレットモードでサービスのURLにアクセスします。 Unauthorizedとなりました。サービスのURLを直接叩いた場合はアクセスできなくなっています。 Approuterを経由した場合はアクセス可能です。 3. CRUD処理に必要な権限をチェックする サービスで実行可能な操作は以下の通りです。各操作に対し、必要な権限(スコープ)を設定します。 URI HTTPメソッド 操作 必要な権限(スコープ) /  GET  "Hello!"を表示  -  /products  GET  すべてのproductを表示  DisplayまたはUpdate  /products/:id  GET  指定したidのproductを表示  DisplayまたはUpdate  /products  POST  productを登録  Update  /products  PUT  productを変更  Update  /products  DELETE  productを削除  Update  3.2. xs-security.jsonを更新 xs-security.jsonでDisplay、およびUpdate用のスコープとそれらのスコープを持つロールテンプレートを定義します。 xs-security.json { "xsappname": "node-postgres-sample", "tenant-mode": "dedicated", "scopes": [ { "name": "$XSAPPNAME.Display", "description": "Display Products" }, { "name": "$XSAPPNAME.Update", "description": "Update Products" } ], "role-templates": [ { "name": "Viewer", "description": "View Products", "scope-references": [ "$XSAPPNAME.Display" ] }, { "name": "Manager", "description": "Maintain Products", "scope-references": [ "$XSAPPNAME.Display", "$XSAPPNAME.Update" ] } ] } 3.3. approuter/xs-app.jsonを更新 特定の操作(URIとHTTPメソッドの組み合わせ)に対して、ユーザが所定のスコープを持っているかどうかをApprouterでチェックします。 approuter/xs-app.json { "welcomeFile": "index.html", "authenticationMethod": "route", "routes": [ { "source": "^/node-pg/products(.*)$", "target": "/products$1", "authenticationType": "xsuaa", "destination": "srv-api", "scope": { "GET": ["$XSAPPNAME.Display", "$XSAPPNAME.Update"], "default": "$XSAPPNAME.Update" } }, { "source": "^/node-pg/", "target": "/", "authenticationType": "xsuaa", "destination": "srv-api" } ] } productsで始まるURIとそれ以外で権限チェックの有無が変わるので、ルートを分けています。 productsで始まるURIの場合(1つ目のルート) GETリクエストに対して、ユーザが$XSAPPNAME.Displayまたは$XSAPPNAME.Updateのスコープを持っているかチェックする・・・scope.GET それ以外のHTTPメソッドに対しては$XSAPPNAME.Updateのスコープを持っているかをチェックする・・・scope.default products始まりでないURIの場合(2つ目のルート) スコープのチェックは行わない 3.3 ビルド、デプロイ ビルド、デプロイした後、シークレットモードでApprouterのURLにアクセスします。 /node-pg/productsにアクセスすると、Forbiddenとなります。 /node-pg/products/1も同様です。 /node-pg/にはアクセスできます。 3.4 ユーザにロールを割り当て デプロイの結果、Approuterのアプリケーションに対して2つのロールテンプレートが作成されています。まずは、Viewerロールを自分のユーザに割り当てます。 Security>Role Collectionsのメニューからロールコレクションを追加します。 ロールコレクションにViewerロールと、自分のユーザを割り当てます。 ロールを割り当てた後、シークレットウインドウからログインしなおし、/node-pg/productsにアクセスしてみます。 /node-pg/products/1にもアクセスできます。 POST、PUT、DELETEについてはブラウザからテストができないため、UIを作成してから確認することにします。 まとめ この記事では、以下を実施しました。 Approuterを使用して認証を行う Approuterを介さないサービスへのアクセスをブロックする CRUD処理に必要な権限をチェックする 次回はサービスを使用したUIを作成したいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsからPostgreSQLへコネクションプールを使った接続

はじめに 前回の記事(LinuxサーバーにPostgreSQL導入~外部サーバー接続まで)で、Linuxサーバに導入したPostgreSQLにNode.jsから接続するところまでをまとめました。 次はコネクションプールを理解して、それを使った接続方法をまとめていきます。 コネクションプールとは クエリを発行する際、使用するコネクションを保持しておいて使いまわす仕組みのことです。 例えば、DBから値を取ってきて返却するAPIサーバがあるとします。 悪い例だと以下のようになります。 (1回目のAPI)   ➀DBへ接続   ➁SELECT文とかでデータ取得   ➂コネクション切断 (2回目のAPI)   ➃DBへ接続   ➄SELECT文とかでデータ取得   ➅コネクション切断 これだと毎回、接続/切断を繰り返しているので、多少なりともオーバーヘッドが生じます。 できれば、最初につないだコネクションを使いまわしたいですよね? そこで登場する仕組みがコネクションプールです。 これによって、先ほどの悪い例が以下のように変わります。 (1回目のAPI)   ➀DBへ接続   ➁SELECT文とかでデータ取得   ➂コネクションをプールに返却 (2回目のAPI)   ➃プールからコネクションをチェックアウト   ➄SELECT文とかでデータ取得   ➅コネクションをプールに返却 実際にやってみる Node.jsでAPIサーバを立てて、実際に動作を確認してみます。 pool.js const { Pool } = require("pg"); // 接続先文字列 const connectionString = 'postgres://user:pass@DBのアドレス:ポート/DB名'; // DB情報をもったプールを生成 const pool = new Pool({ connectionString: connectionString, max: 2 // 保持するコネクション数 }); module.exports = pool; index.js const express = require('express'); const app = express(); const pool = require('./pool.js'); app.get('/', async function(req, res, next) { console.log("============================="); // 取得前 console.log('取得前 totalCount: ', pool.totalCount); console.log('取得前 idleCount: ', pool.idleCount); console.log('取得前 waitingCount: ', pool.waitingCount); // コネクション取得 const connect = await pool.connect(); // 取得後 console.log('取得後 totalCount: ', pool.totalCount); console.log('取得後 idleCount: ', pool.idleCount); console.log('取得後 waitingCount: ', pool.waitingCount); // クエリ発行 const result = await connect.query('SELECT NOW()'); // コネクション返却 connect.release(); // 返却後 console.log('返却後 totalCount: ', pool.totalCount); console.log('返却後 idleCount: ', pool.idleCount); console.log('返却後 waitingCount: ', pool.waitingCount); res.status(200).json({data: result.rows}) }); app.listen(3001, () => console.log(`listening on port 3001!`)); 保持するコネクション数はとりあえず2としています。 設定しない場合はデフォルトで10となります。 ログ出力している値はこのようになります。 ・totalCount : プールに存在するコネクション数 ・idleCount : プールに存在する使用されていないコネクション数 ・waitingCount : コネクションの取得待ち数 実行結果は以下になります。 >node index.js listening on port 3001! ============================= 取得前 totalCount: 0 取得前 idleCount: 0 取得前 waitingCount: 0 取得後 totalCount: 1 取得後 idleCount: 0 取得後 waitingCount: 0 返却後 totalCount: 1 返却後 idleCount: 1 返却後 waitingCount: 0 コネクションを取得した後は忘れずに、返却しなければなりません。 それを忘れてしまうと、永遠に解決しないコネクション取得待ちが発生してしまいます。 試しに、発生させてみましょう。 コネクション返却をコメントアウトします。 index.js // コネクション返却 // connect.release(); // 返却後 // console.log('返却後 totalCount: ', pool.totalCount); // console.log('返却後 idleCount: ', pool.idleCount); // console.log('返却後 waitingCount: ', pool.waitingCount); 4回続けて、APIを呼び出した結果が以下です。 >node index.js listening on port 3001! ============================= 取得前 totalCount: 0 取得前 idleCount: 0 取得前 waitingCount: 0 取得後 totalCount: 1 取得後 idleCount: 0 取得後 waitingCount: 0 ============================= 取得前 totalCount: 1 取得前 idleCount: 0 取得前 waitingCount: 0 取得後 totalCount: 2 取得後 idleCount: 0 取得後 waitingCount: 0 ============================= 取得前 totalCount: 2 取得前 idleCount: 0 取得前 waitingCount: 0 ============================= 取得前 totalCount: 2 取得前 idleCount: 0 取得前 waitingCount: 1 3回目は取得することができていないです。 4回目でwaitingCountの数が1(3回目の呼び出しのこと)となっていることが分かります。 プール内の最大コネクション数 コネクションプールの値をどのくらいにすればいいかわからない場合は、waitingCountを監視して、チューニングすることが推奨されます。 前述しましたが、コネクションの数はmaxプロパティで指定します。 指定しなかった場合、デフォルトの値は10となります。 const pool = new Pool({ connectionString: connectionString, max: 2 // 保持するコネクション数 }); コネクションの接続時間数 1回目のコネクションを2回目もしっかり使っているのか確認してみたく、実際にDBのログを見て確認してみることにしました。 まずはDBの設定を下記の通り編集します。 /var/lib/pgsql/13/data/postgresql.conf log_connections = on log_disconnections = on log_statement = 'all' # none, ddl, mod, all これで、DB接続、切断、SQLの情報がログに出力されるようになります。 試しに2回APIを呼び出してみます。 /var/lib/pgsql/13/data/log/postgresql-XXX.log 2021-06-05 01:57:53.460 JST [unknown] [8788] LOG: connection received: host=XXX.XXX.XXX.XXX port=XXXX 2021-06-05 01:57:53.719 JST first_db [8788] LOG: connection authorized: user=test database=first_db 2021-06-05 01:57:53.739 JST first_db [8788] LOG: statement: SELECT NOW() 2021-06-05 01:58:03.766 JST first_db [8788] LOG: disconnection: session time: 0:00:10.307 user=test database=first_db host=XXX.XXX.XXX.XXX port=XXXX 2021-06-05 01:58:20.797 JST [unknown] [8792] LOG: connection received: host=XXX.XXX.XXX.XXX port=XXXX 2021-06-05 01:58:20.909 JST first_db [8792] LOG: connection authorized: user=test database=first_db 2021-06-05 01:58:20.921 JST first_db [8792] LOG: statement: SELECT NOW() 2021-06-05 01:58:30.933 JST first_db [8792] LOG: disconnection: session time: 0:00:10.136 user=test database=first_db host=XXX.XXX.XXX.XXX port=XXXX disconnectionされてますね… よく見るとどちらも約10秒後に切断されています。 調査してみると、デフォルトで10秒後に切断されるそうです。 ということは10秒に1回アクセスのあるアプリでそのままプールを使っても、何の意味がないことになります。 const pool = new Pool({ connectionString: connectionString, max: 2, // 保持するコネクション数 idleTimeoutMillis: 600000 // 自動切断時間(ミリ秒) }); これで1分はコネクションが保持されることになります。 また、この値を0にすれば、自動切断自体を無効化することができます。 DBのログから確認する作業と、確認する間隔を10秒以上開けていなかったら気付けなかったかもしれない…
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む