Using ArkEnv with NestJS
Validate and type environment variables in NestJS with a fail-fast env module.
Nest builds modules, providers, and database connections inside
NestFactory.create(). ArkEnv validates environment variables when
env.ts is first imported, before that call.
Setup
Declare the schema in src/env.ts and export env. Importing that
module is the validation step. Controllers and services then read the
typed object directly.
import from "@arkenv/core";
export const = ({
: "'development' | 'production' | 'test' = 'development'",
PORT: "number.port = 3000",
DATABASE_URL: "string",
});Import env directly across your controllers, services, and modules:
import { Injectable } from "@nestjs/common";
import { env } from "./env";
@Injectable()
export class AppService {
getDatabaseUrl(): string {
return env.DATABASE_URL;
}
}Fail-fast entrypoint
Import env at the top of src/main.ts, before
NestFactory.create(), so a bad variable stops the process first.
import { env } from "./env";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(env.PORT);
}
bootstrap();Execution
ArkEnv does not load .env files. Nest has to put those values on
process.env before src/env.ts evaluates. On Node.js 20.6+ and
@nestjs/cli 11+, pass the native --env-file flag:
nest start --env-file .envOn @nestjs/cli 10 and earlier, build the app first and run
node --env-file=.env dist/main.js.
In production, the platform or container injects the variables before the Node process starts.
Optional DI provider
Happy importing env directly?
Skip this section. A normal import is the default. A provider is for code that already injects config through Nest's container.
Register env with a Symbol token when a service should receive it
through @Inject instead of importing the module:
import { env } from "./env";
export const ENV = Symbol("ENV");
export type Env = typeof env;
export const EnvProvider = {
provide: ENV,
useValue: env,
};Register EnvProvider in your module's providers array (and exports if used across modules), then inject ENV into services using @Inject(ENV) private readonly env: Env.
Why avoid ConfigModule validation
Leave validationSchema alone
Do not pass an ArkEnv schema into @nestjs/config's
ConfigModule.forRoot({ validationSchema }). Use src/env.ts
instead.
Starting in @nestjs/config 12, validationSchema supports Standard Schema validators, but @nestjs/config passes raw string environment variables directly to the schema without automatic type coercion. A non-coercing validator (like ArkType's number.port or Zod's z.number()) rejects "3000" with a type error instead of parsing it. In @nestjs/config 11 and earlier, validationSchema doesn't support Standard Schema validators.
Using ArkEnv directly in src/env.ts handles string coercion automatically and fails immediately before NestJS initializes.
HTTP validation vs. environment validation
NestJS 12 supports Standard Schema pipes (such as StandardSchemaValidationPipe) for HTTP request payloads (@Body(), @Query()). HTTP validation pipes validate incoming requests at runtime and are distinct from application environment variable validation.