log
码中赤兔

Fastify 最佳实践 #1: 环境变量

发布于 2025年6月20日
更新于 2025年6月26日
11 分钟阅读
TypeScript

前言:为什么环境变量如此重要?

在现代应用开发中,将 配置代码分离 是一条黄金法则。我们的应用需要部署在不同环境中,如本地开发、持续集成(CI)、测试环境和生产环境。每个环境都有其独特的配置,例如数据库连接字符串、API 密钥、端口号等。

环境变量正是实现这种分离的最佳工具。正确地管理它们,可以极大地提升我们应用的灵活性、安全性和可维护性。

这篇博客将从 Fastify 开发中的常见做法讲起,分析其痛点,并最终引导你掌握一套更纯粹、更强大、更符合云原生时代要求的环境变量管理方案。

1. 常见做法:在代码中使用 dotenv

对于大多数 Node.js 开发者来说,dotenv 是一个非常熟悉的库。它能将 .env 文件中的变量加载到 process.env 中。

一个典型的项目结构可能包含以下文件:

  1. .env:用于本地开发环境。
  2. .env.test:用于测试环境。
  3. .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 的启动代码

这种方式虽然能工作,但是它存在一些明显的痛点

  1. 配置与代码耦合:环境变量的加载逻辑侵入了业务代码。代码的职责应该是实现业务功能,而不是关心自己运行在哪个环境以及如何加载配置。
  2. 构建产物不纯粹:打包时,所有环境的 .env 文件(.env.test, .env.production 等)都可能被包含在最终的构建产物中。这不仅增加了包体积,还可能在无意中泄露其他环境的敏感配置。
  3. 配置来源不统一:当使用 Docker 或 CI/CD 工具时,一部分配置可能来自服务器的环境变量,另一部分来自项目内的 .env 文件,导致管理混乱,排查问题时也更加困难。

2. 最佳实践:让配置在运行时注入

我觉得代码需要尽量纯粹,打包之后的代码里不应该有和业务逻辑无关的东西。因此,配置应该存储在环境中,也就是说应用程序本身应该是环境无关的,它只从运行环境中读取配置,而不需要知道这些配置是如何被注入的。

因此,我们需要将环境变量的加载过程从应用代码中剥离,移到应用启动之前。

实现方式一:使用 dotenv-cli(通用方案)

dotenv-cli 是一个简单有效的命令行工具,它可以在执行任何命令之前,先将指定的 .env 文件加载到环境中。

第一步:安装 dotenv-cli 作为开发依赖。

npm i -D dotenv-cli
# 或者
yarn add -D dotenv-cli

第二步:修改 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"
  }
}

实现方式二:使用 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 来校验环境变量,如果校验失败,应用会立即报错退出,从而避免了在运行时出现因配置错误导致的潜在问题。

第一步:安装插件。

npm i -D @fastify/env
# 或者
yarn add -D @fastify/env

第二步:在 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. 总结

我们回顾一下这条演进路径:

  1. 初始阶段:在代码中用 dotenv 加载配置,导致代码与配置耦合。
  2. 最佳实践:使用 dotenv-cli 或 Node.js 原生的 --env-file 在应用启动前注入配置,实现代码与配置的彻底分离。
  3. 进阶增强:通过 @fastify/env 对环境变量进行启动时校验,提升应用的健壮性。

这种将配置从代码中分离并在运行时注入的模式,与 Docker、Kubernetes 等容器化技术的思想完全契合。在 Docker 中,我们可以通过 docker run --env-file .env.production ... 来启动容器;在 Kubernetes 中,我们可以使用 ConfigMapSecret 来管理配置。我们的应用程序无需任何改动,就能无缝地部署在这些现代化的基础设施上。

希望这篇分享能帮助你构建更专业、更可靠的 Fastify 应用。

关于

分享技术见解、经验和思考的个人博客

联系方式

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 码中赤兔. 版权所有