Helpers
HonestJS provides several powerful helper functions that enable you to extend the framework's functionality. These helpers allow you to create custom decorators, standardize error responses, and build reusable components that integrate seamlessly with the framework's architecture.
Error Response Helper
The createErrorResponse
helper function provides a standardized way to format error responses across your application, ensuring consistency and proper HTTP status handling.
Function Signature
function createErrorResponse(
exception: Error,
context: Context,
options?: {
status?: number
title?: string
detail?: string
code?: string
additionalDetails?: Record<string, any>
}
): { response: ErrorResponse; status: ContentfulStatusCode }
Features
- Automatic Status Detection: Extracts HTTP status codes from various exception types
- Request Context Integration: Includes request path, timestamp, and request ID
- Environment-Aware: Shows stack traces in development mode and hides them in production
- HTTPException Support: Built-in support for Hono's HTTPException
- Flexible Overrides: Allows customization of status, message, and additional details
Usage Examples
Basic Error Handling:
import { createErrorResponse, type IFilter } from 'honestjs'
import type { Context } from 'hono'
export class AllExceptionsFilter implements IFilter {
catch(exception: Error, context: Context): Response {
const { response, status } = createErrorResponse(exception, context)
console.log(`[Error]: ${exception.message}`)
return context.json(response, status)
}
}
Custom Error with Overrides:
export class ValidationFilter implements IFilter {
catch(exception: ValidationError, context: Context): Response {
const { response, status } = createErrorResponse(exception, context, {
status: 422,
title: 'Validation Failed',
detail: 'The submitted data failed validation checks',
code: 'VALIDATION_ERROR',
additionalDetails: {
fields: exception.errors,
validationRules: exception.rules,
},
})
return context.json(response, status)
}
}
HTTP Exception Handling:
import { HTTPException } from 'hono/http-exception'
export class HttpExceptionFilter implements IFilter {
catch(exception: HTTPException, context: Context): Response {
const { response, status } = createErrorResponse(exception, context, {
code: 'HTTP_EXCEPTION',
})
// Status and message automatically extracted from HTTPException
return context.json(response, status)
}
}
Response Format
The helper generates a standardized error response structure:
interface ErrorResponse {
status: number // HTTP status code
message: string // Error message
timestamp: string // ISO timestamp
path: string // Request path
requestId?: string // Request ID (if available)
code?: string // Error code
details?: any // Additional details
detail?: string // Detailed description
}
HTTP Method Decorator Helper
The createHttpMethodDecorator
helper allows you to create custom HTTP method decorators for any HTTP verb, including non-standard methods.
Function Signature
function createHttpMethodDecorator(method: string): (path?: string, options?: HttpMethodOptions) => MethodDecorator
Parameters
method
: The HTTP method string (e.g., 'GET', 'POST', 'PROPFIND', 'PURGE')path
: Optional route path (defaults to empty string)options
: Optional configuration object with version and prefix settings
Usage Examples
Creating Standard HTTP Methods:
import { createHttpMethodDecorator } from 'honestjs'
// Create standard REST decorators
export const Get = createHttpMethodDecorator('GET')
export const Post = createHttpMethodDecorator('POST')
export const Put = createHttpMethodDecorator('PUT')
export const Delete = createHttpMethodDecorator('DELETE')
export const Patch = createHttpMethodDecorator('PATCH')
Creating WebDAV Methods:
import { HttpMethod } from 'http-essentials'
export const PropFind = createHttpMethodDecorator(HttpMethod.PROPFIND)
export const PropPatch = createHttpMethodDecorator(HttpMethod.PROPPATCH)
export const MkCol = createHttpMethodDecorator(HttpMethod.MKCOL)
export const Copy = createHttpMethodDecorator(HttpMethod.COPY)
export const Move = createHttpMethodDecorator(HttpMethod.MOVE)
export const Lock = createHttpMethodDecorator(HttpMethod.LOCK)
export const Unlock = createHttpMethodDecorator(HttpMethod.UNLOCK)
Creating Custom HTTP Methods:
// Create decorators for HTTP extension methods
export const Head = createHttpMethodDecorator('HEAD')
export const Connect = createHttpMethodDecorator('CONNECT')
export const Trace = createHttpMethodDecorator('TRACE')
export const Purge = createHttpMethodDecorator('PURGE')
export const Search = createHttpMethodDecorator('SEARCH')
export const Report = createHttpMethodDecorator('REPORT')
Using Custom Methods in Controllers:
import { Controller, Param, Header } from 'honestjs'
import { PropFind, Copy, Purge } from './decorators/webdav.decorators'
@Controller('/webdav')
export class WebDAVController {
@PropFind('/*')
async propFind(@Param('*') path: string) {
// Handle PROPFIND requests for WebDAV
return this.webdavService.getProperties(path)
}
@Copy('/*')
async copyResource(@Param('*') sourcePath: string, @Header('Destination') destination: string) {
// Handle COPY requests
return this.webdavService.copyResource(sourcePath, destination)
}
@Purge('/cache/*')
async purgeCache(@Param('*') cachePath: string) {
// Handle cache purge requests
return this.cacheService.purge(cachePath)
}
}
With Versioning and Prefixes:
// Create versioned API methods
export const GetV2 = createHttpMethodDecorator('GET')
@Controller('/api')
export class ApiController {
@GetV2('/users', { version: 'v2', prefix: '/api' })
async getUsersV2() {
// This will handle GET /api/v2/users
return this.userService.findAllV2()
}
}
Parameter Decorator Helper
The createParamDecorator
helper enables you to create custom parameter decorators that extract and transform data from the request context.
Function Signature
function createParamDecorator<T = any>(
type: string,
factory?: (data: any, ctx: Context) => T
): (data?: any) => ParameterDecorator
Parameters
type
: Unique identifier for the parameter typefactory
: Optional transformation function that receives decorator data and Hono context
Usage Examples
Built-in Parameter Decorators:
import { createParamDecorator } from 'honestjs'
import type { Context } from 'hono'
// Basic decorators without transformation
export const Body = createParamDecorator('body', async (data, ctx: Context) => {
const body = await ctx.req.json()
return data ? body[data] : body
})
export const Param = createParamDecorator('param', (data, ctx: Context) => {
return data ? ctx.req.param(data) : ctx.req.param()
})
export const Query = createParamDecorator('query', (data, ctx: Context) => {
return data ? ctx.req.query(data) : ctx.req.query()
})
export const Header = createParamDecorator('header', (data, ctx: Context) => {
return data ? ctx.req.header(data) : ctx.req.header()
})
Advanced Custom Decorators:
// Client IP extraction with fallbacks
export const ClientIP = createParamDecorator('ip', (_, ctx: Context) => {
const forwardedFor = ctx.req.header('x-forwarded-for')
const realIP = ctx.req.header('x-real-ip')
const cfIP = ctx.req.header('cf-connecting-ip')
return forwardedFor?.split(',')[0].trim() || realIP || cfIP || 'unknown'
})
// User agent parsing
export const UserAgent = createParamDecorator('user-agent', (_, ctx: Context) => {
const userAgent = ctx.req.header('user-agent') || 'unknown'
return {
raw: userAgent,
isMobile: /Mobile|Android|iPhone|iPad/.test(userAgent),
isBot: /bot|crawler|spider/i.test(userAgent),
browser: userAgent.match(/(?:Chrome|Firefox|Safari|Edge)\/[\d.]+/)?.[0] || 'unknown',
}
})
// Request timing
export const RequestTime = createParamDecorator('request-time', () => {
return Date.now()
})
// Locale extraction from multiple sources
export const Locale = createParamDecorator('locale', (fallback = 'en', ctx: Context) => {
// Check query parameter first
const queryLocale = ctx.req.query('locale')
if (queryLocale) return queryLocale
// Check header
const acceptLanguage = ctx.req.header('accept-language')
if (acceptLanguage) {
const primaryLocale = acceptLanguage.split(',')[0].split('-')[0]
return primaryLocale
}
return fallback
})
// JWT token extraction and parsing
export const JwtPayload = createParamDecorator('jwt', (_, ctx: Context) => {
const authHeader = ctx.req.header('authorization')
if (!authHeader?.startsWith('Bearer ')) {
throw new Error('No valid JWT token found')
}
const token = authHeader.substring(7)
// Parse JWT payload (simplified - use proper JWT library in production)
const payload = JSON.parse(atob(token.split('.')[1]))
return payload
})
// File upload handler
export const UploadedFile = createParamDecorator('file', async (fieldName, ctx: Context) => {
const formData = await ctx.req.formData()
const file = formData.get(fieldName || 'file') as File
if (!file) {
throw new Error(`No file found in field: ${fieldName || 'file'}`)
}
return {
name: file.name,
size: file.size,
type: file.type,
buffer: await file.arrayBuffer(),
stream: file.stream(),
}
})
Using Custom Parameter Decorators:
import { Controller, Get, Post } from 'honestjs'
import { ClientIP, UserAgent, RequestTime, Locale, JwtPayload, UploadedFile } from './decorators/parameter.decorators'
@Controller('/api')
export class ApiController {
@Get('/info')
getRequestInfo(
@ClientIP() ip: string,
@UserAgent() userAgent: any,
@RequestTime() timestamp: number,
@Locale('en') locale: string
) {
return {
clientIP: ip,
userAgent,
timestamp,
locale,
serverTime: new Date().toISOString(),
}
}
@Get('/profile')
getProfile(@JwtPayload() user: any) {
return {
userId: user.sub,
email: user.email,
roles: user.roles,
}
}
@Post('/upload')
async uploadFile(@UploadedFile('document') file: any) {
// Process uploaded file
return {
fileName: file.name,
fileSize: file.size,
uploadedAt: new Date().toISOString(),
}
}
}
Complex Data Transformation:
// Pagination parameters with defaults and validation
export const Pagination = createParamDecorator('pagination', (defaults = {}, ctx: Context) => {
const page = parseInt(ctx.req.query('page') || '1', 10)
const limit = parseInt(ctx.req.query('limit') || '10', 10)
const sortBy = ctx.req.query('sortBy') || defaults.sortBy || 'id'
const sortOrder = ctx.req.query('sortOrder') || defaults.sortOrder || 'asc'
// Validation
if (page < 1) throw new Error('Page must be >= 1')
if (limit < 1 || limit > 100) throw new Error('Limit must be between 1 and 100')
if (!['asc', 'desc'].includes(sortOrder)) throw new Error('Sort order must be asc or desc')
return {
page,
limit,
offset: (page - 1) * limit,
sortBy,
sortOrder,
}
})
// Usage
@Controller('/users')
export class UsersController {
@Get()
async findAll(@Pagination({ sortBy: 'name', sortOrder: 'asc' }) pagination: any) {
return this.userService.findMany({
offset: pagination.offset,
limit: pagination.limit,
sortBy: pagination.sortBy,
sortOrder: pagination.sortOrder,
})
}
}
Best Practices
- Error Handling: Always use
createErrorResponse
for consistent error formatting - Type Safety: Provide proper TypeScript types for your custom decorators
- Validation: Include validation logic in parameter decorator factories
- Documentation: Document custom decorators with JSDoc comments
- Reusability: Create helper utilities that can be shared across projects
- Testing: Write unit tests for custom decorator logic
These helper functions form the foundation of HonestJS's extensibility, allowing you to create powerful, reusable components that integrate seamlessly with the framework's decorator-based architecture.