Middlewares Reference
IGNIS provides a collection of built-in middlewares for common application needs including error handling, request logging, and favicon serving.
Files:
packages/core/src/base/middlewares/*.ts
Prerequisites
- Hono Middleware basics
- IGNIS Application basics
- Basic understanding of HTTP request/response lifecycle
Quick Reference
| Middleware | Purpose | Key Options |
|---|---|---|
appErrorHandler | Catches and formats application errors | logger |
notFoundHandler | Handles 404 Not Found responses | logger |
RequestSpyMiddleware | Logs request lifecycle, timing, and parses request body | None |
emojiFavicon | Serves an emoji as favicon | icon |
Table of Contents
- Error Handler (
appErrorHandler) - Not Found Handler (
notFoundHandler) - Request Spy (
RequestSpyMiddleware) - Emoji Favicon
- Creating Custom Middleware
- Middleware Order & Priority
- See Also
Built-in Middlewares
Error Handler (appErrorHandler)
The error handler middleware catches all unhandled errors in your application and formats them into consistent JSON responses.
File: packages/core/src/base/middlewares/app-error.middleware.ts
Features
- Automatic Error Formatting: Converts all errors to structured JSON responses
- ZodError Support: Special handling for Zod validation errors with detailed field-level messages
- Database Error Handling: Automatically returns 400 for database constraint violations (unique, foreign key, not null, etc.)
- Environment-Aware: Hides stack traces and error causes in production
- Request Tracking: Includes
requestIdfor debugging and tracing - Status Code Detection: Automatically extracts
statusCodefrom errors
Usage
import { appErrorHandler } from '@venizia/ignis';
const app = new IgnisApplication({
// ...
});
// Register error handler
app.onError(appErrorHandler({
logger: app.logger
}));Error Response Format
Standard Error:
{
"message": "Something went wrong",
"statusCode": 500,
"requestId": "abc123",
"details": {
"url": "http://localhost:3000/api/users",
"path": "/api/users",
"stack": "Error: Something went wrong\n at ...", // development only
"cause": { ... } // development only
}
}Validation Error (ZodError):
{
"message": "ValidationError",
"statusCode": 422,
"requestId": "abc123",
"details": {
"url": "http://localhost:3000/api/users",
"path": "/api/users",
"stack": "...", // development only
"cause": [
{
"path": "email",
"message": "Invalid email address",
"code": "invalid_string",
"expected": "string",
"received": "undefined"
}
]
}
}Database Constraint Error:
Database constraint violations (unique, foreign key, not null, check) are automatically detected and returned as 400 Bad Request with a human-readable message:
{
"message": "Unique constraint violation\nDetail: Key (email)=(test@example.com) already exists.\nTable: User\nConstraint: UQ_User_email",
"statusCode": 400,
"requestId": "abc123",
"details": {
"url": "http://localhost:3000/api/users",
"path": "/api/users",
"stack": "...", // development only
"cause": { ... } // development only
}
}Supported PostgreSQL Error Codes:
| Code | Error Type |
|---|---|
| 23505 | Unique constraint violation |
| 23503 | Foreign key constraint violation |
| 23502 | Not null constraint violation |
| 23514 | Check constraint violation |
| 23P01 | Exclusion constraint violation |
| 22P02 | Invalid text representation |
| 22003 | Numeric value out of range |
| 22001 | String data too long |
API Reference
appErrorHandler(options)
Parameters:
| Option | Type | Required | Description |
|---|---|---|---|
logger | ApplicationLogger | Yes | Logger instance for error logging |
Returns: ErrorHandler - Hono error handler function
Common Patterns
// Custom error with status code
class NotFoundError extends Error {
statusCode = 404;
constructor(message: string) {
super(message);
this.name = 'NotFoundError';
}
}
// Throw in controller
const GetUserConfig = {
method: HTTP.Methods.GET,
path: '/users/:id',
request: {
params: z.object({ id: z.string() }),
},
responses: jsonResponse({
schema: z.object({ id: z.string(), name: z.string() }),
}),
} as const;
@get({ configs: GetUserConfig })
async getUser(c: TRouteContext) {
const { id } = c.req.valid<{ id: string }>('param');
const user = await this.userRepository.findById(id);
if (!user) {
throw new NotFoundError(`User ${id} not found`);
}
return c.json(user, HTTP.ResultCodes.RS_2.Ok);
}Not Found Handler (notFoundHandler)
Handles requests to routes that don't exist, returning a standardized 404 response.
File: packages/core/src/base/middlewares/not-found.middleware.ts
Usage
import { notFoundHandler } from '@venizia/ignis';
const app = new IgnisApplication({
// ...
});
// Register 404 handler
app.notFound(notFoundHandler({
logger: app.logger
}));Response Format
{
"message": "URL NOT FOUND",
"path": "/api/nonexistent",
"url": "http://localhost:3000/api/nonexistent"
}Status Code: 404 Not Found
API Reference
notFoundHandler(options)
Parameters:
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
logger | ApplicationLogger | No | console | Logger instance for logging 404s |
Returns: NotFoundHandler - Hono not found handler function
Request Spy (RequestSpyMiddleware)
Logs detailed information about each request including timing, IP address, method, path, query parameters, and request body. Also handles request body parsing for JSON, form data, and text content types.
File: packages/core/src/base/middlewares/request-spy.middleware.ts
Features
- Request lifecycle logging (incoming/outgoing)
- Performance timing tracking
- IP address extraction (supports
x-real-ipandx-forwarded-forheaders) - Request ID tracking
- Query and body parameter logging (body only logged in non-production)
- Request body parsing: Automatically parses and caches request bodies:
application/json→req.json()multipart/form-data,application/x-www-form-urlencoded→req.parseBody()- Other content types (text, html, xml) →
req.text()
Usage
import { RequestSpyMiddleware } from '@venizia/ignis';
const app = new IgnisApplication({
// ...
});
// Create and register spy middleware
const requestSpy = new RequestSpyMiddleware();
app.use(requestSpy.value());Log Output
Request Start:
[spy][abc123] START | Handling Request | forwardedIp: 192.168.1.1 | path: /api/users | method: GETRequest Complete:
[spy][abc123] DONE | Handling Request | forwardedIp: 192.168.1.1 | path: /api/users | method: GET | Took: 45.23 (ms)API Reference
RequestSpyMiddleware
Class Methods:
| Method | Returns | Description |
|---|---|---|
value() | MiddlewareHandler | Returns the middleware handler |
Static Properties:
| Property | Type | Value | Description |
|---|---|---|---|
REQUEST_ID_KEY | string | 'requestId' | Context key for request ID |
Accessing Request ID
import { RequestSpyMiddleware, get, HTTP, jsonResponse, TRouteContext, z } from '@venizia/ignis';
const ExampleConfig = {
method: HTTP.Methods.GET,
path: '/example',
responses: jsonResponse({
schema: z.object({ requestId: z.string() }),
}),
} as const;
// In a controller
@get({ configs: ExampleConfig })
async example(c: TRouteContext) {
const requestId = c.get(RequestSpyMiddleware.REQUEST_ID_KEY);
console.log('Request ID:', requestId);
return c.json({ requestId }, HTTP.ResultCodes.RS_2.Ok);
}Performance Impact
Request spy logs every request detail. Consider disabling or reducing verbosity in production environments with high traffic.
Emoji Favicon
Serves an SVG emoji as the application's favicon, providing a lightweight alternative to traditional favicon files.
File: packages/core/src/base/middlewares/emoji-favicon.middleware.ts
Usage
import { emojiFavicon } from '@venizia/ignis';
const app = new IgnisApplication({
// ...
});
// Serve a rocket emoji as favicon
app.use(emojiFavicon({ icon: '🚀' }));How It Works
- Intercepts requests to
/favicon.ico - Returns an inline SVG with the specified emoji
- Sets
Content-Type: image/svg+xml - All other requests pass through unchanged
API Reference
emojiFavicon(options)
Parameters:
| Option | Type | Required | Description |
|---|---|---|---|
icon | string | Yes | Emoji character to use as favicon |
Returns: MiddlewareHandler - Hono middleware function
Examples
// Different emoji icons
app.use(emojiFavicon({ icon: '🔥' })); // Fire
app.use(emojiFavicon({ icon: '⚡' })); // Lightning
app.use(emojiFavicon({ icon: '🎯' })); // Target
app.use(emojiFavicon({ icon: '🌟' })); // StarBrowser Support
SVG favicons are supported in all modern browsers. Fallback to a traditional .ico file if you need to support legacy browsers.
Creating Custom Middleware
IGNIS uses Hono's middleware system. Create custom middleware using the createMiddleware factory:
Basic Middleware
import { createMiddleware } from 'hono/factory';
import type { MiddlewareHandler } from 'hono';
export const myMiddleware = (): MiddlewareHandler => {
return createMiddleware(async (context, next) => {
// Before request handling
console.log('Before:', context.req.path);
await next();
// After request handling
console.log('After:', context.req.path);
});
};Middleware with Options
interface MyMiddlewareOptions {
enabled: boolean;
prefix?: string;
}
export const myMiddleware = (opts: MyMiddlewareOptions): MiddlewareHandler => {
const { enabled, prefix = 'LOG' } = opts;
return createMiddleware(async (context, next) => {
if (enabled) {
console.log(`[${prefix}]`, context.req.path);
}
await next();
});
};
// Usage
app.use(myMiddleware({ enabled: true, prefix: 'API' }));Provider-Based Middleware
For middleware requiring dependency injection:
import { BaseHelper } from '@venizia/ignis-helpers';
import { IProvider } from '@venizia/ignis-inversion';
import { injectable } from '@venizia/ignis-inversion';
import { createMiddleware } from 'hono/factory';
import type { MiddlewareHandler } from 'hono';
@injectable()
export class MyMiddleware extends BaseHelper implements IProvider<MiddlewareHandler> {
constructor() {
super({ scope: MyMiddleware.name });
}
value(): MiddlewareHandler {
return createMiddleware(async (context, next) => {
this.logger.info('Processing request:', context.req.path);
await next();
});
}
}
// Usage
const myMiddleware = app.get(MyMiddleware);
app.use(myMiddleware.value());Middleware Order & Priority
Middleware execution order matters. Follow these guidelines:
Recommended Order
const app = new IgnisApplication({ /* ... */ });
// 1. CORS (if needed)
app.use(cors());
// 2. Request ID generation
app.use(requestId());
// 3. Request spy/logging (also handles body parsing)
const requestSpy = new RequestSpyMiddleware();
app.use(requestSpy.value());
// 4. Security middleware (helmet, etc.)
app.use(helmet());
// 5. Rate limiting
app.use(rateLimit());
// 6. Authentication
app.use('/api/*', authenticate());
// 7. Favicon (can be early or late)
app.use(emojiFavicon({ icon: '🚀' }));
// 8. Application routes
app.mountControllers();
// 9. Error handler (LAST in chain)
app.onError(appErrorHandler({ logger: app.logger }));
// 10. Not found handler (AFTER error handler)
app.notFound(notFoundHandler({ logger: app.logger }));Key Principles
- Request ID First: Generate request ID before logging
- Request Spy Early: Log and parse request bodies before business logic
- Security Middleware Before Routes: Protect routes with security checks
- Error Handler Last: Catch all errors from previous middleware
- 404 Handler After Error Handler: Ensure unhandled routes return 404
Order Matters
Placing error handler before routes will prevent it from catching route errors. Always register error handlers last.
Common Patterns
Conditional Middleware
const app = new IgnisApplication({ /* ... */ });
// Enable request spy only in development
if (process.env.NODE_ENV === 'development') {
const requestSpy = new RequestSpyMiddleware();
app.use(requestSpy.value());
}Route-Specific Middleware
// Apply middleware to specific routes
app.use('/api/admin/*', adminAuthMiddleware());
app.use('/api/public/*', rateLimitMiddleware());Middleware Composition
// Combine multiple middleware
const apiMiddleware = (): MiddlewareHandler => {
return createMiddleware(async (context, next) => {
// Run multiple middleware in sequence
await rateLimit()(context, async () => {
await authenticate()(context, next);
});
});
};Performance Considerations
Request Spy in Production
Request spy logs detailed information for every request. In high-traffic production environments:
// Conditional request spy
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment) {
const requestSpy = new RequestSpyMiddleware();
app.use(requestSpy.value());
}Error Logging Volume
Error handlers log every error. For high error rates, consider:
- Sampling (log 1 in N errors)
- Error aggregation services (Sentry, Rollbar)
- Rate-limited logging
See Also
Related References:
- Application - Application setup and configuration
- Controllers - HTTP routing and request handling
- Dependency Injection - DI container and providers
Guides:
Best Practices:
External Resources: