Node.jsの設計思想・動作から理解し、アプリケーション開発の実践的なスキルを身につける
2023/1に出たばかりの本です。著者の伊藤康太さんは元Yahooのスペシャリスト認定制度の黒帯保持、有識者による一冊。タイトルは「Node.js入門」とありますがNode.js本体の話にとどまらずJavaScriptの文法の話、ExpressによるWebアプリにCLIツールにフロント/バックエンド両方の開発や運用...と、実質最新のフロントエンド・バックエンド全般を概観できる本となっています。384Pなのでそんなに分厚くはないですね。
Node.jsは仕事でもインストールはしてるしたまに使うこともあるのですが、ふんいきでなんとなく使ってる感はずっとあったので、これを機会に体系的に押さえなおそうとじっくり拝読しました。
- Node.jsの設計思想・動作から理解し、アプリケーション開発の実践的なスキルを身につける
- 1 はじめてのNode.js
- 2 JavaScript/Node.jsの文法
- 3 Node.jsとモジュール
- 4 Node.jsにおける非同期処理(フロー制御)
- 5 CLIツールの開発
- 6 ExpressによるREST APIサーバー/Webサーバー
- 7 フロントエンド/バックエンドの開発
- 8 アプリケーションの運用と改善
- まとめ:テーマ一式網羅、最新情報に基づいたNode.js周りを体系的に学べる本
1 はじめてのNode.js
- Chrome内蔵のV8JavaScriptエンジンを使ったJavaScriptランタイム。バックエンドのサーバーやフロントエンドのツールにも使えるように。
- 2009年登場の黎明期を経て、2010年代前半から市民権獲得。
- 非同期のイベント駆動型ランタイム:クライアントからの命令は非同期で実行される。JSは元からブラウザ上で非同期なイベント処理に適していたので、バックエンドでも書きやすい。
- シングルプロセス・シングルスレッドで動作:プロセスが1つなので軽量。
- イベントループとNon-Blicking I/O:ユーザーA,Bからリクエストが飛んできたときに、Aの分が終わるのを待たずにBの分を処理できる。イベントを細かく分割している。無限のイベントループがずっと動いているので、何もしないアイドル状態になりにくい。
同時接続数が大まかに10万人を超えると重くなる「C10K問題」に対し、上の特徴で対応。(ただし近年は他のWebサーバーも問題なくなっている)
2000年代後半のJS復権とともにプラグインやパッケージ管理が必要になってくる→Node.jsが持っていたnpmが解決。 沢山のファイルを完成版では1つのファイルにまとめてバンドル、minifyで最小化するタスクランナーが必要。初期はGruntやgulpなど。SPAフレームワーク群にも必要。→これらのバックにNode.jsが。
- JavaScriptにはモジュール化の仕組みがなかったが、ES6からようやく諸々仕様が策定。古いブラウザでも動くようにトランスパイルするBabelが登場。モジュール化のES moduleが一般化する前にCommonJSの考え方もあり、バンドラーでBrowserifyやWebpackが登場。→これらのツール群もJavaScriptで実装、バックにはやはりNode.jsが。
- SSR: Server Side Rendering SPAベースのアプリでも最初の1回は空のHTMLを返すのでなく、サーバーサイドで描画して表示してクローラーに引っかかるようにしたい。フロントエンドがJS実装ならバックエンドも同じJSでやったほうが良いので、実質Node.jsが必須になる。
- BFF: Backend For Frontend SPAベースの画面に各種APIからいろいろ値を返すなら、複数APIを束ねる役割が必要だねという概念。これもフロントエンドエンジニアにとっての扱いやすさを考えるとNode.jsでやるのが合理的。
最初は歴史を踏まえてNode.jsの役割を語っています。個別には知ってはいた話もあるのですが体系的に解説しているのでありがたい。改めてNode.js登場が2006年頃のJavaScript復権や、その後のフロントエンドの隆盛に大きな役割を果たしたのだなあと思います。
Node Package Managerは最近は略称でなく正式名称がnpmぽいというのは初めて知りました。
なお本書では解説の意図があってコードはJavaScriptになっていますが、複数人が関わるJavaScriptプロジェクトを始める時はもう最初からTypeScriptを推奨となっています。本も出揃ってきましたし最近はTSが整ってきた感がありますね。
あまり意識していなかったのですが、ブラウザ上で動作するJavaScriptとNode.js上で動作するバックエンドのJavaScriptは微妙に違うとのこと。本書でしっかり押さえておかねば...
2 JavaScript/Node.jsの文法
- インストールは安定版LTSの最新バージョンがオススメ。偶数番号が安定版になる。現在v18。
- JavaScriptの文法諸々。データ型でundefinedがその変数が未定義、nullはデータがないことを表す。
Object
型は中に何でも持てるが、JSONは文字列、数値、配列、boolean、null、JSON Objectしか持てないのに注意。関数を持ったObjectをJSONに変換して戻すと一部消えたりする。- 継承は、例えば
Array.prototype.myFunction = ..
のように定義するとできる。class構文はシンタックスシュガー。 - Node.jsを使ったバックエンド実装では、中に状態を持った
class
を使う設計は避けた方がよい。 - 有名な
this
の指す先が変わる挙動の話。アロー関数で解決する。 const c = [...a];
のように既存の配列やオブジェクトを展開するSpread構文をよく使う。元のオブジェクトとは違う新しい参照を持ったオブジェクトを作れる。(中にまたオブジェクトを持っていたら挙動が違う。const [first, second, ...foo] =
のようにオブジェクトからまとめて取れる分割代入も便利。Reactで出てくる。- ループで添字iが必要なら伝統の
for
ループ、不要ならfor...of
が便利。
アロー関数は省略しまくると const doubleFunc = a = > a* 2;
のように書けますが、本書の作者さんは()
や{}
やreturn
文はむやみに省略しない派とのこと。僕もバックエンドが主なのでコードやコメントは冗長でもいいから他人や未来の自分向けにちゃんと書く派なので同意見なのですが、このへん世の中の流れや言語ごとの文化圏によって違いがありますね。
ここで分割代入のコード例で console.log(foo);
した結果が20,40,50
になっていたのですが、30,40,50
の間違いかな? と思いました。
3 Node.jsとモジュール
- 歴史的経緯から、Node.jsは標準未策定の頃に採用したCommonJS(正確にはCommonJS modules)と、のちに標準になったECMAScript modules の両方を使っている。後者が言語としての標準だが普及にはまだまだ。
- CommonJS:
- コード中では
export.(関数や変数) = ....
で別ファイルからはrequire('./sample');
のようにして読み込む。 - 変な別名はつけない方がよい。
-
module.exports = ....
と書く方法もあるが、export
の方がおすすめ。 - モジュールはシングルトンで読まれるので、中の変数を書き換えると他の場所で読み込んでも反映される。
- 拡張子は
*.js
か*.cjs
がデフォルト。
- コード中では
- ECMAScript modules:
- 標準では
*.mjs
という拡張子を使う。 - ドットがつかない
export const hoge = 1
のようにしてエクスポート。 - 別ファイルからは
import {hoge} from './hoge.mjs';
のように拡張子まで書く。import * as
も可能。 -
export default
という変数名未指定でも使える記法もあるが、本書では混在させない方法をオススメ。 - ボタン押下のイベントなどで
import
してthen
で処理を書き、呼び出された時点で初めて読み込まれるDynamic imports機能もあるが、現状はバンドルでファイル名を減らす方が効果は高い。
- 標準では
- package.jsonの
type
プロパティでどちらかに固定できる。アプリ開発はまだCommonJS、ライブラリなら両方に。 - ブラウザの上で動く標準のJavaScriptにないNode.jsでの各種実装が「標準モジュール(Core API)」。ファイルアクセスなどなど。最近は
require('node:fs');
のようにnode:接頭辞で区別することもある。今後は増えていく予定。 - package.json に
"private": true;
と書いておくと公開されないので安心。 - npmで公開の全てのモジュールには適用されきってはいないが、セマンティックバージョニング(semver)が推奨なので参考に。
- npm 以外の yarn や pnpm などのパッケージマネージャーをサポートしようという動きもある。
時々遭遇することのある*.cjs
や*.mjs
ってそういうことだったのか...!と改めて整理になる章でした。今後はもうECMAScript modulesでシュッと統一、となれば良いのですが、CommonJSもまだまだ使われているとのこと。このへんJS文化圏はいつも発展の途中だなと。
yarnは速いが結局npm本体も進化してるので結局npmのままで良いというような話も聞きましたが、他にもパッケージマネージャーでpnpmがあるのですね。いやはや広い世界です。
4 Node.jsにおける非同期処理(フロー制御)
- イベントハンドリングが
Calblack, Promise, async/await, EventEmitter/Stream
の4種。可能ならasync/await
, ストリーム処理のみEventEmitter/Stream
に統一するのが本書お勧めの設計パターン。 - Node.jsは常時イベントストリームが動いているので、同期処理でイベントループを停止させないことが重要。
- 非同期処理は実はlibuvというライブラリから提供されている。V8エンジンで実装されているのが同期処理(JSON操作や配列など)
- もっとも古くからある
Callback: readFile(ファイル名, コールバック関数);
方式; - Callbackの弱点を解消した
Promise
:const PromiseFn = new Promise((resolve, reject) => { (正常時はresolve,失敗時はrejectを返すよう実装) });
- そして
then
,catch
で受ける。 - ネストを防いでチェーンで書ける。しかしループや条件分岐になるとまだ苦しい。CallbackのPromise化も実はできる。
- 推奨の
async/await
:シンタックスシュガーで同期的コードのように書ける。最推奨。async function someFn() = { await Promiseを返す式 };
const someArrowFn = async () => { await Promiseを返す式 };
- これらの関数を実行、
.then((data) => {})
や.catch((err) => {})
でエラーハンドリングできる。 Promise
と組み合わせて並行処理も実はシンプルに書ける。
ストリーム処理の
EventEmitter/Stream
:- 処理の開始や途中や終了、エラー時など様々なタイミングに別の処理を挟める。Core APIで実装。
EventEmitter
クラスを拡張したクラスを定義、newして作り、.on('{イベント名}', {入れる処理});
でリスナーを設定.emit('{イベント名}', {引数});
でイベントを発生。細切れの非同期イベントに対応できる。- このEventEmitterにデータを貯めるバッファのようなものがStream。HTTPサーバーを立てるコードでも使われている。データを少しづつ処理できる。
- このStream処理では
try-catch
ではエラーが捕捉できないのでエラー用イベントの定義が必要。
AsyncIterator : async/awaitとストリーム処理を一緒に扱える
for await (変数名 of iteratableな対象) {
のようにして、大量データを少しづつ処理する非同期処理を同期コード的に書ける。
- エラーハンドリングをまとめると、同期処理ならふつうの
try-catch
かasync/await
でラップするの1通り。非同期処理はそれぞれ違う、となる。 - 他の技術では1リクエスト=1プロセスだがNode.jsは複数リクエスト=1プロセスの割り当てになるので、プロセスがクラッシュすると全リクエストがエラーになるので注意。
- Top-Level Await: v14.8.0から可能に。asyncな関数の中でなく一番外側でもawaitが書ける。ECMAScript modulesのみ。例えばHTTPサーバーの実装だとリクエスト待ち受けになりパフォーマンスは悪くなる場合もある。
JavaScriptの入門書などでもたびたび登場するこのあたり、本格的に使い込まないとなかなか身につかずよく忘れてしまうのですが、本書では改めて整理されています。こうして一覧化されると改めてasync/awaitがだいぶ見やすくなったのだなあと分かります。
ファイル処理やHTTPサーバーなどコード例も登場するのですが、バックエンドの他の言語に比べるとやはりかなり独特なところがありますね...注意が必要そうです。
コラムにはNode.jsの進歩を加速させたio.jsや、async/awaitと名前が同じでややこしいけどそれより前に同じ問題を解決しようとしたasync.jsの話なども載っています。JavaScript周りはこういう四方山話が豊富なのでこういうのもありがたいですね。
5 CLIツールの開発
Node.jsのパワーを活かして、ローカル上のMarkdownファイルを読み込んでHTMLに変換してファイルで出力するコマンドラインツールを作っていく章。難易度的にもちょうどよい題材かと思います。
- まずはプロジェクトのフォルダを作って、
>npm init -y
でpackage.jsonを作る。 - ファイル読み込みは標準モジュールの
fs.readFileSync(....)
で可能。CLIツールなので同期処理で良い。 - コマンド引数はグローバル変数の
process.argv
で取れる。配列の0,1,の先の2番目~が引数。 - yargs モジュールが上位互換で引数処理諸々をよろしくやってくれる。オプションを表示してくれたり。
- 処理が長くなってきたら1ファイル=1関数基本で
lib/
の下に分割したり。ファイルパスは絶対パス注意。 - .md->.html への変換は今回は marked モジュールを使う。
marked({入力文字列});
と関数を呼ぶだけ。 - リンターとしてはよく使われるESListを
>npm install
。package.jsonの中でルールを書いたりお勧め設定を読み込んだり。 - そのままだと実行は
> ./node_modules/.bin/eslint *.js
とコマンドが長いので、package.jsonのscriptsプロパティにショートハンドを書いて便利に。./node_modules/.bin/
のパスを解決してくれる。>npm run
で実行。 - npmでESListを入れた上で、VSCodeの拡張機能「ESLint」を入れるとVSCode上でも実施できる。
- ユニットテストは、実はNode.js標準モジュールにassertモジュールがあり、型まで含めた
assert.strictEqual()
やオブジェクトの中まで見てくれるassert.deepStrictEqual()
などができる。 - しかし前処理後処理などの高度な機能はないので、「テストランナー」を導入。Jestやmochaが有名。
- testコマンドは特別扱いで、package.jsonにコマンドを書いておくと、
>npm run test
でなく> npm test
で可能。 - テストコードの置き場は、JavaScript文化圏では統一されていない。
- フォーマッターはPrettierが有名。
他言語の経験はあるのでこの章は割とスラスラ読めてだいぶイメージが湧きました。npmに公開されているJS製CLIツールのモジュールも、根本的にはこんな感じで開発されているのですね。
コラムで日時処理は昔はmoment.jsがあったが今日ではDays.jsやdate-fnsがお勧め...とあり、こういうこぼれ話も役に立ちます。
ちょっと分からなかったのはファイル分割でlibの下に切り出されたファイルでした。exportsしたい関数の外側で変数を宣言してるのですが、こういう変数は関数の中で定義するのがふつうじゃないでしょうか..JSやNode.js界隈だとお作法が違うのかな?
6 ExpressによるREST APIサーバー/Webサーバー
指定されたURLへのアクセスでレスポンスやHTMLを返す、NoSQLのRedisからユーザーデータを返す、エラーハンドリングやテストもしていこう...という実戦的な章。
- 実は標準モジュールのhttpでもAPIサーバーはすぐ書けるのだが、本格的なAPI/Webサーバー構築ならフレームワークを使う。今もExpressがデファクトスタンダード。ミニマムな作りでパフォーマンスを下げない。
- ルーティング機能
- ミドルウェアという各ルートに対応したコールバック関数 の2機能
- 開発者側でさらにエラーハンドリングもしておけばOK。
- ふつうにnpmでインストール、変数
app
に入れたらapp.get({パス}, {ミドルウェア関数}, {ミドルウェア関数}...)
でルーティング。 app.listen({ポート番号}, {コールバック関数});
で立ち上がる。- ミドルウェアは引数に
req,res
を取り、複数動かす場合は第3引数のnext
をnext()
で呼べばよい。 app.use({全ルート適用のミドルウェア関数});
のようにして共通ロギングなども可能。app.use((err, req,res, next)=> {500を返すとか});
のようにして全体のエラーをハンドリング。- asyncな関数が出す非同期エラーはキャッチできないので注意。
- Redisとの接続には、ioredisというモジュールを使用。Redisオブジェクトをnewしてパラメーターで接続情報を渡す。
- Node.jsでは
process.env.XXX
で環境変数が使える。 - このオブジェクトは
EventEmiitter
を継承しているので一度だけ実行されるイベントハンドリングの.once
で起動処理のハンドラー関数を書けばよい。 - 本書のサンプルではハンドラー内でP
romise.all
を使用して初期のuserデータを保存。ハンドラー内部でエラーが起きるとキャッチできないので、明確にtry-catch
で処理する。 - Redisから取得したデータは4章で出てきたAsyncIteratorの書き方で変換してレスポンスに返す。
ページングが必要な場合はURLパラメーターのoffsetに載せるのが一般的。Redisを呼ぶ関数の引数で使える。
Webサーバー側でHTMLを生成して返したい場合は、テンプレートエンジンとしてはejsが有名。
- 普通にnpmでインストールし、サーバーのコードに
app.set('view engine', 'ejs');
と書いて認識される。 - レスポンスを返すミドルウェアの関数内で
res.render({xxx.ejsの絶対パス}, {セットする値の集合});
のように書く。 慣例的にapp_name/view/xxx.ejs
の場所に書き、中身はHTML。<% ~ %>
でJSが書けてそこに変数をセット。 純粋なjsファイルなどは
app_name/public/jsfuncs.js
に配置することが多い。ルーティングではここを/static
として設定、EJSの中のHTMLの<script>
タグ内でも static/jsfuncs.js のように書く。express-generatorという初期コードを生成してくれるライブラリがあるが、ファイル構成の考え方が若干古い。本書のスタイルでは
server.js
という単一ファイルにルーティングをまとめる方法を推奨。- 設定ファイルの分割は昔はconfigモジュールというものが長く使われてきたが、近年はなくてもよくなりつつある。本書では単一のファイルに集約。
DBなどを指すミドルウェアとは別の意味のミドルウェアは、PHPのLaravelフレームワークでもルーティング周りに出てくるので、へーと思いました。共通処理だけを指す訳ではなくNode.jsの文脈だとパスに対応して動く関数全体を指すのでしょうか? などとWikipediaを見たら
また別の意味として、Django、Laravel、ASP.NET CoreなどWebフレームワークの一部において、HttpリクエストからControllerに至るまで、およびControllerからHttpレスポンスを返すまでの共通処理を層状に追加する概念である。
とありました。なるほどなるほど。他の言語でのフレームワーク類を知っていればだいたい対応するところがあるので、この6章前半は他言語の方も理解できていくのではないでしょうか。
難しいと思ったのはところどころにasyncがついた非同期の関数が入ることですね。ハンドラー内部のエラーはキャッチできないそうですが、あれEventEmiitterの解説と違うような...(混乱中)
本書の例ではファイル構成は以下のようになるかと思います。
app_name/ +- public/ +- index.js //HTML内で使うJS関数 + handlers/ +- user.js // ルーティングで/userに来たときのハンドラー関数 +- user.test.js // ハンドラー関数に対応したテストコード + views/ +- user.ejs // ルーティングで/userに来たときのレスポンスで返す画面 +- index.ejs // デフォルトのレスポンスで返す画面 + server.js // app.jsなどでも。Webサーバー起動、ルーティング、DB起動、初期データ投入など + config.js // 明言されていないが設定ファイルもルート直下のここ? + package.json // いつもの + package-lock.json // いつもの
- テストランナーのJestにはjest.mockという機能で、読み込んでいるモジュールをモック化できる。本書の例ではRedisとのやりとりをモック化。
- async/awaitとストリーム処理を一緒に扱えるAsyncIteratorの書き方をしているコードのモック化はちょっと高度になる。以下を活用して行う。
- JSの仕様で*をつけると何度も値を返す関数が定義できる
Generator
関数 - JSの仕様でオブジェクト生成のように書くが
===
が真にならないSymbol
- JSの仕様で*をつけると何度も値を返す関数が定義できる
- 失敗時のテスト
- モック化されたクライアントに
Error
を渡して、ハンドラーの関数を呼んでcatch
に来ることで確認したり - Jestは
expect
が何回呼ばれたかで判定するテストも書ける。これが重要になる場合も。
- モック化されたクライアントに
- ハンドラーの単位とテストのしやすさの話
- 本書のサンプルコードの
getUsers
等の関数は引数req
、戻り値が結果になっている。 - 引数に
res
追加、これにステータスコードや検索結果を加える方法もあるが、依存が発生して後々大変になる話 - ルーティングからはまずハンドラー関数自体を引数にしたエラーハンドリング用関数を呼ぶというテクも
- エラーをクラス化するテクなどもある
- 本書のサンプルコードの
Jestは様々な機能があって便利そうですが、モック周りも入ってくると本格的になってきてテストコードを書くのもけっこう難しそう...!と思いました。ちゃんと入門しないといけなさそうですね。
作者の方が様々な現場で試行錯誤して得られたのであろう、様々なテクニックも入っていて参考になります。このへんやはりNode.js開発で標準で唯一定まっている方法はないのですね。
- Node.js製アプリをLinuxサーバーにデプロイする時の話
- Docker上で動作させると、内部でプロセスは
pid=1
として起動する。Ndode.jsはpid=1
として動作するよう設計されていない。結果としてCtrl+Cで止まってくれない。> docker run
に--init
をつける方法がある。 - デプロイ先がマルチコアでCPUが複数あると、Node.jsはシングルコア・シングルプロセスなのでリソースを活用しきれない。npmで入れるclusterというモジュールでマルチプロセス対応ができる。
- CPUの数まで増やしたり、-1に留めたり、半分にとどめたり
- クラウドのPaaS環境ではプラットフォーム側でスケールしてくれるので、そちらに任せた方がよい。
- RDBに繋げる場合...
- 最近の、Promiseのインターフェースに対応したモジュールを使う。
- SQL部分を担うクエリービルダーではKnex.js
- より進んだORMではSequelize、MongoDB用のmongose
- TypeScriptではTypeORM, Prismaなど
- プレーンなJavaScriptオブジェクトに変換して使うようになるとORMの意味が薄くなってしまったりもする。今後も移り変わりがありそう。
沢山CPUを積んだマルチコアだとそれ用のモジュールがあったりするのか...なんて話は全く知りませんでした。浅い情報だとこのへん載ってないことも多いのでありがたいです。シングルプロセス・シングルスレッドならではの注意点がありそうです。
7 フロントエンド/バックエンドの開発
なかなか長い充実の7章は、フロントエンドをReact、バックエンドをExpressで動くAPIサーバーとして、両方を同じリポジトリで実装していきます。
- モノレポ(Monorepo): 複数のアプリやパッケージをひとつのリポジトリで管理。フロントとバックにも。マイクロサービス指向で細かく分割しすぎると影響範囲が広がってしまうため、モノレポにする手法がもある。
- 他の管理ツールがLenaやbazelなど。今回取り上げるnpm workspacesはnpm v7でnpm自体に追加された機能、package.json内に
workspaces
プロパティで管理したいパッケージを記述。今回はpackages/*
とし、packages/frontend, backend
の2つを管理。それぞれの下にまたpackage.jsonがある。
- いにしえのjQueryスタイルではボタンクリックごとにイベントでHTML操作を記述したりしていたが、HTMLが表示だけでなく状態の責務も保持してしまい、複雑化すると管理が大変という弱点。ここでフロントエンドフレームワーク群が生まれた。
Reactによるフロントエンドの開発
- 最初は
> npx create-react-app packages/frontend
でファイル一式が生成。 src/App.js
がトップのReactコンポーネントで、モジュールはECMAScript modules 形式。- 昔はjQueryがブラウザ間の差異を吸収。その後BabelがES6→ES5への変換に。TypeScript言語もJavaScriptへの変換が必要。モジュールやライブラリの増加でコードを結合するためにビルド工程、全体をひとつにまとめるモジュールバンドラーが重要に。
- これらの経緯があり2015年頃からビルドが必須に。React固有のJSXもビルドで変換される。create-react-appでは仕組みの中に隠蔽しているが、ビルドの中でトランスコンパイル、モジュールバンドラーが動いている。
- Reactコンポーネント実装を進め、関数内で変数を持つように。描画ではバーチャルDOMを使い差分だけを再描画。繰り返し表示にはkeyが必要。
- ボタンを押した際の
handleSubmit
関数をコンポーネントの中に実装。素のJSのaddEventListener
関数を使う方法だと、DOM要素がなくなったら消えてしまう。 - 入力した文字列が再描画でも消えないように、ここでReact Hooks登場。useStateを使って保持。ここで新しい描画用配列を元の配列とは別に作りたい場合などに、
...users
のSpread構文がよく出てくる。
- 最初は
React開発の応用
- 同一マシン上でフロントエンドとバックエンドを別のポート番号でWebサーバーとして起動していると、Cross-Origin Resource Sharing(CORS) が起こる問題アリ:
- 開発サーバーではcreate-react-appに
proxy
オプションが内蔵されている。packaje.jsonにバックエンド側のポート番号(本書ではhttp://localhost:8000
)を指定、fetchのコードでは/api
のようにドメインの後からだけの記述に。 - 本番サーバーにフロントエンド部分を配信する場合は、Node.jsで動いているならhttp-proxy-middlewareを使うとよい。フロント配信用のコードをExpressフレームワークで実装、
server.js
として書いた基本コード内に記述。 - 他、フロントエンド部分をnginxで置いてProxyサーバーにする方法もある。
- 近年はフレームワークが自動的に吸収してくれていることもある。本書では別コラムで説明。
- 開発サーバーではcreate-react-appに
- React Hooksの活用
- コンポーネント呼び出し時に1回だけfetchしたい時は、
useEffect({実行する関数}, []);
ただし空配列にしてもReact v18~では2回実行されるので注意。
- コンポーネント呼び出し時に1回だけfetchしたい時は、
Client Side Routing
- JavaScriptのHistory API実装に伴い可能になってきた。画面リロードが伴わず、スマートフォンアプリの利用体験に近づける。
- 自前実装も理論的には可能だが、ReactではReact Router(react-router-dom)を使うのが主流。
- ルートで呼び出されるApp.jsのReactコンポーネントAppで、これをimport、
<Router><Routes>~</Routes></Router>
要素内にパスと呼び出す子供のコンポーネントを書いていく。 - ルーティングを使っている場合はリンクは
<a>
要素で書くとブラウザの読み込みが発生、CSSなどのリソースをネットワークからもう一度取ってきてしまう。用意されている<Link to="">~</Link>
を使うほうがよい。
フロントエンド部分のデプロイ
- 本書の例では
> npm run build -w package/frontend
で本番用のものができる。 - Expressフレームワークを使った配信用のコードも
frontend/
内に格納。GETリクエストは/
でなく/*
でindex.htmlを
返すようにしておく。 frontend/package.json
内に設定してコマンドで起動。- Reactコンポーネント部分を初回はサーバー側で描画する Server Side Renderingは、理論的には自作もできるがNode.jsの知識等々も必要。ラップしてくれるNext.jsに任せるのがお勧め。Vercel社の企業主導だがGoogleもサポートしている。
- 本書の例では
本書の構成ではフロントエンド部分もNode.jsのサーバーで動かして、バックエンドもとはポート番号で分ける構成でやっています。2つあって慣れないうちはちょっと混乱しそうですね。
- フロントエンド部分のテスト
- 実行環境でNode.jsは必要、標準のテストランナーであるJestがcreate-react-appでは最初から入っている。
- バックエンドのテストと違うのはDOMがあることだが、DOMをエミュレートしてくれるjsdomというライブラリを使う。
- テストの中で共通して現れる描画の部分をカバーしてくれるtesting-library というモジュールがcreate-react-appには入っている。
- バックエンドへのfetchのテストについては、fetch処理をmock化するか、mockサーバーを別に用意する2通りがある。testing-libraryがサポート。
- mockサーバーでの方法は、
*.test.js
にGET /api/hoge
が来たらこれを返すよ...というのを書き、その後にテスト本体も書く。 - 本書の例ではUserコンポーネントがデータの取得と描画を両方受け持っていたので、描画はコンポーネント、データの保持・変更はCustom Hooksを使った別関数に分割。ファイルは
src/Users.hook.js
としている。それぞれをテストしやすくできる。 - mockの利用は一長一短な所もある。
- コンポーネントの責務としてのロジックと描画はなるべく分離、一つのテストでは片方だけをテスト、は常に正しい。
- 描画されたHTMLを文字列で比較するスナップショットテストの手法もある。react-test-rendererを使用。無闇に採用すると手間が増えるだけのこともあるので注意。ロジックのテスト、コンポーネントのテストの方がよい。
- ブラウザ操作自動化ではPupeteer、Chrome以外ならMicrosoftのPlaywrightがある。
この7章がなかなか長く本格的なのですが、React Hooks含めた最新のReactでの基本的な開発諸々~デプロイ~テストまで、様々な知見が詰まっていて非常に充実しています。フロントとバックは別リポジトリで別々に開発するのが普通かと思ってたんですが、今はモノレポの手法もあるんですねー。コラムにもいろんなお役立ち情報があってありがたいです。
8 アプリケーションの運用と改善
- Node.js自体も各種パッケージもなるべくバージョンアップする。
> npm outdated
で各パッケージがどれだけ古いか分かる。 >npm audit
で脆弱性チェック。>npm audit fix
ですべて解決されるわけではないので注意。- 作成済みのモノレポなプロジェクトでも、後からworkspace(物理的にはフォルダ)を追加して共通部品を切り出したりできる。依存の方向は片方向に。
- アプリ実運用に向けた注意として...
この8章は短いのですが実際の運用時の知見が詰まっています。ユーザー情報をキャッシュとして単に変数に入れておくと次のリクエストでアクセスできちゃったりするんですね。このへんかなり独特だなと思いました。他言語のバックエンド実装と同じ雰囲気でやっていると罠に嵌りそうです。
まとめ:テーマ一式網羅、最新情報に基づいたNode.js周りを体系的に学べる本
国産の本なので翻訳本特有のとっつきにくさもなく、文章も読みやすかったです。後書きによると作者さんもNode.jsを様々な案件で使い、慣れない頃は勉強会をやったり毎週泣きながらNode.js自体のソースを読んだり(ヒエッ...笑)したそうですが、そうした長年の経験に基づいた知見、独自の意見などもしっかり詰まっています。自分的には断片ごとには知っていたようなことも載っていたのですが、そうした断片が体系的に繋がり、知らなかったことが補完され、最近のアップデート分が上書き更新され、体系的に改めて整理できた感がありました。ちょうど勉強会でフロントエンド関連の話題をやろうと思っていたのでよいタイミングで読むことができました。
Node.js自体にモダンJavaScriptにReactにExpressにDBアクセスにユニットテストに運用とテーマ一式全部載せ、オールラウンドで詰まったおトクな本です。現役フロントエンドエンジニアの方もこれから挑戦する方も、バックエンドの方も、十分に役に立つことと思います。
おまけ:Node.js関連書籍
本書から過去に遡っていくと...安心のオライリー本『ハンズオンNode.js』が2020/11。500ページ以上のしっかりした本です。
初心者向けの様々な超入門本を出している掌田津耶乃さんの『Node.js超入門[第3版]』が2020/7。その前の第2版が2018年で僕も読んだような...まあエンジニアが仕事で使うにはちょっと浅いシリーズですね。バックエンドのFWはExpress、DBアクセスにsequelizeが出てきます。
同じ超入門シリーズで『Node.jsフレームワーク超入門』が2022/5で新しい本。バックエンドでExpress、TypeScript夜のORMにPrisma、TypeORM。他にFWでSails.js、Adonis.js、NestJS、Meteorという珍しいテーマもあります。この辺をつまみ食いするにはよいかもしれません。
『Node.jsデザインパターン 第2版』が2019/5。こちらもがっつりしたオライリー本、Node,js v11.9を対象にガッツリと内部に踏み込んだ中級者以上向けの本。
『入門Node.jsプログラミング』も2019/9。こちらも同じく海外本、基本は分かっている中級者以上向けに様々なプロジェクトと共に解説していく本。
『サーバサイドJavaScript Node.js入門 (アスキー書籍)』は遡って2014年、うーんNode.jsが徐々に市民権を得てきた頃の唯一の本ですね。さすがに今となっては古いですね。(僕も前に読もうと思ってやめたことがあります...)
Node.js自体を扱った本というのは意外と少なかった感がありますが最近揃ってきました。超入門シリーズよりもっと深い本、かつ海外本特有のとっつきにくさがない国産本、そしてエンジニアが仕事で使うレベルの各種テーマを総合的に網羅した最新の本...という立ち位置でも、本書『実践Node.js入門 ―基礎・開発・運用』はちょうど良い位置にフィットするのではないでしょうか。
仕事で使うレベルのTypeScript自体の一冊というと2022/4に出たブルーベリー本こと『プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで』がド定番、もしくは2020/3のオライリー本の『プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発』の2冊が有名。
そして最近は2023/2の『フロントエンド開発のためのセキュリティ入門 知らなかったでは済まされない脆弱性対策の必須知識』、2023/4に出る『フロントエンド開発のためのテスト入門 今からでも知っておきたい自動テスト戦略の必須知識』と今まではなかった個別テーマの本も出てきました。
JavaScript自体の本というと2020/4の『JavaScript Primer 迷わないための入門書』が現場のエンジニアからも推奨されていましたが、その後オライリーのサイ本『JavaScript 第7版』が2021/12に出たり、前の版も評価の高かったJavaScript本格入門が2023/2に『改訂3版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで』と遂にアップデートされました。
一昔前まではフロントエンドは変化が早いから本が出ない!なんて言われていましたが、こうしてみると商業書籍もだいぶ陣容が揃ってきましたね。2023年現在では、学ぶ環境が成熟してきたといえるでしょうか。