Configuration
MiiaJS provides a ConfigModule for loading and validating environment variables with any ZodLike schema.
Setup
import { Module } from '@miiajs/core'
import { ConfigModule } from '@miiajs/config'
import { z } from 'zod'
const EnvSchema = z.object({
PORT: z.string().default('3000'),
DATABASE_URL: z.string(),
JWT_SECRET: z.string(),
DEBUG: z.string().default('false'),
})
@Module({
imports: [
ConfigModule.configure({ schema: EnvSchema }),
],
})
class AppModule {}
ConfigModule.configure() validates process.env against your schema at startup. If validation fails, the application throws with detailed error messages.
Using ConfigService
Inject ConfigService to access validated values:
import { Injectable, inject } from '@miiajs/core'
import { ConfigService } from '@miiajs/config'
@Injectable()
class DatabaseService {
private configService = inject(ConfigService)
connect() {
const url = this.configService.getOrThrow('DATABASE_URL')
const port = this.configService.get('PORT') ?? '3000'
// ...
}
}
API
| Method | Returns | On missing key |
|---|---|---|
get(key) | T[K] | undefined | Returns undefined |
getOrThrow(key) | T[K] | Throws Error |
Factory configuration
Use a factory function to resolve config from DI:
import { DrizzleModule } from '@miiajs/drizzle'
@Module({
imports: [
ConfigModule.configure({ schema: EnvSchema }),
DrizzleModule.configure((resolve) => {
const config = resolve(ConfigService)
return {
dialect: 'postgres',
connection: { url: config.getOrThrow('DATABASE_URL') },
}
}),
],
})
class AppModule {}
The resolve function gives access to the DI container, allowing modules to depend on each other's configuration.
Custom env source
By default, ConfigModule reads from process.env. You can pass a custom source:
ConfigModule.configure({
schema: EnvSchema,
env: {
PORT: '8080',
DATABASE_URL: 'postgres://localhost:5432/test',
JWT_SECRET: 'test-secret',
},
})
This is useful for testing or when loading from a custom source.
Schema requirements
The schema must implement a safeParse() method (ZodLike interface):
interface ZodLike<T = any> {
safeParse(data: unknown):
| { success: true; data: T }
| { success: false; error: { issues: { message: string; path?: (string | number)[] }[] } }
}
@miiajs/config does not declare zod as a peer dependency - bring your own validator. Any library or custom object that exposes a safeParse method matching the shape above works.
Using non-Zod validators
Zod implements ZodLike natively, but you can plug in any modern validator. The two most common alternatives:
Valibot
Valibot's safeParse returns { success, output, issues } - one tiny adapter:
import * as v from 'valibot'
import { ConfigModule } from '@miiajs/config'
const schema = v.object({
PORT: v.pipe(v.string(), v.transform(Number), v.number()),
DATABASE_URL: v.string(),
})
ConfigModule.configure({
schema: {
safeParse: (data) => {
const r = v.safeParse(schema, data)
return r.success
? { success: true, data: r.output }
: { success: false, error: { issues: r.issues.map((i) => ({ message: i.message, path: i.path?.map((p) => p.key) })) } }
},
},
})
ArkType
ArkType's type(...) already returns either the parsed value or an error array - similar shape, similar adapter:
import { type } from 'arktype'
import { ConfigModule } from '@miiajs/config'
const schema = type({
PORT: 'string.numeric.parse',
DATABASE_URL: 'string',
})
ConfigModule.configure({
schema: {
safeParse: (data) => {
const r = schema(data)
return r instanceof type.errors
? { success: false, error: { issues: r.map((e) => ({ message: e.message, path: e.path })) } }
: { success: true, data: r }
},
},
})
Hand-rolled
For simple cases you don't need a library at all:
ConfigModule.configure({
schema: {
safeParse: (data: any) => {
if (!data.DATABASE_URL) {
return { success: false, error: { issues: [{ message: 'DATABASE_URL is required', path: ['DATABASE_URL'] }] } }
}
return { success: true, data: { ...data, PORT: Number(data.PORT ?? 3000) } }
},
},
})