Fastify ベストプラクティス #1: 環境変数
はじめに:なぜ環境変数がそれほど重要なのか?
アプリケーション開発において、設定 と コードの分離は黄金律です。私たちのアプリケーションは、ローカル開発、CI(継続的インテグレーション)、テスト環境、本番環境など、さまざまな環境でデプロイされる必要があります。各環境には、データベース接続文字列、APIキー、ポート番号など、それぞれ固有の設定があります。
環境変数は、この分離を実現するための最適なツールです。これらを適切に管理することで、アプリケーションの柔軟性、セキュリティ、保守性を大幅に向上させることができます。
このブログでは、Fastify開発における一般的な手法から始め、その課題を分析し、最終的にクラウドネイティブ時代により適した、より純粋で強力な環境変数管理手法をマスターできるようご案内します。
1. 一般的な手法:コード内で dotenv を使用する
ほとんどの Node.js 開発者にとって、dotenv は非常によく知られたライブラリです。これは .env ファイル内の変数を process.env にロードすることができます。
典型的なプロジェクト構造には、以下のファイルが含まれるかもしれません:
env:ローカル開発環境用。env.test:テスト環境用。env.production:本番環境用。
Fastify アプリケーションにこれらの変数をロードさせるため、通常、エントリーファイル(例:index.ts)の先頭に次のようなコードを追加します:
// index.ts
import dotenv from 'dotenv';
import path from 'path';
// NODE_ENV に応じて異なる .env ファイルをロードする
const envFile = process.env.NODE_ENV ? `.env.${process.env.NODE_ENV}` : '.env';
dotenv.config({ path: path.resolve(__dirname, envFile) });
// ... この後に Fastify の起動コードが続く
この方法は機能しますが、いくつかの明確な課題があります:
- 設定とコードの結合:環境変数のロードロジックがビジネスコードに侵入してしまっています。コードの責務はビジネス機能の実装であり、自分がどの環境で実行されているかや、どのように設定をロードするかを関知すべきではありません。
- ビルド成果物が純粋でない:ビルド時に、すべての環境の
.envファイル(.env.test,.env.productionなど)が最終的なビルド成果物に含まれてしまう可能性があります。これはバンドルサイズを増加させるだけでなく、意図せず他の環境の機密設定が漏洩するリスクも生みます。 - 設定ソースの不統一:DockerやCI/CDツールを使用する場合、一部の設定はサーバーの環境変数から、別の一部はプロジェクト内の
.envファイルから提供されることになり、管理が混乱し、問題の特定も困難になります。
2. ベストプラクティス:設定は実行時に注入する
コードは可能な限り純粋であるべきで、ビルド後のコードにビジネスロジックと無関係なものが含まれるべきではないと私は考えます。したがって、設定は環境に保存されるべきです。つまり、アプリケーション自体は環境に依存せず、実行環境から設定を読み取るだけで、それらの設定がどのように注入されたかを知る必要はありません。
そのため、環境変数のロードプロセスをアプリケーションコードから切り離し、アプリケーションの起動前に移動させる必要があります。
実装方法1:dotenv-cliを使用する(汎用的な解決策)
dotenv-cli は、任意のコマンドを実行する前に、指定された .env ファイルを環境にロードすることができる、シンプルで効果的なコマンドラインツールです。
ステップ1:dotenv-cli を開発依存(devDependencies)としてインストールします。
npm i -D dotenv-cli
# または
yarn add -D dotenv-cli
ステップ2:package.json の scripts コマンドを修正します。
コード内で import 'dotenv' する必要はなくなり、代わりに起動コマンドの前に dotenv ディレクティブを追加するだけです。
// package.json
{
"scripts": {
"start:dev": "dotenv -e .env -- ts-node index.ts",
"start:prod": "dotenv -e .env.production -- node dist/index.js",
"test": "dotenv -e .env.test -- jest"
}
}
実装方法2:Node.js のネイティブ機能を使用する(新たな解決策)
良いニュースです。Node.js v20.6.0 から、Node.js は --env-file フラグをネイティブでサポートするようになりました。これにより、外部依存なしで同じ効果を実現できます!
使用方法は以下の通りです:
// package.json
{
"scripts": {
"start:dev": "node --env-file=.env --loader=ts-node/esm index.ts",
"start:prod": "node --env-file=.env.production dist/index.js"
}
}
3. さらなる一歩:環境変数を検証する
環境変数をロードするだけでは不十分です。堅牢なアプリケーションは、これらの変数が存在し、かつ有効であることを保証する必要があります。例えば、PORT は数値でなければならず、API_URL は有効なURLでなければなりません。
Fastify の公式プラグイン @fastify/env は、まさにこの目的のために作られた強力なツールです。アプリケーション起動時に、定義した schema に基づいて環境変数を検証し、検証に失敗した場合はアプリケーションは即座にエラーを吐いて終了します。これにより、実行時に設定ミスによる潜在的な問題が発生するのを防ぎます。
ステップ1:プラグインをインストールします。
npm i @fastify/env
# または
yarn add @fastify/env
ステップ2:Fastify に登録し、設定します。
import fastifyEnv from '@fastify/env';
const schema = {
type: 'object',
required: ['PORT', 'DATABASE_URL'],
properties: {
PORT: {
type: 'string',
default: '3000',
},
DATABASE_URL: {
type: 'string',
},
LOG_LEVEL: {
type: 'string',
default: 'info',
},
},
};
const options = {
confKey: 'config', // fastify インスタンスに登録されるキー名。デフォルトは 'config'
schema: schema,
};
await fastify.register(fastifyEnv, options);
// アプリケーション内では fastify.config を通じて、検証および型変換された変数にアクセスできます
// fastify.config.PORT
この方法により、私たちは環境変数をロードするだけでなく、それらのために信頼性の高い「ファイアウォール」を築くことができます。
4. まとめ
この進化の道のりを振り返ってみましょう:
- 初期段階:コード内で
dotenvを使って設定をロードし、コードと設定が結合してしまう。 - ベストプラクティス:
dotenv-cliまたは Node.js ネイティブの--env-fileを使い、アプリケーション起動前に設定を注入することで、コードと設定を完全に分離する。 - さらなる強化:
@fastify/envを通じて起動時に環境変数を検証し、アプリケーションの堅牢性を向上させる。
このように設定をコードから分離し、実行時に注入するモデルは、Docker や Kubernetes といったコンテナ技術の思想と完全に一致します。Docker では docker run --env-file .env.production ... でコンテナを起動でき、Kubernetes では ConfigMap や Secret を使って設定を管理できます。私たちのアプリケーションは一切変更することなく、これらの現代的なインフラストラクチャ上にシームレスにデプロイすることが可能です。
この共有が、よりプロフェッショナルで信頼性の高い Fastify アプリケーションを構築する助けとなれば幸いです。