Type safety
One of Env Core’s key features is full TypeScript type inference. When you define a schema, TypeScript automatically knows the exact types of your validated environment variables.
Automatic type inference
Env Core uses TypeScript’s type system to infer the correct types from your schema definition:
import { validateEnv } from 'env-core' ;
const env = validateEnv ({
PORT: Number ,
NODE_ENV: String ,
DEBUG: Boolean ,
HOST: { type: String , default: '0.0.0.0' },
});
// TypeScript knows the exact types:
env . PORT // number
env . NODE_ENV // string
env . DEBUG // boolean
env . HOST // string
No type annotations needed! The env object is automatically typed based on your schema.
How type inference works
Env Core uses TypeScript’s advanced type features to transform your schema into precise types:
Simple type inference
When you use constructor functions directly:
const schema = {
PORT: Number ,
NODE_ENV: String ,
DEBUG: Boolean ,
};
// Inferred type:
// {
// PORT: number
// NODE_ENV: string
// DEBUG: boolean
// }
Configuration object inference
When you use configuration objects with defaults:
const schema = {
DEBUG: { type: Boolean , default: false },
PORT: { type: Number , default: 3000 },
};
// Inferred type:
// {
// DEBUG: boolean
// PORT: number
// }
Mixed schema inference
Env Core correctly infers types even when mixing approaches:
const schema = {
PORT: Number ,
DEBUG: { type: Boolean , default: false },
HOST: { type: String , default: '0.0.0.0' },
};
// Inferred type:
// {
// PORT: number
// DEBUG: boolean
// HOST: string
// }
The ValidatedEnv type
Env Core exports a ValidatedEnv type that you can use for type annotations:
import { validateEnv , type ValidatedEnv , type EnvSchema } from 'env-core' ;
const schema = {
PORT: Number ,
NODE_ENV: String ,
DEBUG: { type: Boolean , default: false },
} satisfies EnvSchema ;
type Env = ValidatedEnv < typeof schema >;
// {
// PORT: number
// NODE_ENV: string
// DEBUG: boolean
// }
const env : Env = validateEnv ( schema );
Use satisfies EnvSchema to ensure your schema is valid while preserving its specific type for inference.
Type safety benefits
TypeScript’s type inference provides several benefits:
Autocomplete
Your IDE knows which environment variables are available:
const env = validateEnv ( schema );
env . // IDE shows: PORT, NODE_ENV, DEBUG, HOST
Type checking
TypeScript prevents type errors at compile time:
const env = validateEnv ({
PORT: Number ,
DEBUG: Boolean ,
});
// ✅ Valid
const port : number = env . PORT ;
const isDebug : boolean = env . DEBUG ;
// ❌ TypeScript error: Type 'number' is not assignable to type 'string'
const port : string = env . PORT ;
// ❌ TypeScript error: Property 'INVALID' does not exist
const invalid = env . INVALID ;
Refactoring safety
When you change your schema, TypeScript helps you find all affected code:
// Before
const schema = {
PORT: Number ,
};
// After - renamed to SERVER_PORT
const schema = {
SERVER_PORT: Number ,
};
// TypeScript will flag all uses of `env.PORT` as errors
Using types in your application
You can export the validated environment type for use throughout your application:
import { validateEnv , type ValidatedEnv } from 'env-core' ;
export const envSchema = {
PORT: Number ,
NODE_ENV: String ,
DEBUG: { type: Boolean , default: false },
HOST: { type: String , default: '0.0.0.0' },
} as const ;
export const env = validateEnv ( envSchema );
export type Env = ValidatedEnv < typeof envSchema >;
import { env , type Env } from './env' ;
function startServer ( config : Env ) {
console . log ( `Starting server on ${ config . HOST } : ${ config . PORT } ` );
}
startServer ( env );
Type inference internals
For the curious, here’s how Env Core’s type system works:
// From src/types.ts
type SchemaType = typeof String | typeof Number | typeof Boolean ;
type InferSchemaType < T > =
T extends typeof String ? string :
T extends typeof Number ? number :
T extends typeof Boolean ? boolean :
never ;
type InferType < T > =
T extends SchemaType ? InferSchemaType < T > :
T extends EnvSchemaItem < infer U > ? U :
never ;
type ValidatedEnv < T extends EnvSchema > = {
[ K in keyof T ] : InferType < T [ K ]>
};
This type system:
Maps constructor functions to their corresponding TypeScript types
Extracts types from configuration objects
Transforms the schema object into the validated environment type
Best practices
Use `as const` for schema objects
Using as const preserves the specific types of your schema: const schema = {
PORT: Number ,
NODE_ENV: String ,
} as const ;
Export your environment type
Export the validated environment type so other parts of your application can use it: export type Env = ValidatedEnv < typeof envSchema >;
Use satisfies for type checking
Use satisfies EnvSchema to validate your schema while preserving specific types: const schema = {
PORT: Number ,
} satisfies EnvSchema ;
Avoid runtime type assertions
Trust Env Core’s validation - you don’t need runtime type assertions: // ❌ Unnecessary
const port = env . PORT as number ;
// ✅ TypeScript already knows it's a number
const port = env . PORT ;
Next steps
Express.js guide See type-safe environment validation in an Express.js app
NestJS guide Learn how to use Env Core with NestJS