@Config - Application Configuration
The @Config decorator provides a centralized way to configure your Asena application, including server options, error handling, and global middleware. It allows you to customize both Bun's native server settings and Asena-specific features in a type-safe manner.
Quick Start
import type { ConfigService, Context } from '@asenajs/ergenecore';
import type { AsenaServeOptions } from '@asenajs/asena/adapter';
import { Config } from '@asenajs/asena/server';
@Config()
export class AppConfig implements ConfigService {
public serveOptions(): AsenaServeOptions {
return {
serveOptions: {
hostname: 'localhost',
port: 3000,
development: true,
},
wsOptions: {
perMessageDeflate: true,
maxPayloadLimit: 1024 * 1024, // 1MB
},
};
}
public onError(error: Error, _context: Context) {
console.error('Application error:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}import type { ConfigService, Context } from '@asenajs/hono-adapter';
import type { AsenaServeOptions } from '@asenajs/asena/adapter';
import { Config } from '@asenajs/asena/server';
@Config()
export class AppConfig implements ConfigService {
public serveOptions(): AsenaServeOptions {
return {
serveOptions: {
hostname: 'localhost',
port: 3000,
development: true,
},
wsOptions: {
perMessageDeflate: true,
maxPayloadLimit: 1024 * 1024, // 1MB
},
};
}
public onError(error: Error, _context: Context) {
console.error('Application error:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}API Reference
@Config Decorator
Marks a class as the application configuration component. Only one @Config class is allowed per application.
function Config(params?: ComponentParams | string): ClassDecoratorParameters:
params(optional) - Configuration name or component parameters
Example:
@Config('AppConfig')
class AppConfig implements AsenaConfig {
// ...
}AsenaConfig Interface
The configuration interface that your config class should implement. All methods are optional.
interface AsenaConfig<C extends AsenaContext<any, any> = AsenaContext<any, any>> {
/**
* Configure server options
*/
serveOptions?(): AsenaServeOptions;
/**
* Custom error handler for unhandled errors
*/
onError?(error: Error, context: C): Response | Promise<Response>;
/**
* Global middleware configuration with pattern-based filtering
*/
globalMiddlewares?(): Promise<GlobalMiddlewareEntry[]> | GlobalMiddlewareEntry[];
}serveOptions() Method
Configure Bun server options and WebSocket settings. This method returns AsenaServeOptions which contains both HTTP server configuration and WebSocket-specific options.
Type Structure
interface AsenaServeOptions {
serveOptions?: AsenaServerOptions; // Bun server configuration
wsOptions?: WSOptions; // WebSocket configuration
}AsenaServerOptions
Type-safe wrapper around Bun's ServeOptions that excludes framework-managed properties:
type AsenaServerOptions = Omit<ServeOptions, 'fetch' | 'routes' | 'websocket' | 'error'>;❌ Excluded Options (Managed by AsenaJS):
| Property | Managed By | Reason |
|---|---|---|
fetch | HTTP Adapter (e.g., HonoAdapter) | Core request handler |
routes | AsenaJS decorators (@Get, @Post, etc.) | Route definitions |
websocket | AsenaWebsocketAdapter | WebSocket handler |
error | AsenaConfig.onError() | Error handling |
✅ Available Options:
| Category | Options | Description |
|---|---|---|
| Network | hostname, port, unix | Network interface binding |
| Network | reusePort, ipv6Only | Advanced networking |
| Security | tls | TLS/SSL configuration |
| Performance | maxRequestBodySize, idleTimeout | Performance tuning |
| Development | development, id | Development features |
Network Configuration
Configure network interfaces and ports.
@Config()
class AppConfig implements AsenaConfig {
public serveOptions(): AsenaServeOptions {
return {
serveOptions: {
hostname: '0.0.0.0', // Bind to all interfaces
port: 8080, // Server port
reusePort: true, // Enable load balancing across processes
ipv6Only: false, // Allow both IPv4 and IPv6
},
};
}
}Unix Socket Configuration:
public serveOptions(): AsenaServeOptions {
return {
serveOptions: {
unix: '/tmp/asena.sock', // Use Unix domain socket
},
};
}Dynamic Port (Random Available Port):
public serveOptions(): AsenaServeOptions {
return {
serveOptions: {
port: 0, // Bun will assign a random available port
},
};
}TLS/SSL Configuration
Configure HTTPS with TLS certificates.
@Config()
class AppConfig implements AsenaConfig {
public serveOptions(): AsenaServeOptions {
return {
serveOptions: {
hostname: 'mydomain.com',
port: 443,
tls: {
cert: Bun.file('/path/to/cert.pem'),
key: Bun.file('/path/to/key.pem'),
ca: Bun.file('/path/to/ca.pem'), // Optional: CA certificate
passphrase: 'secret', // Optional: Key passphrase
serverName: 'mydomain.com', // Optional: SNI server name
lowMemoryMode: false, // Optional: Reduce memory footprint
dhParamsFile: '/path/to/dhparams.pem', // Optional: DH parameters
},
},
};
}
}Multiple TLS Certificates (SNI Support):
public serveOptions(): AsenaServeOptions {
return {
serveOptions: {
tls: [
{
cert: Bun.file('/path/to/domain1-cert.pem'),
key: Bun.file('/path/to/domain1-key.pem'),
serverName: 'domain1.com',
},
{
cert: Bun.file('/path/to/domain2-cert.pem'),
key: Bun.file('/path/to/domain2-key.pem'),
serverName: 'domain2.com',
},
],
},
};
}Performance Configuration
Tune server performance and resource limits.
@Config()
class AppConfig implements AsenaConfig {
public serveOptions(): AsenaServeOptions {
return {
serveOptions: {
maxRequestBodySize: 10 * 1024 * 1024, // 10MB max body size
idleTimeout: 120, // 120 seconds idle timeout
},
};
}
}Options:
maxRequestBodySize- Maximum allowed request body size in bytes. Requests exceeding this limit will be rejected.idleTimeout- Maximum time (in seconds) a connection can remain idle before being closed. Default: 120 seconds.
Development Mode
Enable development features for better debugging.
@Config()
class AppConfig implements AsenaConfig {
public serveOptions(): AsenaServeOptions {
return {
serveOptions: {
development: process.env.NODE_ENV !== 'production',
id: 'my-app-server', // Used for hot reload identification
},
};
}
}WebSocket Configuration (wsOptions)
Configure WebSocket-specific settings for real-time communication.
interface WSOptions {
maxPayloadLimit?: number; // Maximum message size
backpressureLimit?: number; // Backpressure threshold
closeOnBackpressureLimit?: boolean; // Close on backpressure
idleTimeout?: number; // WebSocket idle timeout
publishToSelf?: boolean; // Receive own published messages
sendPings?: boolean; // Enable automatic ping frames
perMessageDeflate: // Compression configuration (required)
| boolean
| {
compress?: boolean | WebSocketCompressor;
decompress?: boolean | WebSocketCompressor;
};
}Complete WebSocket Configuration:
@Config()
class AppConfig implements AsenaConfig {
public serveOptions(): AsenaServeOptions {
return {
wsOptions: {
// Size and Buffer Limits
maxPayloadLimit: 16 * 1024 * 1024, // 16MB (default)
backpressureLimit: 1024 * 1024, // 1MB backpressure threshold
closeOnBackpressureLimit: false, // Don't auto-close on backpressure
// Timeout
idleTimeout: 120, // 120 seconds idle timeout
// Publishing
publishToSelf: false, // Don't receive own messages
// Keep-Alive
sendPings: true, // Send automatic ping frames
// Compression (required field)
perMessageDeflate: true, // Enable compression with defaults
},
};
}
}WebSocket Configuration Details:
| Option | Default | Description |
|---|---|---|
maxPayloadLimit | 16 MB | Maximum message size. Larger messages close the connection. |
backpressureLimit | 1 MB | Threshold for backpressure detection. Triggers drain event. |
closeOnBackpressureLimit | false | Whether to close connection when backpressure limit is reached. |
idleTimeout | 120 seconds | Auto-close connections exceeding idle period. |
publishToSelf | false | Whether socket receives its own published messages. |
sendPings | true | Enable automatic ping frames for keep-alive. |
perMessageDeflate | false | Enable per-message compression (reduces bandwidth). Required field. |
Advanced Compression Configuration:
public serveOptions(): AsenaServeOptions {
return {
wsOptions: {
perMessageDeflate: {
compress: true, // Enable compression
decompress: false, // Disable decompression
},
},
};
}Available compressor values:
true/false- Enable/disable with defaultsWebSocketCompressor- Custom compressor configuration
onError() Method
Custom error handler for application-wide error handling. This method is called whenever an unhandled error occurs during request processing.
onError?(error: Error, context: C): Response | Promise<Response>Parameters:
error- The error that occurredcontext- The Asena context for the current request
Returns: Response or Promise<Response>
Basic Error Handler
@Config()
class AppConfig implements AsenaConfig {
public onError(error: Error, context: AsenaContext<any, any>) {
console.error('Application error:', error);
return new Response(
JSON.stringify({ error: error.message }),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
}
}Production Error Handler
Hide sensitive error details in production.
@Config()
class AppConfig implements AsenaConfig {
public onError(error: Error, context: AsenaContext<any, any>) {
const isDevelopment = process.env.NODE_ENV !== 'production';
// Log full error details
console.error('[ERROR]', {
message: error.message,
stack: error.stack,
url: context.req.url,
});
// Return appropriate response
if (isDevelopment) {
return new Response(
JSON.stringify({
error: error.message,
stack: error.stack,
url: context.req.url,
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
}
return new Response(
JSON.stringify({ error: 'Internal Server Error' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
}
}Error Handler with Dependency Injection
Integrate with dependency injection for advanced error handling.
@Config()
class AppConfig implements AsenaConfig {
@Inject(LoggerService)
private logger!: LoggerService;
@Inject(ErrorReportingService)
private errorReporter!: ErrorReportingService;
public async onError(error: Error, context: AsenaContext<any, any>) {
// Log to logging service
await this.logger.error('Unhandled error', {
error: error.message,
stack: error.stack,
url: context.req.url,
});
// Report to external service (Sentry, etc.)
await this.errorReporter.report(error);
// Return user-friendly error
return new Response(
JSON.stringify({
error: 'Something went wrong. Please try again later.',
}),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
}
}globalMiddlewares() Method
Configure global middleware that applies to all or specific routes. Supports both simple array syntax and pattern-based filtering.
globalMiddlewares?(): Promise<GlobalMiddlewareEntry[]> | GlobalMiddlewareEntry[]Returns: Array of middleware classes or GlobalMiddlewareEntry objects
Simple Global Middleware
Apply middleware to all routes.
@Config()
class AppConfig implements AsenaConfig {
public globalMiddlewares() {
return [
LoggerMiddleware,
CorsMiddleware,
CompressionMiddleware,
];
}
}Pattern-Based Middleware
Apply middleware to specific route patterns using include and exclude filters.
@Config()
class AppConfig implements AsenaConfig {
public globalMiddlewares() {
return [
// Apply to all routes
LoggerMiddleware,
// Apply only to /api/* and /admin/* routes
{
middleware: AuthMiddleware,
routes: {
include: ['/api/*', '/admin/*'],
},
},
// Apply to all routes except /health and /metrics
{
middleware: RateLimitMiddleware,
routes: {
exclude: ['/health', '/metrics'],
},
},
// Complex pattern: only /api/* but not /api/public/*
{
middleware: JwtMiddleware,
routes: {
include: ['/api/*'],
exclude: ['/api/public/*'],
},
},
];
}
}GlobalMiddlewareEntry
type GlobalMiddlewareEntry =
| MiddlewareClass
| {
middleware: MiddlewareClass;
routes?: {
include?: string[]; // Glob patterns to include
exclude?: string[]; // Glob patterns to exclude
};
};Pattern Matching:
*- Matches any characters except/**- Matches any characters including//api/*- Matches/api/usersbut not/api/v1/users/api/**- Matches/api/usersand/api/v1/users
Execution Order
Middleware executes in the order defined in the array:
public globalMiddlewares() {
return [
LoggerMiddleware, // 1. Runs first
AuthMiddleware, // 2. Runs second
ValidationMiddleware, // 3. Runs third
RateLimitMiddleware, // 4. Runs last
];
}Async Global Middleware
The method can return a Promise for async configuration.
@Config()
class AppConfig implements AsenaConfig {
@Inject(ConfigService)
private configService!: ConfigService;
public async globalMiddlewares() {
const config = await this.configService.load();
const middlewares = [LoggerMiddleware];
if (config.enableAuth) {
middlewares.push(AuthMiddleware);
}
if (config.enableRateLimit) {
middlewares.push(RateLimitMiddleware);
}
return middlewares;
}
}Complete Example
A real-world configuration example combining all features:
import { Config, Inject, Service } from '@asenajs/asena/server';
import type { ConfigService, Context } from '@asenajs/ergenecore';
import type { AsenaServeOptions } from '@asenajs/asena/adapter';
@Service()
class LoggerService {
public error(message: string, meta: any) {
console.error(`[ERROR] ${message}`, meta);
}
}
@Config()
export class AppConfig implements ConfigService {
@Inject(LoggerService)
private logger!: LoggerService;
public serveOptions(): AsenaServeOptions {
const isProduction = process.env.NODE_ENV === 'production';
return {
serveOptions: {
hostname: process.env.HOSTNAME || '0.0.0.0',
port: parseInt(process.env.PORT || '3000', 10),
development: !isProduction,
maxRequestBodySize: 10 * 1024 * 1024, // 10MB
idleTimeout: isProduction ? 30 : 120,
tls: isProduction
? {
cert: Bun.file(process.env.TLS_CERT!),
key: Bun.file(process.env.TLS_KEY!),
}
: undefined,
},
wsOptions: {
perMessageDeflate: isProduction,
maxPayloadLimit: 5 * 1024 * 1024, // 5MB
backpressureLimit: 1024 * 1024, // 1MB
closeOnBackpressureLimit: false,
idleTimeout: 120,
sendPings: true,
publishToSelf: false,
},
};
}
public async onError(error: Error, context: Context) {
await this.logger.error('Unhandled error', {
error: error.message,
stack: error.stack,
url: context.getUrl(),
});
const isProduction = process.env.NODE_ENV === 'production';
if (isProduction) {
return context.send({ error: 'Internal Server Error' }, 500);
}
return context.send({
error: error.message,
stack: error.stack,
}, 500);
}
public globalMiddlewares() {
return [
LoggerMiddleware,
{
middleware: AuthMiddleware,
routes: {
include: ['/api/**'],
exclude: ['/api/public/**'],
},
},
{
middleware: RateLimitMiddleware,
routes: {
exclude: ['/health'],
},
},
];
}
}import { Config, Inject, Service } from '@asenajs/asena/server';
import type { ConfigService, Context } from '@asenajs/hono-adapter';
import type { AsenaServeOptions } from '@asenajs/asena/adapter';
@Service()
class LoggerService {
public error(message: string, meta: any) {
console.error(`[ERROR] ${message}`, meta);
}
}
@Config()
export class AppConfig implements ConfigService {
@Inject(LoggerService)
private logger!: LoggerService;
public serveOptions(): AsenaServeOptions {
const isProduction = process.env.NODE_ENV === 'production';
return {
serveOptions: {
hostname: process.env.HOSTNAME || '0.0.0.0',
port: parseInt(process.env.PORT || '3000', 10),
development: !isProduction,
maxRequestBodySize: 10 * 1024 * 1024, // 10MB
idleTimeout: isProduction ? 30 : 120,
tls: isProduction
? {
cert: Bun.file(process.env.TLS_CERT!),
key: Bun.file(process.env.TLS_KEY!),
}
: undefined,
},
wsOptions: {
perMessageDeflate: isProduction,
maxPayloadLimit: 5 * 1024 * 1024, // 5MB
backpressureLimit: 1024 * 1024, // 1MB
closeOnBackpressureLimit: false,
idleTimeout: 120,
sendPings: true,
publishToSelf: false,
},
};
}
public async onError(error: Error, context: Context) {
await this.logger.error('Unhandled error', {
error: error.message,
stack: error.stack,
url: context.req.url,
});
const isProduction = process.env.NODE_ENV === 'production';
if (isProduction) {
return context.json({ error: 'Internal Server Error' }, 500);
}
return context.json({
error: error.message,
stack: error.stack,
}, 500);
}
public globalMiddlewares() {
return [
LoggerMiddleware,
{
middleware: AuthMiddleware,
routes: {
include: ['/api/**'],
exclude: ['/api/public/**'],
},
},
{
middleware: RateLimitMiddleware,
routes: {
exclude: ['/health'],
},
},
];
}
}Technical Details
Bootstrap Lifecycle
The @Config decorator is processed during the application bootstrap sequence:
- Phase: CONTAINER_INIT - IoC container initializes
- Phase: IOC_ENGINE_INIT - Component discovery begins
- Phase: USER_COMPONENTS_SCAN - Config class is discovered and registered
- Phase: USER_COMPONENTS_INIT - Config instance is created
- Phase: APPLICATION_SETUP - Config methods are applied:
serveOptions()is called and passed to adapteronError()is registered as error handlerglobalMiddlewares()is called and middleware are registered
- Phase: SERVER_READY - Server starts with applied configuration
Singleton Validation
AsenaJS enforces a single @Config instance per application. If multiple @Config classes are detected, an error is thrown during bootstrap.
// ✅ Valid: Single config
@Config()
class AppConfig implements AsenaConfig { }
// ❌ Invalid: Multiple configs (throws error during bootstrap)
@Config()
class AppConfig1 implements AsenaConfig { }
@Config()
class AppConfig2 implements AsenaConfig { } // Error!Dependency Injection in Config
Config classes are regular components in the IoC container, so you can use @Inject to inject other services:
@Config()
class AppConfig implements AsenaConfig {
@Inject(DatabaseService)
private db!: DatabaseService;
@Inject(LoggerService)
private logger!: LoggerService;
public async serveOptions(): Promise<AsenaServeOptions> {
const settings = await this.db.getSettings();
return {
serveOptions: {
port: settings.port,
hostname: settings.hostname,
},
};
}
}Async Configuration
When using async operations in serveOptions(), the method can return Promise<AsenaServeOptions>.
Best Practices
✅ Do's
Use Environment Variables
typescriptport: parseInt(process.env.PORT || '3000', 10), hostname: process.env.HOSTNAME || 'localhost',Separate Development and Production Configuration
typescriptconst isProduction = process.env.NODE_ENV === 'production'; development: !isProduction, idleTimeout: isProduction ? 30 : 120,Keep Config Simple
- Config should focus on configuration, not business logic
- Use services for complex logic, inject them if needed
Enable Compression in Production
typescriptwsOptions: { perMessageDeflate: process.env.NODE_ENV === 'production', }Log Errors Properly
typescriptpublic onError(error: Error, context: AsenaContext<any, any>) { console.error('[ERROR]', { message: error.message, stack: error.stack, url: context.req.url, }); // Return response... }
❌ Don'ts
Don't Create Multiple @Config Classes
typescript// ❌ Wrong: Only one @Config allowed per application @Config() class Config1 { } @Config() class Config2 { } // Error!Don't Try to Set Framework-Managed Properties
typescript// ❌ Wrong: These cause TypeScript compile errors serveOptions: { fetch: () => new Response(), // Compile error! routes: { '/': new Response() }, // Compile error! websocket: { /* ... */ }, // Compile error! }Don't Hardcode Secrets
typescript// ❌ Wrong: Never hardcode secrets tls: { key: Bun.file('/path/to/key.pem'), passphrase: 'mySecretPassword123', // Use env vars! }Don't Forget perMessageDeflate When Using WebSockets
typescript// ❌ Wrong: perMessageDeflate is required in WSOptions wsOptions: { maxPayloadLimit: 1024, // Missing perMessageDeflate! } // ✅ Correct wsOptions: { perMessageDeflate: true, maxPayloadLimit: 1024, }
Troubleshooting
"Only one config instance is allowed"
Error: Multiple @Config classes are defined in your application.
Solution: Keep only one @Config class. Use conditionals for environment-specific configs:
@Config()
class AppConfig implements AsenaConfig {
public serveOptions(): AsenaServeOptions {
if (process.env.NODE_ENV === 'production') {
return { /* production config */ };
}
return { /* development config */ };
}
}Type Errors with serveOptions
Error: TypeScript complains about fetch, routes, websocket, or error properties.
Solution: These properties are excluded from AsenaServerOptions:
// ❌ Wrong: TypeScript will show errors
serveOptions: {
fetch: () => new Response(), // Not allowed!
}
// ✅ Correct
serveOptions: {
port: 3000,
hostname: 'localhost',
tls: { /* ... */ },
}WebSocket Configuration Not Working
Problem: WebSocket options don't seem to apply.
Solution: Use wsOptions, not serveOptions.websocket:
// ❌ Wrong
{
serveOptions: {
websocket: { /* ... */ }, // Not allowed!
}
}
// ✅ Correct
{
wsOptions: {
perMessageDeflate: true,
maxPayloadLimit: 1024 * 1024,
}
}Related Documentation
- Middleware - Learn about middleware patterns
- Error Handling - Advanced error handling strategies
- WebSocket - WebSocket implementation guide
- CLI Configuration - Asena CLI build configuration
Next Steps:
- Learn about Middleware patterns
- Explore Error Handling strategies
- Set up WebSocket communication