@proventuslabs/nestjs-zod
v2.0.1
Published
A collection of NestJS modules to integrate Zod into your application.
Downloads
2,954
Readme
@proventuslabs/nestjs-zod
A collection of NestJS modules to integrate Zod v4 into your application with enhanced configuration management and type safety.
✨ Features
- 🔧 Zod-powered Configuration: Type-safe configuration management using Zod schemas (v4)
- 🌍 Environment Variable Support: Automatic parsing and validation of environment variables
- 🏗️ Configurable Modules: Enhanced NestJS configurable modules with Zod validation
- 🎯 Type Safety: Full TypeScript support with automatic type inference
- 🔄 Merge Strategy: Environment variables override schema defaults
- 📝 Descriptive Errors: Enhanced error messages with schema descriptions
📦 Installation
npm install @proventuslabs/nestjs-zod zodPeer Dependencies:
@nestjs/common^10.0.0 || ^11.0.0@nestjs/config^3.0.0 || ^4.0.0zod^3.25.0 || ^4.0.0
🎯 Quick Start
import { Module, Injectable } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import {
registerConfig,
type NamespacedConfigType,
} from "@proventuslabs/nestjs-zod";
import { z } from "zod";
// Define configuration schema
const appConfig = registerConfig(
"app",
z.object({
port: z.coerce.number<string | undefined>().default(3000).describe("Server port"),
host: z.string().default("localhost").describe("Server host"),
apiKey: z.string().describe("API key for external services"),
}),
{
whitelistKeys: new Set(["API_KEY"]), // Allow API_KEY without APP_ prefix
}
);
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [appConfig],
}),
],
})
export class AppModule {}
@Injectable()
export class AppService {
constructor(
private configService: ConfigService<
NamespacedConfigType<typeof appConfig>,
true
>
) {}
getAppConfig() {
return {
port: this.configService.get("app.port", { infer: true }),
host: this.configService.get("app.host", { infer: true }),
apiKey: this.configService.get("app.apiKey", { infer: true }),
};
}
}🔧 Configuration and Options
It comes out of the box with the support for env variables.
Priority order: Environment Variables > Schema Defaults
Environment Variables
Variables are automatically parsed using the namespace prefix:
# Standard namespaced variables
APP_PORT=8080
APP_HOST=0.0.0.0
# Nested objects using double underscores
APP_DATABASE__HOST=localhost
APP_DATABASE__PORT=5432Schema defaults
// Multiple configuration sources
const dbConfig = registerConfig(
"database",
z.object({
host: z.string().default("localhost"),
port: z.coerce.number<string | undefined>().default(5432),
ssl: z.coerce.boolean<string | undefined>().default(false),
})
);📋 API Reference
registerConfig
Registers a type-safe configuration with automatic validation and environment variable parsing.
registerConfig(namespace, zodSchema, options?)Parameters:
namespace: Configuration namespace (camelCase)zodSchema: Zod schema for validationoptions.whitelistKeys: Environment variables to allow without namespace prefixoptions.variables: Environment variables to use (defaultsprocess.env)
Whitelist Keys
By default, only environment variables with the namespace prefix are processed. You can whitelist additional environment variables to bypass this restriction:
const appConfig = registerConfig(
"app",
z.object({
port: z.coerce.number<string | undefined>().default(3000),
apiKey: z.string().describe("API key from environment"),
}),
{
whitelistKeys: new Set(["API_KEY"]), // Allow API_KEY without APP_ prefix
}
);API_KEY=your-secret-key # Will be mapped to app.apiKey
APP_PORT=8080 # Standard namespaced variableZodConfigurableModuleBuilder
Create type-safe configurable modules with Zod validation:
import { Module } from "@nestjs/common";
import { ZodConfigurableModuleBuilder } from "@proventuslabs/nestjs-zod";
import { z } from "zod";
const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ZodConfigurableModuleBuilder(
z.object({
apiKey: z.string(),
baseUrl: z.url().default("https://api.example.com"),
timeout: z.number().default(5000),
})
).build();
@Module({
providers: [
{
provide: "EXTERNAL_SERVICE",
useFactory: (options) => new ExternalService(options), // fully typed
inject: [MODULE_OPTIONS_TOKEN],
},
],
})
export class ExternalServiceModule extends ConfigurableModuleClass {}
// Usage
ExternalServiceModule.register({
apiKey: "your-key",
timeout: 10000,
});Offers identical API to the standard NestJS ConfigurableModuleBuilder, consult their docs for usage.
🚨 Error Handling
Enhanced validation errors with detailed information:
- Schema descriptions and validation paths
- Original environment variable names
- Uses Zod errors under the hood
🎭 Types
// Configuration type extraction
type AppConfig = ConfigType<typeof appConfig>;
// Namespaced configuration for ConfigService
type NamespacedConfig = NamespacedConfigType<typeof appConfig>;
// Usage with ConfigService
constructor(
private configService: ConfigService<NamespacedConfig, true>,
@Inject(appConfig.KEY)
private appConfigObject: AppConfig,
) {}⚡ Best Practices
- Use descriptive schema descriptions for better error messages
- Leverage whitelist keys for common environment variables
- Combine multiple configuration namespaces for organized settings
- Utilize schema defaults to reduce required environment variables
// ✅ Good: Descriptive and well-structured
const appConfig = registerConfig(
"app",
z.object({
port: z.coerce.number<string | undefined>().default(3000).describe("HTTP server port"),
apiKey: z.string().describe("Third-party API authentication key"),
}),
{ whitelistKeys: new Set(["API_KEY"]) }
);
// ✅ Good: Multiple namespaces for organization
const dbConfig = registerConfig("database", dbSchema);
const redisConfig = registerConfig("redis", redisSchema);🤝 Contributing
We welcome contributions! Please see our Contributing Guidelines for details.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
