🌟
fastify-env+TypeBoxで環境変数を管理する
fastifyには環境変数を管理するfastify-envが用意されている。
今回はfastify-envを使用して環境変数から呼び出した情報をもとにDBの接続を行うプラグインを作成してみる。
GitHubにサンプルを用意したので、実際の動作は下記を参照。
使用ライブラリ
- @fastify/env:fastifyで環境変数を管理するプラグイン。
- @sinclair/typebox:Typescriptで静的解析を行うライブラリ。
- dotenv:.envファイルを読み込むライブラリ。
- cross-env:起動時に環境変数を指定するライブラリ。NODE_ENVを指定するために使用。
ディレクトリ構成
.
├── plugins
│ └── db.plugin.ts
├── postgres
│ └── Dockerfile
├── src
│ ├── infrastructure
│ │ └── config
│ │ └── env.ts
│ ├── app.ts
│ └── server.ts
├── .env.development
├── .gitignore
├── docker-compose.yml
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json
.env.development(環境変数)
環境変数の設定場所。ここではデータベースの接続先が設定されている。
DATABASE_URL=postgresql://admin:password@localhost:5432/db?schema=public
env.ts(環境変数の管理)
環境変数を読み込むための設定を行う。起動時にNODE_ENV=development
が実行されると、.env.development
が読み込まれる。
- schema:環境変数を管理する。.envファイルに併せて追加する。ここでTypeboxを用いた静的解析が可能。
- envOptions:fastify-envのオプションの設定。Usageを参考にする。
env.ts
import { TObject, Type } from '@sinclair/typebox';
import * as dotenv from 'dotenv';
declare module 'fastify' {
interface FastifyInstance {
config: {
DATABASE_URL: string;
};
}
}
const schema: TObject = Type.Object({
DATABASE_URL: Type.String(),
});
export const envOptions = () => {
if (!process.env.NODE_ENV) {
throw new Error('NODE_ENV is not defined.');
}
dotenv.config({ path: `.env.${process.env.NODE_ENV}` });
return {
dotenv: true,
confKey: 'config',
schema,
data: process.env,
};
};
※ 環境変数を追加した際にdeclare module 'fastify'にも追加すること。
server.ts(プラグインの登録+起動)
プラグインを登録してサーバーを起動する。
- fastifyEnv:
env.ts
で作成したenvOptionsを指定して、fastify-envを登録する。これによって環境変数を呼び出すことができる。 - dbPlugin:DBの設定を行う。環境変数を使用するのでfastifyEnvの後に登録を行う。
server.ts(プラグインの登録+起動)
import fastifyEnv from '@fastify/env';
import App from './app';
import { envOptions } from './infrastructure/config/env';
import DbPlugin from '../plugins/db.plugin';
async function bootstrap() {
const app = new App();
await app.$server.register(fastifyEnv, envOptions());
await app.$server.register(DbPlugin);
await app.listen();
}
bootstrap();
db.plugin.ts(環境変数の呼び出し部分)
データベースの接続に成功した場合は接続先がログが出力され、失敗した場合はエラー内容がログに出力される。
db.plugin.ts
import { FastifyInstance, FastifyPluginOptions, FastifyPluginAsync } from 'fastify';
import { fastifyPlugin } from 'fastify-plugin';
import { Client } from 'pg';
declare module 'fastify' {
interface FastifyInstance {
pg: Client;
}
}
const dbPlugin: FastifyPluginAsync = async (
$server: FastifyInstance,
_opts: FastifyPluginOptions,
): Promise<void> => {
const client = new Client({
connectionString: $server.config.DATABASE_URL,
});
try {
await client.connect();
$server.log.info(`Connected to the DB at URL: ${$server.config.DATABASE_URL}`);
} catch (error) {
$server.log.error(error);
throw error
}
$server.addHook('onClose', async () => {
await $server.pg.end();
});
};
export default fastifyPlugin(dbPlugin, '4.x');
実行
DBの起動
docker-compose up postgres
サーバーの起動
npm run start:dev
実行結果(接続成功)
> fastify-env-eample@1.0.0 start:dev
> cross-env NODE_ENV=development ts-node-dev --watch -- src/server
[INFO] 18:25:14 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.2, typescript ver. 5.3.3)
{"level":30,"time":1704360316295,"pid":19964,"hostname":"DESKTOP-UOO17L2","msg":"Connected to the DB at URL: postgresql://admin:password@localhost:5432/db?schema=public"}
{"level":30,"time":1704360316299,"pid":19964,"hostname":"DESKTOP-UOO17L2","msg":"Server listening at http://[::1]:3000"}
{"level":30,"time":1704360316300,"pid":19964,"hostname":"DESKTOP-UOO17L2","msg":"Server listening at http://127.0.0.1:3000"}
実行結果(接続失敗)
> fastify-env-eample@1.0.0 start:dev
> cross-env NODE_ENV=development ts-node-dev --watch -- src/server
[INFO] 18:33:57 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.2, typescript ver. 5.3.3)
{"level":50,"time":1704360839127,"pid":11576,"hostname":"DESKTOP-UOO17L2","err":{"type":"AggregateError","message":"","stack":"AggregateError: \n at internalConnectMultiple (node:net:1114:18)\n at afterConnectMultiple (node:net:1667:5)","aggregateErrors":[{"type":"Error","message":"connect ECONNREFUSED ::1:5432","stack":"Error: connect ECONNREFUSED ::1:5432\n at createConnectionError (node:net:1634:14)\n at afterConnectMultiple (node:net:1664:40)","errno":-4078,"code":"ECONNREFUSED","syscall":"connect","address":"::1","port":5432},{"type":"Error","message":"connect ECONNREFUSED 127.0.0.1:5432","stack":"Error: connect ECONNREFUSED 127.0.0.1:5432\n at createConnectionError (node:net:1634:14)\n at afterConnectMultiple (node:net:1664:40)","errno":-4078,"code":"ECONNREFUSED","syscall":"connect","address":"127.0.0.1","port":5432}],"code":"ECONNREFUSED"},"msg":""}
AggregateError:
at internalConnectMultiple (node:net:1114:18)
at afterConnectMultiple (node:net:1667:5)
[ERROR] 18:33:59 AggregateError
Discussion