This is the full developer documentation for HonestJS. # Start of HonestJS documentation # Configuration HonestJS applications can be configured through the `HonestOptions` interface when creating your application. This allows you to customize various aspects of your application's behavior, from routing to error handling. ## Basic Configuration The most basic way to configure your application is through the `Application.create()` method: ```typescript import { Application } from 'honestjs' import AppModule from './app.module' const { app, hono } = await Application.create(AppModule, { // Configuration options go here }) ``` ## Configuration Options ### Container Configuration You can provide a custom dependency injection container: ```typescript import { Container } from 'honestjs' import type { DiContainer } from 'honestjs' class CustomContainer implements DiContainer { resolve(target: Constructor): T { // Custom resolution logic return new target() } register(target: Constructor, instance: T): void { // Custom registration logic } } const { app, hono } = await Application.create(AppModule, { container: new CustomContainer(), }) ``` ### Hono-specific Configuration Configure the underlying Hono instance: ```typescript const { app, hono } = await Application.create(AppModule, { hono: { // Whether to use strict matching for routes strict: true, // Custom router implementation router: customRouter, // Custom path extraction function getPath: (request, options) => { // Custom logic to extract path from request return request.url }, }, }) ``` ### Routing Configuration Set global routing options that apply to all routes: ```typescript import { VERSION_NEUTRAL } from 'honestjs' const { app, hono } = await Application.create(AppModule, { routing: { // Global API prefix (e.g., all routes become /api/*) prefix: 'api', // Global API version (e.g., all routes become /v1/*) version: 1, // You can also use VERSION_NEUTRAL or an array of versions // version: VERSION_NEUTRAL // Routes accessible with and without version // version: [1, 2] // Routes available at both /v1/* and /v2/* }, }) ``` **Example result:** With `prefix: 'api'` and `version: 1`, a route `@Get('/users')` becomes accessible at `/api/v1/users`. ### Global Components Configuration Apply components (middleware, guards, pipes, filters) globally to all routes: ```typescript import type { IMiddleware, IGuard, IPipe, IFilter } from 'honestjs' import { AuthGuard } from './guards/auth.guard' import { LoggerMiddleware } from './middleware/logger.middleware' import { ValidationPipe } from './pipes/validation.pipe' import { HttpExceptionFilter } from './filters/http-exception.filter' const { app, hono } = await Application.create(AppModule, { components: { // Global middleware applied to every route middleware: [ new LoggerMiddleware(), // You can also pass classes; they will be instantiated by the container SomeOtherMiddleware, ], // Global guards for authentication/authorization guards: [new AuthGuard()], // Global pipes for data transformation/validation pipes: [new ValidationPipe()], // Global exception filters for error handling filters: [new HttpExceptionFilter()], }, }) ``` ### Plugin Configuration Extend your application with plugins: ```typescript import type { IPlugin } from 'honestjs' import { Application } from 'honestjs' class DatabasePlugin implements IPlugin { async beforeModulesRegistered(app: Application, hono: Hono) { // Setup database connection console.log('Setting up database...') } async afterModulesRegistered(app: Application, hono: Hono) { // Perform post-registration tasks console.log('Database setup complete') } } class CachePlugin implements IPlugin { constructor(private options: { ttl: number; maxSize: number }) {} async beforeModulesRegistered(app: Application, hono: Hono) { // Initialize cache console.log(`Initializing cache with TTL: ${this.options.ttl}`) } } const { app, hono } = await Application.create(AppModule, { plugins: [ new DatabasePlugin(), new CachePlugin({ ttl: 3600, maxSize: 1000, }), ], }) ``` Plugins can hook into the application lifecycle with `beforeModulesRegistered` and `afterModulesRegistered` methods. ### Error Handling Configuration Customize global error handling: ```typescript import type { Context } from 'hono' const { app, hono } = await Application.create(AppModule, { // Custom error handler for unhandled exceptions onError: (error: Error, context: Context) => { console.error('Unhandled error:', error) return context.json( { error: 'Internal Server Error', message: 'Something went wrong', timestamp: new Date().toISOString(), path: context.req.path, }, 500 ) }, // Custom handler for routes that don't match any pattern notFound: (context: Context) => { return context.json( { error: 'Not Found', message: `Route ${context.req.path} not found`, timestamp: new Date().toISOString(), }, 404 ) }, }) ``` ## Complete Configuration Example Here's a comprehensive example showing all configuration options: ```typescript import { Application, VERSION_NEUTRAL } from 'honestjs' import type { HonestOptions } from 'honestjs' import { AuthGuard } from './guards/auth.guard' import { LoggerMiddleware } from './middleware/logger.middleware' import { ValidationPipe } from './pipes/validation.pipe' import { HttpExceptionFilter } from './filters/http-exception.filter' import { DatabasePlugin } from './plugins/database.plugin' import AppModule from './app.module' const options: HonestOptions = { // Custom DI container (optional) // container: new CustomContainer(), // Hono configuration hono: { strict: true, }, // Global routing configuration routing: { prefix: 'api', version: 1, }, // Global components components: { middleware: [new LoggerMiddleware()], guards: [new AuthGuard()], pipes: [new ValidationPipe()], filters: [new HttpExceptionFilter()], }, // Plugins plugins: [new DatabasePlugin()], // Custom error handlers onError: (error, context) => { console.error('Error:', error) return context.json( { error: 'Internal Server Error', timestamp: new Date().toISOString(), path: context.req.path, }, 500 ) }, notFound: (context) => { return context.json( { error: 'Route not found', path: context.req.path, timestamp: new Date().toISOString(), }, 404 ) }, } const { app, hono } = await Application.create(AppModule, options) export default hono ``` ## Configuration Best Practices ### 1. Environment-Based Configuration Use environment variables to configure your application for different environments: ```typescript const options: HonestOptions = { routing: { prefix: process.env.API_PREFIX || 'api', version: parseInt(process.env.API_VERSION || '1'), }, components: { middleware: process.env.NODE_ENV === 'production' ? [new ProductionLoggerMiddleware()] : [new DevelopmentLoggerMiddleware()], }, } ``` ### 2. Modular Configuration Split your configuration into logical modules: ::: code-group ```typescript [config/database.ts] export const databaseConfig = { host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), } ``` ```typescript [config/security.ts] export const securityConfig = { jwtSecret: process.env.JWT_SECRET || 'default-secret', bcryptRounds: parseInt(process.env.BCRYPT_ROUNDS || '10'), } ``` ```typescript [main.ts] import { databaseConfig } from './config/database' import { securityConfig } from './config/security' const { app, hono } = await Application.create(AppModule, { plugins: [new DatabasePlugin(databaseConfig), new SecurityPlugin(securityConfig)], }) ``` ::: ### 3. Type-Safe Configuration Create typed configuration objects for better type safety: ```typescript interface AppConfig { database: { host: string port: number } security: { jwtSecret: string bcryptRounds: number } api: { prefix: string version: number } } const config: AppConfig = { database: { host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), }, security: { jwtSecret: process.env.JWT_SECRET || 'default-secret', bcryptRounds: parseInt(process.env.BCRYPT_ROUNDS || '10'), }, api: { prefix: process.env.API_PREFIX || 'api', version: parseInt(process.env.API_VERSION || '1'), }, } const { app, hono } = await Application.create(AppModule, { routing: { prefix: config.api.prefix, version: config.api.version, }, plugins: [new DatabasePlugin(config.database), new SecurityPlugin(config.security)], }) ``` This configuration approach gives you fine-grained control over your application's behavior while maintaining clean and organized code. # API Reference This document provides a comprehensive reference for all the APIs available in HonestJS. ## Table of Contents - [Application](#application) - [Decorators](#decorators) - [Components](#components) - [Interfaces](#interfaces) - [Types](#types) - [Constants](#constants) - [Utilities](#utilities) ## Application ### `Application` The main application class for creating and configuring HonestJS applications. #### `Application.create(rootModule, options?)` Creates and initializes a new application with a root module. ```typescript static async create( rootModule: Constructor, options?: HonestOptions ): Promise<{ app: Application; hono: Hono }> ``` **Parameters:** - `rootModule`: The root module class for the application - `options`: Optional application configuration **Returns:** Object containing the application and Hono instances **Example:** ```typescript const { app, hono } = await Application.create(AppModule, { routing: { prefix: '/api', version: 1 }, }) ``` #### `Application.getApp()` Gets the underlying Hono instance for direct access. ```typescript getApp(): Hono ``` **Returns:** The Hono application instance #### `Application.getRoutes()` Gets information about all registered routes in the application. ```typescript getRoutes(): ReadonlyArray ``` **Returns:** Array of route information objects #### `Application.register(moduleClass)` Registers a module with the application. ```typescript async register(moduleClass: Constructor): Promise ``` **Parameters:** - `moduleClass`: The module class to register **Returns:** The application instance for method chaining ## Decorators ### Routing Decorators #### `@Controller(route?, options?)` Marks a class as a controller and defines the base route for all its endpoints. ```typescript function Controller(route?: string, options?: ControllerOptions): ClassDecorator ``` **Parameters:** - `route`: Optional base route path - `options`: Controller configuration options **Example:** ```typescript @Controller('users', { version: 1 }) class UsersController {} ``` #### HTTP Method Decorators ##### `@Get(path?, options?)` Defines a GET endpoint. ```typescript function Get(path?: string, options?: HttpMethodOptions): MethodDecorator ``` ##### `@Post(path?, options?)` Defines a POST endpoint. ```typescript function Post(path?: string, options?: HttpMethodOptions): MethodDecorator ``` ##### `@Put(path?, options?)` Defines a PUT endpoint. ```typescript function Put(path?: string, options?: HttpMethodOptions): MethodDecorator ``` ##### `@Delete(path?, options?)` Defines a DELETE endpoint. ```typescript function Delete(path?: string, options?: HttpMethodOptions): MethodDecorator ``` ##### `@Patch(path?, options?)` Defines a PATCH endpoint. ```typescript function Patch(path?: string, options?: HttpMethodOptions): MethodDecorator ``` ##### `@Options(path?, options?)` Defines an OPTIONS endpoint. ```typescript function Options(path?: string, options?: HttpMethodOptions): MethodDecorator ``` ##### `@All(path?, options?)` Defines an endpoint that matches all HTTP methods. ```typescript function All(path?: string, options?: HttpMethodOptions): MethodDecorator ``` ### Dependency Injection Decorators #### `@Service()` Marks a class as a service that can be injected as a dependency. ```typescript function Service(): ClassDecorator ``` **Example:** ```typescript @Service() class UserService {} ``` #### `@Module(options)` Defines a module that organizes controllers, services, and other modules. ```typescript function Module(options?: ModuleOptions): ClassDecorator ``` **Parameters:** - `options`: Module configuration options **Example:** ```typescript @Module({ controllers: [UsersController], services: [UserService], imports: [AuthModule], }) class AppModule {} ``` ### Parameter Decorators #### `@Body(data?)` Extracts the request body or a specific property from it. ```typescript function Body(data?: string): ParameterDecorator ``` **Parameters:** - `data`: Optional property name to extract from the body **Example:** ```typescript @Post() createUser(@Body() userData: CreateUserDto) {} @Post() createUser(@Body('name') name: string) {} ``` #### `@Param(data?)` Extracts route parameters. ```typescript function Param(data?: string): ParameterDecorator ``` **Parameters:** - `data`: Optional parameter name to extract **Example:** ```typescript @Get(':id') getUser(@Param('id') id: string) {} @Get(':id') getUser(@Param() params: Record) {} ``` #### `@Query(data?)` Extracts query string parameters. ```typescript function Query(data?: string): ParameterDecorator ``` **Parameters:** - `data`: Optional query parameter name to extract **Example:** ```typescript @Get() getUsers(@Query('page') page?: string) {} @Get() getUsers(@Query() query: Record) {} ``` #### `@Header(data?)` Extracts HTTP headers. ```typescript function Header(data?: string): ParameterDecorator ``` **Parameters:** - `data`: Optional header name to extract **Example:** ```typescript @Get() getProfile(@Header('authorization') auth: string) {} @Get() getProfile(@Header() headers: Record) {} ``` #### `@Req()` / `@Request()` Injects the full request object. ```typescript function Req(): ParameterDecorator function Request(): ParameterDecorator ``` **Example:** ```typescript @Get() getInfo(@Req() req: Request) {} ``` #### `@Res()` / `@Response()` Injects the response object. ```typescript function Res(): ParameterDecorator function Response(): ParameterDecorator ``` **Example:** ```typescript @Get() getInfo(@Res() res: Response) {} ``` #### `@Ctx()` / `@Context()` Injects the Hono context object. ```typescript function Ctx(): ParameterDecorator function Context(): ParameterDecorator ``` **Example:** ```typescript @Get() getInfo(@Ctx() ctx: Context) {} ``` #### `@Var(data)` / `@Variable(data)` Extracts a variable from the context. ```typescript function Var(data: string): ParameterDecorator function Variable(data: string): ParameterDecorator ``` **Parameters:** - `data`: The variable name to extract from context **Example:** ```typescript @Get() getCurrentUser(@Var('user') user: User) {} ``` ### Component Decorators #### `@UseMiddleware(...middleware)` Applies middleware to a controller or method. ```typescript function UseMiddleware(...middleware: MiddlewareType[]): ClassDecorator | MethodDecorator ``` **Parameters:** - `middleware`: Array of middleware classes or instances **Example:** ```typescript @UseMiddleware(LoggerMiddleware, AuthMiddleware) @Controller('users') class UsersController { @UseMiddleware(RateLimitMiddleware) @Get() getUsers() {} } ``` #### `@UseGuards(...guards)` Applies guards to a controller or method. ```typescript function UseGuards(...guards: GuardType[]): ClassDecorator | MethodDecorator ``` **Parameters:** - `guards`: Array of guard classes or instances **Example:** ```typescript @UseGuards(AuthGuard, RoleGuard) @Controller('admin') class AdminController { @UseGuards(AdminGuard) @Get('users') getUsers() {} } ``` #### `@UsePipes(...pipes)` Applies pipes to a controller or method. ```typescript function UsePipes(...pipes: PipeType[]): ClassDecorator | MethodDecorator ``` **Parameters:** - `pipes`: Array of pipe classes or instances **Example:** ```typescript @UsePipes(ValidationPipe, TransformPipe) @Controller('users') class UsersController { @UsePipes(CustomPipe) @Post() createUser(@Body() user: UserDto) {} } ``` #### `@UseFilters(...filters)` Applies exception filters to a controller or method. ```typescript function UseFilters(...filters: FilterType[]): ClassDecorator | MethodDecorator ``` **Parameters:** - `filters`: Array of filter classes or instances **Example:** ```typescript @UseFilters(HttpExceptionFilter, ValidationExceptionFilter) @Controller('users') class UsersController { @UseFilters(CustomExceptionFilter) @Get() getUsers() {} } ``` ### MVC Decorators #### `@View(route?, options?)` Alias for `@Controller` with MVC naming. ```typescript function View(route?: string, options?: ControllerOptions): ClassDecorator ``` #### `@Page(path?, options?)` Alias for `@Get` with MVC naming. ```typescript const Page = Get ``` #### `@MvcModule(options)` Enhanced module decorator with view support. ```typescript function MvcModule(options?: ModuleOptions & { views?: Constructor[] }): ClassDecorator ``` ## Components ### Layout Component #### `Layout(props)` Creates a complete HTML document with SEO optimization and modern web standards. ```typescript function Layout(props: PropsWithChildren): string ``` **Parameters:** - `props`: Layout configuration object **Example:** ```typescript const html = Layout({ title: 'My App', description: 'A modern web application', children: '

Hello World

', }) ``` ## Interfaces ### Application Interfaces #### `HonestOptions` Configuration options for the HonestJS application. ```typescript interface HonestOptions { container?: DiContainer hono?: { strict?: boolean router?: any getPath?: (request: Request, options?: any) => string } routing?: { prefix?: string version?: number | typeof VERSION_NEUTRAL | number[] } components?: { middleware?: MiddlewareType[] guards?: GuardType[] pipes?: PipeType[] filters?: FilterType[] } plugins?: PluginType[] onError?: (error: Error, context: Context) => Response | Promise notFound?: (context: Context) => Response | Promise } ``` #### `ControllerOptions` Configuration options for controllers. ```typescript interface ControllerOptions { prefix?: string | null version?: number | null | typeof VERSION_NEUTRAL | number[] } ``` #### `HttpMethodOptions` Configuration options for HTTP method decorators. ```typescript interface HttpMethodOptions { prefix?: string | null version?: number | null | typeof VERSION_NEUTRAL | number[] } ``` #### `ModuleOptions` Configuration options for modules. ```typescript interface ModuleOptions { controllers?: Constructor[] services?: Constructor[] imports?: Constructor[] } ``` ### Component Interfaces #### `IMiddleware` Interface for middleware classes. ```typescript interface IMiddleware { use(c: Context, next: Next): Promise } ``` #### `IGuard` Interface for guard classes. ```typescript interface IGuard { canActivate(context: Context): boolean | Promise } ``` #### `IPipe` Interface for pipe classes. ```typescript interface IPipe { transform(value: unknown, metadata: ArgumentMetadata): Promise | unknown } ``` #### `IFilter` Interface for exception filter classes. ```typescript interface IFilter { catch(exception: Error, context: Context): Promise | Response | undefined } ``` #### `IPlugin` Interface for plugin classes. ```typescript interface IPlugin { beforeModulesRegistered?: (app: Application, hono: Hono) => void | Promise afterModulesRegistered?: (app: Application, hono: Hono) => void | Promise } ``` ### Dependency Injection Interfaces #### `DiContainer` Interface for dependency injection containers. ```typescript interface DiContainer { resolve(target: Constructor): T register(target: Constructor, instance: T): void } ``` ### Route Interfaces #### `RouteDefinition` Definition of a route. ```typescript interface RouteDefinition { path: string method: string handlerName: string | symbol parameterMetadata: ParameterMetadata[] version?: number | null | typeof VERSION_NEUTRAL | number[] prefix?: string | null } ``` #### `RouteInfo` Information about a registered route. ```typescript interface RouteInfo { controller: string | symbol handler: string | symbol method: string prefix: string version?: string route: string path: string fullPath: string parameters: ParameterMetadata[] } ``` #### `ParameterMetadata` Metadata about a parameter. ```typescript interface ParameterMetadata { index: number name: string data?: any factory: (data: any, ctx: Context) => any metatype?: Constructor } ``` #### `ArgumentMetadata` Metadata about an argument for pipes. ```typescript interface ArgumentMetadata { type: 'body' | 'query' | 'param' | 'custom' metatype?: Constructor data?: string } ``` ### Error Interfaces #### `ErrorResponse` Standard error response format. ```typescript interface ErrorResponse { status: number message: string timestamp: string path: string requestId?: string code?: string details?: Record errors?: Array<{ property: string; constraints: Record }> } ``` ### Layout Interfaces #### `SiteData` Configuration for the Layout component. ```typescript interface SiteData { title: string description?: string image?: string url?: string locale?: string type?: string siteName?: string customMeta?: MetaTag[] scripts?: (string | ScriptOptions)[] stylesheets?: string[] favicon?: string twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player' csp?: string htmlAttributes?: HtmlAttributes headAttributes?: HtmlAttributes bodyAttributes?: HtmlAttributes } ``` #### `MetaTag` Custom meta tag configuration. ```typescript interface MetaTag { property: string content: string name?: string prefix?: string } ``` #### `HtmlAttributes` HTML attributes configuration. ```typescript type HtmlAttributes = Record ``` ## Types ### `Constructor` Type for class constructors. ```typescript type Constructor = new (...args: any[]) => T ``` ### Component Types #### `MiddlewareType` Type for middleware classes or instances. ```typescript type MiddlewareType = Constructor | IMiddleware ``` #### `GuardType` Type for guard classes or instances. ```typescript type GuardType = Constructor | IGuard ``` #### `PipeType` Type for pipe classes or instances. ```typescript type PipeType = Constructor | IPipe ``` #### `FilterType` Type for filter classes or instances. ```typescript type FilterType = Constructor | IFilter ``` #### `PluginType` Type for plugin classes or instances. ```typescript type PluginType = Constructor | IPlugin ``` ## Constants ### `VERSION_NEUTRAL` Symbol to use when marking a route as version-neutral. ```typescript const VERSION_NEUTRAL = Symbol('VERSION_NEUTRAL') ``` **Usage:** ```typescript @Controller('health', { version: VERSION_NEUTRAL }) class HealthController { @Get('status') getStatus() { // Accessible at both /health/status and /v1/health/status } } ``` ## Utilities ### Helper Functions #### `createParamDecorator(type, factory?)` Creates a custom parameter decorator. ```typescript function createParamDecorator( type: string, factory?: (data: any, ctx: Context) => T ): (data?: any) => ParameterDecorator ``` **Parameters:** - `type`: The type identifier for the parameter - `factory`: Optional function to transform the parameter value **Example:** ```typescript export const CurrentUser = createParamDecorator('user', (_, ctx) => { const token = ctx.req.header('authorization')?.replace('Bearer ', '') return token ? decodeJWT(token) : null }) ``` #### `createHttpMethodDecorator(method)` Creates an HTTP method decorator. ```typescript function createHttpMethodDecorator(method: string): (path?: string, options?: HttpMethodOptions) => MethodDecorator ``` **Parameters:** - `method`: The HTTP method name **Example:** ```typescript const CustomGet = createHttpMethodDecorator('get') ``` #### `createErrorResponse(exception, context, options?)` Creates a standardized error response. ```typescript function createErrorResponse( exception: Error, context: Context, options?: { status?: number title?: string detail?: string code?: string additionalDetails?: Record } ): { response: ErrorResponse; status: ContentfulStatusCode } ``` ### Utility Functions #### `isConstructor(val)` Checks if a value is a constructor function. ```typescript function isConstructor(val: unknown): boolean ``` #### `isObject(val)` Checks if a value is an object. ```typescript function isObject(val: unknown): val is Record ``` #### `isFunction(val)` Checks if a value is a function. ```typescript function isFunction(val: unknown): val is Function ``` #### `isString(val)` Checks if a value is a string. ```typescript function isString(val: unknown): val is string ``` #### `isNumber(val)` Checks if a value is a number. ```typescript function isNumber(val: unknown): val is number ``` #### `normalizePath(path?)` Normalizes a path string. ```typescript function normalizePath(path?: string): string ``` #### `addLeadingSlash(path?)` Adds a leading slash to a path if it doesn't have one. ```typescript function addLeadingSlash(path?: string): string ``` #### `stripEndSlash(path)` Removes the trailing slash from a path. ```typescript function stripEndSlash(path: string): string ``` This API reference provides comprehensive documentation for all the features and functionality available in HonestJS. For more detailed examples and usage patterns, refer to the individual documentation sections. # Getting Started This guide demonstrates how to create a basic "Hello, World!" application with HonestJS. ## Prerequisites Before you begin, make sure you have the following installed: - [Bun](https://bun.sh/) (recommended) or Node.js - TypeScript knowledge (basic understanding) ## Project Setup The fastest way to create a new Honest application is with the HonestJS CLI. ### 1. Install the CLI To install the CLI globally, run: ```bash bun add -g @honestjs/cli ``` ### 2. Create a Project Create a new project using the `new` command: ```bash honestjs new my-project # alias: honest, hnjs ``` This command will prompt you to select a template and configure the project. For this guide, choose the `barebone` template. ### 3. Start the Development Server Navigate to your new project directory and start the development server: ```bash cd my-project bun dev ``` Your application will be available at `http://localhost:3000`. ## Manual Setup If you prefer to set up your project manually, follow these steps: ### 1. Initialize Project First, create a new project and install the necessary dependencies. ```bash bun init bun add honestjs hono reflect-metadata ``` ### 2. Configure TypeScript Ensure your `tsconfig.json` has the following options enabled for decorator support: ::: code-group ```json [tsconfig.json] { "compilerOptions": { // Enable latest features "lib": ["ESNext", "DOM"], "target": "ESNext", "module": "ESNext", "moduleDetection": "force", // Optional: Enable JSX support for Hono "jsx": "react-jsx", "jsxImportSource": "hono/jsx", // Bundler mode "moduleResolution": "bundler", "verbatimModuleSyntax": true, // Enable declaration file generation "declaration": false, "declarationMap": false, "emitDeclarationOnly": false, "outDir": "dist", "rootDir": "src", "sourceMap": false, // Best practices "strict": true, "skipLibCheck": true, "noFallthroughCasesInSwitch": true, "forceConsistentCasingInFileNames": true, "esModuleInterop": true, // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, "noPropertyAccessFromIndexSignature": false, // Decorators "experimentalDecorators": true, "emitDecoratorMetadata": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` ::: ## Building Your First App ### 0. Create a directory structure ``` Project ├── src │ ├── app.module.ts │ ├── app.controller.ts │ ├── app.service.ts └── └── main.ts ``` ### 1. Create a Service Services are responsible for business logic. This service will provide the "Hello, World!" message. ::: code-group ```typescript [app.service.ts] import { Service } from 'honestjs' @Service() class AppService { helloWorld(): string { return 'Hello, World!' } } export default AppService ``` ::: ### 2. Create a Controller Controllers handle incoming requests and use services to fulfill them. ::: code-group ```typescript [app.controller.ts] import { Controller, Get } from 'honestjs' import AppService from './app.service' @Controller() class AppController { constructor(private readonly appService: AppService) {} @Get() helloWorld(): string { return this.appService.helloWorld() } } export default AppController ``` ::: ### 3. Create a Module Modules organize the application's components and define the dependency injection scope. ::: code-group ```typescript [app.module.ts] import { Module } from 'honestjs' import AppController from './app.controller' import AppService from './app.service' @Module({ controllers: [AppController], services: [AppService], }) class AppModule {} export default AppModule ``` ::: ### 4. Create the Application Entrypoint Finally, create the main application file to bootstrap the HonestJS app. ::: code-group ```typescript [main.ts] import { Application } from 'honestjs' import 'reflect-metadata' import AppModule from './app.module' const { app, hono } = await Application.create(AppModule) // Export the Hono instance for deployment export default hono ``` ::: ### 5. Run the Application Now, run the application: ```bash bun src/main.ts ``` Your application will be available at `http://localhost:3000` (or the port configured by your deployment environment). ## What Just Happened? Let's break down what we just created: 1. **AppService**: A service class that contains our business logic 2. **AppController**: A controller that handles HTTP requests and uses the service 3. **AppModule**: A module that organizes our components and enables dependency injection 4. **main.ts**: The entry point that bootstraps our application The magic happens through: - **Decorators**: `@Service()`, `@Controller()`, `@Get()`, `@Module()` tell HonestJS how to handle each class - **Dependency Injection**: The controller automatically receives the service instance - **Reflection**: TypeScript's reflection metadata enables the DI system to work ## Next Steps Now that you have a basic application running, you can explore: - [Configuration](./configuration.md) - Learn how to configure your application - [Routing](./concepts/routing.md) - Understand how to define routes and handle requests - [Dependency Injection](./concepts/dependency-injection.md) - Learn about the DI system - [Parameters](./concepts/parameters.md) - See how to extract data from requests - [Components](./components/overview.md) - Explore middleware, guards, pipes, and filters # Overview Welcome to the HonestJS documentation! HonestJS is a modern, lightweight web framework for TypeScript and JavaScript, built on top of [Hono](https://hono.dev). ## What is HonestJS? HonestJS provides a clean, decorator-based API for building web applications with: - **Decorator-based Architecture**: TypeScript decorators for Controllers, Services, and Modules - **Dependency Injection**: Simple yet powerful DI container for managing application components - **Comprehensive Components System**: Support for middleware, guards, pipes, and filters - **API Versioning**: Built-in support for API versioning with flexible versioning strategies - **Plugin System**: Extensible architecture with plugin support for custom functionality - **MVC Support**: Includes support for building full-stack applications with JSX-based views - **Error Handling**: Comprehensive error handling with customizable exception filters - **Route Management**: Advanced routing with parameter binding, query parsing, and header extraction ## Quick Start Get up and running with HonestJS in minutes! Check out our [Getting Started](./getting-started.md) guide for a complete tutorial, or jump right in with this minimal example: ```typescript import { Application, Controller, Get, Service, Module } from 'honestjs' import 'reflect-metadata' @Service() class AppService { helloWorld(): string { return 'Hello, World!' } } @Controller() class AppController { constructor(private readonly appService: AppService) {} @Get() helloWorld(): string { return this.appService.helloWorld() } } @Module({ controllers: [AppController], services: [AppService], }) class AppModule {} const { app, hono } = await Application.create(AppModule) export default hono ``` ## Documentation Sections ### Getting Started - **[Getting Started](./getting-started.md)** - Complete tutorial to build your first app - **[Configuration](./configuration.md)** - Application configuration options - **[API Reference](./api-reference.md)** - Complete API documentation ### Core Concepts - **[Routing](./concepts/routing.md)** - Route definitions, versioning, and path management - **[Dependency Injection](./concepts/dependency-injection.md)** - DI container and service management - **[Parameters](./concepts/parameters.md)** - Parameter decorators and data extraction - **[Error Handling](./concepts/error-handling.md)** - Exception filters and error management ### Components - **[Overview](./components/overview.md)** - Overview of the components system - **[Middleware](./components/middleware.md)** - Request/response processing middleware - **[Guards](./components/guards.md)** - Authentication and authorization guards - **[Pipes](./components/pipes.md)** - Data transformation and validation pipes - **[Filters](./components/filters.md)** - Exception handling filters ### Features - **[MVC Support](./features/mvc.md)** - Model-View-Controller architecture - **[Plugins](./features/plugins.md)** - Extending framework functionality - **[Helpers](./features/helpers.md)** - Utility functions and helper methods ## Framework Architecture HonestJS is organized around several core concepts: ``` Application ├── Modules (organize components) │ ├── Controllers (handle requests) │ ├── Services (business logic) │ └── Components (cross-cutting concerns) │ ├── Middleware (request processing) │ ├── Guards (authentication/authorization) │ ├── Pipes (data transformation) │ └── Filters (error handling) └── Dependency Injection (automatic instantiation) ``` ## Key Features ### Decorator-Based API ```typescript @Controller('users', { version: 1 }) class UsersController { @Get() @UseGuards(AuthGuard) @UsePipes(ValidationPipe) async getUsers(@Query('page') page?: string) { return await this.usersService.findAll({ page }) } } ``` ### Dependency Injection ```typescript @Service() class UserService { constructor(private readonly db: DatabaseService) {} async findAll() { return await this.db.query('SELECT * FROM users') } } @Controller('users') class UsersController { constructor(private readonly userService: UserService) {} // UserService is automatically injected } ``` ### API Versioning ::: code-group ```typescript [main.ts] // Global version const { app, hono } = await Application.create(AppModule, { routing: { version: 1 }, }) ``` ```typescript [users.controller.ts] // Controller-specific version @Controller('users', { version: 2 }) class UsersController {} ``` ```typescript [health.controller.ts] // Version-neutral routes @Controller('health', { version: VERSION_NEUTRAL }) class HealthController {} ``` ::: ### Component System ::: code-group ```typescript [main.ts] // Global components const { app, hono } = await Application.create(AppModule, { components: { middleware: [new LoggerMiddleware({ level: 'info' })], guards: [AuthGuard], pipes: [ValidationPipe], filters: [HttpExceptionFilter], }, }) ``` ```typescript [users.controller.ts] // Controller-level components @Controller('users') @UseMiddleware(LoggerMiddleware) @UseGuards(AuthGuard) class UsersController {} ``` ```typescript [users.controller.ts] // Handler-level components @Controller('users') class UsersController { @Get() @UseGuards(AdminGuard) @UsePipes(CustomPipe) getUsers() {} } ``` ::: ### Server-Side Rendering ```typescript import { Controller, Get, Layout } from 'honestjs' @Controller('pages') class PagesController { @Get('home') home() { return Layout({ title: 'Home - My App', description: 'Welcome to our application', children: '

Welcome to My App

', }) } } ``` ## Installation ::: code-group ```bash [bun (recommended)] bun add honestjs hono reflect-metadata ``` ```bash [npm] npm install honestjs hono reflect-metadata ``` ```bash [pnpm] pnpm add honestjs hono reflect-metadata ``` ```bash [yarn] yarn add honestjs hono reflect-metadata ``` ::: For detailed setup instructions, see our [Getting Started](./getting-started.md) guide. ## Community and Support - **Repository**: [GitHub Repository](https://github.com/honestjs/honest) - **Issues**: [GitHub Issues](https://github.com/honestjs/honest/issues) - **Discussions**: [GitHub Discussions](https://github.com/honestjs/honest/discussions) ## License HonestJS is licensed under the MIT License. See the [LICENSE](https://github.com/honestjs/honest/blob/master/LICENSE) file for details. --- Start building with HonestJS today! Check out the [Getting Started](./getting-started.md) guide for a detailed tutorial. # MVC Beyond creating REST APIs, HonestJS supports building traditional Model-View-Controller (MVC) applications with server-side rendered views, powered by Hono's JSX engine and the JsxRenderer middleware. ## Core Concepts The MVC support in HonestJS is built around a few specialized decorators and concepts that extend the core framework: - **Views:** These are special controllers designed for rendering UI. They are decorated with `@View()` instead of `@Controller()`. - **Page Decorator:** A custom HTTP method decorator, `@Page()`, is used within Views to signify a method that renders a page. It's essentially a specialized `@Get()` decorator. - **JsxRenderer Middleware:** The `JsxRendererMiddleware` provides JSX rendering capabilities with automatic layout wrapping. - **Layout Component:** The `Layout` component provides comprehensive HTML document structure with SEO optimization and modern web standards support. - **JSX and Components:** You can use JSX (`.tsx`) to define your components and layouts, which are then rendered to HTML. ## Layout Component The Layout component is a powerful server-side rendering utility that provides a comprehensive HTML document structure with SEO optimization, flexible configuration, and modern web standards support. ### Overview The Layout component is designed for building full-stack applications with HonestJS, providing a clean way to generate complete HTML documents with proper meta tags, scripts, stylesheets, and content. It works seamlessly with the JsxRenderer middleware to provide automatic layout wrapping. ### Basic Usage ```typescript import { Layout } from 'honestjs' const html = Layout({ title: 'My Application', description: 'A modern web application built with HonestJS', children: '

Hello World

', }) ``` ### Configuration Options The Layout component accepts a comprehensive configuration object: ```typescript interface SiteData { title: string // Required: Page title description?: string // Page description image?: string // Open Graph and Twitter image URL url?: string // Canonical URL locale?: string // Page locale (defaults to 'en_US') type?: string // Open Graph type (defaults to 'website') siteName?: string // Site name for Open Graph customMeta?: MetaTag[] // Array of custom meta tags scripts?: (string | ScriptOptions)[] // Array of script URLs or objects stylesheets?: string[] // Array of stylesheet URLs favicon?: string // Favicon URL twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player' csp?: string // Content Security Policy htmlAttributes?: HtmlAttributes // Custom HTML attributes headAttributes?: HtmlAttributes // Custom head attributes bodyAttributes?: HtmlAttributes // Custom body attributes } ``` ### SEO Optimization The Layout component automatically generates comprehensive SEO meta tags: #### Basic SEO ```typescript const html = Layout({ title: 'Product Page', description: 'Amazing product with great features', url: 'https://example.com/product', siteName: 'My Store', }) ``` #### Open Graph Tags ```typescript const html = Layout({ title: 'Product Page', description: 'Amazing product with great features', image: 'https://example.com/product.jpg', url: 'https://example.com/product', type: 'product', siteName: 'My Store', }) ``` #### Twitter Cards ```typescript const html = Layout({ title: 'Product Page', description: 'Amazing product with great features', image: 'https://example.com/product.jpg', twitterCard: 'summary_large_image', }) ``` #### Custom Meta Tags ```typescript const html = Layout({ title: 'Product Page', customMeta: [ { property: 'og:price:amount', content: '29.99' }, { property: 'og:price:currency', content: 'USD' }, { name: 'keywords', content: 'product, amazing, features' }, { name: 'author', content: 'John Doe' }, ], }) ``` ### Script and Stylesheet Management #### Basic Scripts and Stylesheets ```typescript const html = Layout({ title: 'My App', scripts: ['/app.js', '/analytics.js'], stylesheets: ['/styles.css', '/components.css'], }) ``` #### Advanced Script Configuration ```typescript const html = Layout({ title: 'My App', scripts: [ '/app.js', { src: '/analytics.js', async: true }, { src: '/critical.js', defer: true }, { src: '/lazy.js', async: true, defer: true }, ], stylesheets: ['/styles.css', '/print.css'], }) ``` ### Content Security Policy ```typescript const html = Layout({ title: 'Secure App', csp: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';", }) ``` ### Custom Attributes ```typescript const html = Layout({ title: 'My App', htmlAttributes: { lang: 'en', 'data-theme': 'dark', }, headAttributes: { 'data-head': 'true', }, bodyAttributes: { class: 'app-body', 'data-page': 'home', }, }) ``` ### Complete Layout Example Here's a comprehensive example showing all features: ```typescript import { Layout } from 'honestjs' const html = Layout({ title: 'HonestJS - Modern Web Framework', description: 'A lightweight, fast web framework built on Hono with TypeScript support', image: 'https://honestjs.dev/og-image.png', url: 'https://honestjs.dev', locale: 'en_US', type: 'website', siteName: 'HonestJS', favicon: '/favicon.ico', twitterCard: 'summary_large_image', csp: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;", scripts: ['/app.js', { src: '/analytics.js', async: true }, { src: '/critical.js', defer: true }], stylesheets: ['/styles.css', '/components.css'], customMeta: [ { name: 'keywords', content: 'web framework, typescript, hono, decorators' }, { name: 'author', content: 'HonestJS Team' }, { property: 'og:site_name', content: 'HonestJS' }, ], htmlAttributes: { lang: 'en', 'data-framework': 'honestjs', }, headAttributes: { 'data-head': 'true', }, bodyAttributes: { class: 'app-body', 'data-page': 'home', }, children: `

Welcome to HonestJS

A modern web framework for TypeScript and JavaScript

© 2024 HonestJS

`, }) ``` ### Integration with Controllers and JsxRenderer You can use the Layout component in your controllers with the JsxRenderer middleware: ```typescript import { Controller, Get, Ctx } from 'honestjs' import type { Context } from 'hono' @Controller('pages') export class PagesController { @Get('home') home(@Ctx() ctx: Context) { return ctx.render(

Welcome to My App

Built with HonestJS

, { title: 'Home - My App', description: 'Welcome to our application', } ) } @Get('about') about(@Ctx() ctx: Context) { return ctx.render(

About Us

We are a modern web development company.

, { title: 'About - My App', description: 'Learn more about our company', } ) } } ``` ### Dynamic Content You can generate dynamic content based on data: ```typescript @Controller('products') export class ProductsController { @Get(':id') async product(@Ctx() ctx: Context, @Param('id') id: string) { const product = await this.productService.findById(id) return ctx.render(

{product.name}

{product.description}

${product.price}

, { title: `${product.name} - My Store`, description: product.description, image: product.image, url: `https://mystore.com/products/${id}`, type: 'product', customMeta: [ { property: 'og:price:amount', content: product.price.toString() }, { property: 'og:price:currency', content: 'USD' }, ], } ) } } ``` ### Layout Best Practices #### 1. Always Provide a Title The title is required and crucial for SEO: ```typescript // ✅ Good ctx.render(
Content
, { title: 'Page Title', }) // ❌ Avoid ctx.render(
Content
) ``` #### 2. Use Descriptive Descriptions Provide meaningful descriptions for better SEO: ```typescript ctx.render(
Content
, { title: 'Product Page', description: 'High-quality product with amazing features and competitive pricing', }) ``` #### 3. Include Open Graph Images Add images for better social media sharing: ```typescript ctx.render(
Content
, { title: 'Product Page', description: 'Amazing product', image: 'https://example.com/product.jpg', }) ``` #### 4. Optimize Script Loading Use appropriate loading strategies for scripts: ```typescript ctx.render(
Content
, { title: 'My App', scripts: [ { src: '/critical.js', defer: true }, // Load early but don't block { src: '/analytics.js', async: true }, // Load in parallel { src: '/lazy.js', defer: true }, // Load after page ], }) ``` #### 5. Set Proper Viewport The Layout component automatically includes the viewport meta tag for responsive design: ```typescript // Automatically included: // ``` #### 6. Use Content Security Policy Implement CSP for better security: ```typescript ctx.render(
Content
, { title: 'Secure App', csp: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';", }) ``` ### Performance Considerations #### 1. Minimize Scripts Only include necessary scripts: ```typescript // ✅ Good - Only essential scripts ctx.render(
Content
, { scripts: ['/app.js', '/analytics.js'], }) // ❌ Avoid - Too many scripts ctx.render(
Content
, { scripts: ['/app.js', '/lib1.js', '/lib2.js', '/lib3.js', '/lib4.js', '/lib5.js'], }) ``` #### 2. Use Async/Defer Appropriately Choose the right loading strategy: ```typescript ctx.render(
Content
, { scripts: [ { src: '/critical.js', defer: true }, // Critical functionality { src: '/analytics.js', async: true }, // Non-critical tracking { src: '/lazy.js', defer: true }, // Lazy-loaded features ], }) ``` #### 3. Optimize Images Use optimized images for Open Graph: ```typescript ctx.render(
Content
, { image: 'https://example.com/optimized-image-1200x630.jpg', // Optimal size for social sharing }) ``` ## MVC Decorators HonestJS provides several decorators specifically for MVC applications: ### `@View(route?, options?)` An alias for `@Controller` with MVC naming conventions. Views are typically configured to ignore global prefixes and versioning, making them suitable for top-level page routes. ```typescript import { View } from 'honestjs' @View('pages') class PagesController { // This controller handles page rendering } ``` ### `@Page(path?, options?)` An alias for `@Get` with MVC naming conventions. Used to clearly indicate that a method renders a view. ```typescript import { View, Page } from 'honestjs' @View('pages') class PagesController { @Page('home') home() { // Renders the home page } } ``` ### `@MvcModule(options)` An enhanced module decorator with view support. It automatically includes views in the controllers array. ```typescript import { MvcModule } from 'honestjs' @MvcModule({ views: [PagesController], controllers: [ApiController], services: [DataService], }) class AppModule {} ``` ## Setting up an MVC Application ### 1. Project Configuration Your `tsconfig.json` needs to be configured to support JSX: ::: code-group ```json [tsconfig.json] { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "hono/jsx" } } ``` ::: ### 2. Creating Custom Layouts with JSX Create custom layouts using JSX components for better type safety and maintainability: ::: code-group ```tsx [MainLayout.tsx] import { Layout, type SiteData } from 'honestjs' import type { PropsWithChildren } from 'hono/jsx' import { Footer } from '../components/Footer' import { Header } from '../components/Header' export const MainLayout = ({ children, stylesheets, scripts, ...props }: PropsWithChildren) => { const globalStylesheets: string[] = ['/static/css/main.css'] const globalScripts: string[] = ['/static/js/main.js'] return (
{children}
) } ``` ::: ### 3. Setting up JSX Rendering Configure the JsxRenderer middleware in your application: ::: code-group ```typescript [main.ts] import { Application } from 'honestjs' import { JsxRendererMiddleware } from '@honestjs/middleware' import 'reflect-metadata' import AppModule from './app.module' import { MainLayout } from './layouts/MainLayout' declare module 'hono' { interface ContextRenderer { (content: string | Promise, props: SiteData): Response } } const { hono } = await Application.create(AppModule, { hono: { strict: true }, routing: { prefix: 'api', version: 1 }, components: { middleware: [new JsxRendererMiddleware(MainLayout)], }, }) export default hono ``` ::: ### 4. Using Custom Layouts in Views You can use your custom layouts in views by rendering JSX components. The Layout component (documented above) provides the foundation for creating consistent HTML structure across your application: ::: code-group ```tsx [users.view.ts] import { Ctx, Page, View } from 'honestjs' import type { Context } from 'hono' import { UserList } from './components/UserList' import UsersService from './users.service' @View('/users') class UsersView { stylesheets: string[] = ['/static/css/views/users.css'] scripts: string[] = ['/static/js/views/users.js'] constructor(private readonly usersService: UsersService) {} @Page() async index(@Ctx() ctx: Context) { const users = await this.usersService.findAll() return ctx.render(, { title: 'Users', description: 'List of users', stylesheets: this.stylesheets, scripts: this.scripts, }) } } ``` ::: ### 5. Creating Reusable Components You can create reusable components using JSX with proper TypeScript types: ::: code-group ```tsx [Header.tsx] import { memo } from 'hono/jsx' export const Header = memo(() => { return (

Honest.js MVC

) }) ``` ```tsx [Footer.tsx] export const Footer = memo(() => { return (

© {new Date().getFullYear()} Company. All rights reserved.

) }) ``` ```tsx [UserList.tsx] import type { FC } from 'hono/jsx' import type { User } from '../models/user.model' interface UserListProps { users: User[] } export const UserList: FC = (props: UserListProps) => { return (

All Users

{props.users.length === 0 ? (

No users yet

Get started by adding your first user

) : ( props.users.map((user) => (

{user.name}

{user.email &&

{user.email}

} {user.role && {user.role}}
)) )}
) } ``` ::: ## Module Configuration ### MVC Module Setup ::: code-group ```typescript [users.module.ts] import { MvcModule } from 'honestjs' import UsersController from './users.controller' import UsersService from './users.service' import UsersView from './users.view' @MvcModule({ views: [UsersView], controllers: [UsersController], services: [UsersService], }) class UsersModule {} export default UsersModule ``` ::: ### App Module Configuration ::: code-group ```typescript [app.module.ts] import { Module } from 'honestjs' import UsersModule from './modules/users/users.module' @Module({ imports: [UsersModule], }) class AppModule {} export default AppModule ``` ::: ## Combining API and Views You can have both API controllers and view controllers in the same application: ::: code-group ```typescript [users.controller.ts] // API Controller @Controller('users') class UsersController { constructor(private readonly usersService: UsersService) {} @Get() async getUsers(): Promise { return await this.usersService.findAll() } @Post() async createUser(@Body() body: CreateUserDto): Promise { return await this.usersService.create(body) } } ``` ```typescript [users.view.ts] // View Controller @View('/users') class UsersView { stylesheets: string[] = ['/static/css/views/users.css'] scripts: string[] = ['/static/js/views/users.js'] constructor(private readonly usersService: UsersService) {} @Page() async index(@Ctx() ctx: Context) { const users = await this.usersService.findAll() return ctx.render(, { title: 'Users', description: 'List of users', stylesheets: this.stylesheets, scripts: this.scripts, }) } } ``` > [!NOTE] > The `@View` decorator is just a shortcut for `@Controller` without prefix and versioning. > Make sure to add versioning or prefix to the API controllers to avoid conflicts. ::: ## Service Layer The service layer handles business logic and data operations: ::: code-group ```typescript [users.service.ts] import { Service } from 'honestjs' import { CreateUserDto } from './dtos/create-user.dto' import { User } from './models/user.model' @Service() class UsersService { private users: User[] = [ { id: 1, name: 'John', email: 'john@mail.com', role: 'admin' }, { id: 2, name: 'Jane', email: 'jane@mail.com', role: 'admin' }, ] async create(user: CreateUserDto): Promise { const id = this.users.length + 1 this.users.push({ id, name: user.name, email: user.email, role: 'user', }) return this.users[id - 1] } async findAll(): Promise { return this.users } async findById(id: number): Promise { return this.users.find((user) => user.id === id) || null } } export default UsersService ``` ::: ## Best Practices ### 1. Separate API and View Controllers Keep API controllers and view controllers separate for better organization: ::: code-group ```typescript [users.controller.ts] // API for data @Controller('users', { prefix: 'api', version: 1 }) class UsersApiController { @Get() async getUsers() { return await this.usersService.findAll() } } ``` ```typescript [users.view.ts] // Views for UI @View('users') class UsersView { @Page() async list() { // Render the users page } } ``` ::: ### 2. Use Custom Layouts for All Pages Always use custom layouts for consistent HTML structure. The Layout component provides comprehensive HTML document structure with SEO optimization: ```tsx @Page('home') async home(@Ctx() ctx: Context) { return ctx.render(

Welcome

, { title: 'Home', description: 'Welcome to our app' } ) } ``` ### 3. Leverage SEO Features Take advantage of the Layout component's comprehensive SEO features including Open Graph tags, Twitter Cards, and custom meta tags: ```tsx return ctx.render(, { title: 'Page Title', description: 'Page description', image: 'https://example.com/image.jpg', url: 'https://example.com/page', type: 'website', }) ``` ### 4. Use JSX Components for Reusability Create reusable JSX components for common UI elements: ::: code-group ```tsx [Header.tsx] import { memo } from 'hono/jsx' export const Header = memo(() => (
)) ``` ```tsx [Footer.tsx] export const Footer = memo(() => (

© {new Date().getFullYear()} My App

)) ``` ::: ### 5. Handle Dynamic Data with JSX Use services to fetch data for your views with JSX components: ::: code-group ```tsx [Dashboard.tsx] import type { FC } from 'hono/jsx' import type { User } from '../models/user.model' interface DashboardProps { users: User[] stats: { totalUsers: number; activeUsers: number } } export const Dashboard: FC = ({ users, stats }) => { return (

Dashboard

Total Users: {stats.totalUsers}

Active Users: {stats.activeUsers}

Recent Users

{users.map((user) => (

{user.name}

))}
) } ``` ```tsx [dashboard.view.ts] @View('/dashboard') class DashboardView { constructor(private readonly userService: UserService, private readonly statsService: StatsService) {} @Page() async dashboard(@Ctx() ctx: Context) { const [users, stats] = await Promise.all([ this.userService.getRecentUsers(), this.statsService.getDashboardStats(), ]) return ctx.render(, { title: 'Dashboard', }) } } ``` ::: With these MVC features, you can build powerful full-stack applications that combine the robust backend features of HonestJS with flexible server-side rendering capabilities using JSX and the JsxRenderer middleware. # 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 ```typescript function createErrorResponse( exception: Error, context: Context, options?: { status?: number title?: string detail?: string code?: string additionalDetails?: Record } ): { 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:** ```typescript 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:** ```typescript 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:** ```typescript 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: ```typescript 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 ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript // 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:** ```typescript 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:** ```typescript // 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 ```typescript function createParamDecorator( type: string, factory?: (data: any, ctx: Context) => T ): (data?: any) => ParameterDecorator ``` ### Parameters - `type`: Unique identifier for the parameter type - `factory`: Optional transformation function that receives decorator data and Hono context ### Usage Examples **Built-in Parameter Decorators:** ```typescript 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:** ```typescript // 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:** ```typescript 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:** ```typescript // 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 1. **Error Handling**: Always use `createErrorResponse` for consistent error formatting 2. **Type Safety**: Provide proper TypeScript types for your custom decorators 3. **Validation**: Include validation logic in parameter decorator factories 4. **Documentation**: Document custom decorators with JSDoc comments 5. **Reusability**: Create helper utilities that can be shared across projects 6. **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. # Plugins Plugins provide a powerful way to extend HonestJS functionality by hooking into the application lifecycle. They allow you to add custom features, integrate third-party services, or modify the application's behavior without changing the core framework code. > [!NOTE] > For cross-cutting concerns like logging, authentication, validation, and request/response modification, > prefer using [middleware](./../components/middleware.md), [guards](./../components/guards.md), [pipes](./../components/pipes.md), > or [filters](./../components/filters.md) over plugins. These components are specifically designed for these use cases > and provide better integration with the request lifecycle. Use plugins primarily for application-level setup, > external service integration, or framework extensions. ## Plugin Interface A plugin must implement the `IPlugin` interface, which provides two optional lifecycle hooks: ```typescript interface IPlugin { beforeModulesRegistered?: (app: Application, hono: Hono) => void | Promise afterModulesRegistered?: (app: Application, hono: Hono) => void | Promise } ``` Both hooks receive: - `app`: The HonestJS `Application` instance - `hono`: The underlying Hono application instance ## Lifecycle Hooks ### `beforeModulesRegistered` This hook runs before any modules are registered with the application. Use this hook for: - Setting up global services that modules might depend on - Configuring the Hono instance - Registering global middleware that needs to run before module-specific middleware - Initializing external services (databases, caches, etc.) ### `afterModulesRegistered` This hook runs after all modules have been registered. Use this hook for: - Cleanup operations - Final configuration that requires all modules to be loaded - Setting up monitoring or health checks - Registering catch-all routes ## Creating a Simple Plugin > [!WARNING] IMPORTANT > The logging example below demonstrates plugin usage, but for request logging, > [middleware](./../components/middleware.md) is the preferred approach. This example is shown for educational purposes. Here's a basic example of a logging plugin: ```typescript import { IPlugin } from 'honestjs' import type { Application } from 'honestjs' import type { Hono } from 'hono' export class LoggerPlugin implements IPlugin { private logLevel: string constructor(logLevel: string = 'info') { this.logLevel = logLevel } async beforeModulesRegistered(app: Application, hono: Hono): Promise { console.log(`[LoggerPlugin] Initializing with log level: ${this.logLevel}`) // Add a request logging middleware hono.use('*', async (c, next) => { const start = Date.now() console.log(`[${new Date().toISOString()}] ${c.req.method} ${c.req.path} - Started`) await next() const duration = Date.now() - start console.log(`[${new Date().toISOString()}] ${c.req.method} ${c.req.path} - ${c.res.status} (${duration}ms)`) }) } async afterModulesRegistered(app: Application, hono: Hono): Promise { console.log('[LoggerPlugin] All modules registered, logging is active') } } ``` ## Database Connection Plugin Here's a more complex example that manages database connections: ```typescript import { IPlugin } from 'honestjs' import type { Application } from 'honestjs' import type { Hono } from 'hono' interface DatabaseConfig { host: string port: number database: string username: string password: string } export class DatabasePlugin implements IPlugin { private config: DatabaseConfig private connection: any = null constructor(config: DatabaseConfig) { this.config = config } async beforeModulesRegistered(app: Application, hono: Hono): Promise { console.log('[DatabasePlugin] Connecting to database...') try { // Simulate database connection this.connection = await this.createConnection() // Make the connection available in Hono context hono.use('*', async (c, next) => { c.set('db', this.connection) await next() }) console.log('[DatabasePlugin] Database connection established') } catch (error) { console.error('[DatabasePlugin] Failed to connect to database:', error) throw error } } async afterModulesRegistered(app: Application, hono: Hono): Promise { console.log('[DatabasePlugin] Database plugin initialization complete') // Add a health check endpoint hono.get('/health/db', async (c) => { const isHealthy = await this.checkConnection() return c.json( { status: isHealthy ? 'healthy' : 'unhealthy', timestamp: new Date().toISOString(), }, isHealthy ? 200 : 503 ) }) } private async createConnection(): Promise { // Simulate connection creation return { host: this.config.host, port: this.config.port, connected: true, } } private async checkConnection(): Promise { // Simulate health check return this.connection && this.connection.connected } } ``` ## Configuration and Usage Register plugins when creating your application: ```typescript import { Application } from 'honestjs' import { LoggerPlugin } from './plugins/logger.plugin' import { DatabasePlugin } from './plugins/database.plugin' import AppModule from './app.module' const { hono } = await Application.create(AppModule, { plugins: [ new LoggerPlugin('debug'), new DatabasePlugin({ host: 'localhost', port: 5432, database: 'myapp', username: 'user', password: 'password', }), ], }) export default hono ``` ## Plugin Types You can provide plugins in two ways: 1. **Plugin Instance**: Pass an already instantiated plugin object ```typescript plugins: [new LoggerPlugin('debug')] ``` 2. **Plugin Class**: Pass the plugin class (it will be instantiated by the DI container) ```typescript plugins: [LoggerPlugin] ``` ## Best Practices ### 1. Error Handling Always handle errors gracefully in plugins, especially in the `beforeModulesRegistered` hook: ```typescript async beforeModulesRegistered(app: Application, hono: Hono): Promise { try { await this.initialize() } catch (error) { console.error('[MyPlugin] Initialization failed:', error) // Decide whether to throw the error (which fails app startup) or continue throw error } } ``` ### 2. Resource Cleanup Consider implementing cleanup logic: ```typescript export class ResourcePlugin implements IPlugin { private resources: any[] = [] async beforeModulesRegistered(app: Application, hono: Hono): Promise { // Setup resources this.resources = await this.createResources() // Setup cleanup on process termination process.on('SIGTERM', () => this.cleanup()) process.on('SIGINT', () => this.cleanup()) } private async cleanup(): Promise { console.log('[ResourcePlugin] Cleaning up resources...') await Promise.all(this.resources.map((resource) => resource.close())) } } ``` ### 3. Configuration Validation Validate plugin configuration early: ```typescript export class ConfigurablePlugin implements IPlugin { constructor(private config: any) { this.validateConfig(config) } private validateConfig(config: any): void { if (!config.requiredProperty) { throw new Error('[ConfigurablePlugin] requiredProperty is missing from configuration') } } } ``` ## Real-world Plugin Examples ### CORS Plugin ```typescript export class CorsPlugin implements IPlugin { constructor(private origins: string[] = ['*']) {} async beforeModulesRegistered(app: Application, hono: Hono): Promise { hono.use('*', async (c, next) => { c.header('Access-Control-Allow-Origin', this.origins.join(', ')) c.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization') if (c.req.method === 'OPTIONS') { return c.text('', 204) } await next() }) } } ``` ### Metrics Plugin ```typescript export class MetricsPlugin implements IPlugin { private requestCount = 0 private requestDurations: number[] = [] async beforeModulesRegistered(app: Application, hono: Hono): Promise { hono.use('*', async (c, next) => { const start = Date.now() this.requestCount++ await next() const duration = Date.now() - start this.requestDurations.push(duration) }) } async afterModulesRegistered(app: Application, hono: Hono): Promise { hono.get('/metrics', (c) => { const avgDuration = this.requestDurations.length > 0 ? this.requestDurations.reduce((a, b) => a + b, 0) / this.requestDurations.length : 0 return c.json({ totalRequests: this.requestCount, averageResponseTime: avgDuration, uptime: process.uptime(), }) }) } } ``` Plugins are a powerful way to keep your application modular and maintainable while adding the functionality you need. # Filters Exception filters provide a mechanism for handling unhandled exceptions that occur during the request-response cycle. They allow you to catch specific types of errors and send a customized response to the client. By default, HonestJS includes a built-in global exception filter that handles standard `Error` objects and `HttpException`s from Hono. However, you can create custom filters to handle specific error cases. ## Use Cases Exception filters are essential for centralized error handling. Common use cases include: - **Logging Errors:** Capturing and logging unhandled exceptions for debugging purposes. - **Custom Error Responses:** Formatting error responses to match your API's error schema. - **Handling Specific Exceptions:** Creating dedicated filters for specific exceptions, such as `NotFoundException` or database-related errors. - **Monitoring and Alerting:** Sending notifications to a monitoring service when critical errors occur. ## Creating an Exception Filter An exception filter is a class that implements the `IFilter` interface. This interface has a `catch` method that receives the exception and the Hono `Context`. ```typescript interface IFilter { catch(exception: T, context: Context): void | Promise } ``` - `exception`: The exception object that was thrown. - `context`: The Hono `Context` object. The `catch` method is responsible for handling the exception and sending a response to the client. **Example:** A custom filter for a `NotFoundException`. ```typescript import { IFilter } from 'honestjs' import { Context } from 'hono' import { NotFoundException } from 'http-essentials' export class NotFoundExceptionFilter implements IFilter { catch(exception: NotFoundException, context: Context) { if (exception instanceof NotFoundException) { context.status(404) return context.json({ statusCode: 404, message: 'The requested resource was not found.', error: 'Not Found', timestamp: new Date().toISOString(), path: context.req.path, }) } } } ``` This filter specifically catches a `NotFoundException` and returns a formatted 404 response. ## Applying Filters Filters can be applied at the global, controller, or handler level using the `@UseFilters()` decorator. ### Global Filters Global filters are ideal for handling common exceptions across an entire application. ```typescript const { hono } = await Application.create(AppModule, { components: { filters: [new NotFoundExceptionFilter()], }, }) ``` ### Controller- and Handler-Level Filters You can also apply filters to a specific controller or route handler, which is useful for handling exceptions specific to a particular part of your application. ```typescript import { Controller, Get, UseFilters } from 'honestjs' import { CustomExceptionFilter } from './filters/custom.filter' @Controller('/special') @UseFilters(CustomExceptionFilter) export class SpecialController { @Get() doSomethingSpecial() { // If this handler throws a CustomException, it will be caught by the CustomExceptionFilter. } } ``` ## How It Works When an unhandled exception is thrown during the request lifecycle, the HonestJS exception handling mechanism takes over. It searches for a suitable filter to handle the exception, starting at the handler level, then moving to the controller level, and finally checking for global filters. The first filter that matches the exception type is used to handle the exception. If no specific filter is found, the default global exception filter is used. Using exception filters allows you to centralize error handling logic and provide consistent, well-formatted error responses. # Pipes Pipes are classes that can transform or validate incoming data before it reaches the route handler. They are particularly useful for handling Data Transfer Objects (DTOs) from request bodies, parameters, or queries. A pipe must implement the `IPipe` interface, which has a single `transform` method. ```typescript interface IPipe { transform(value: T, metadata: ArgumentMetadata): any | Promise } ``` - `value`: The incoming data from the request. - `metadata`: An object containing information about the parameter being processed, such as its type and the decorator used. ## Use Cases Pipes have two main use cases: 1. **Transformation:** Converting data from one form to another (e.g., converting a string ID to a number). 2. **Validation:** Checking if incoming data meets certain criteria and throwing an exception if it does not. ## Creating a Pipe ### Transformation Example Here is a simple pipe that transforms a string value into a number. ```typescript import { IPipe, ArgumentMetadata } from 'honestjs' import { BadRequestException } from 'http-essentials' export class ParseIntPipe implements IPipe { transform(value: string, metadata: ArgumentMetadata): number { const val = parseInt(value, 10) if (isNaN(val)) { throw new BadRequestException('Validation failed: not a number') } return val } } ``` ### Validation Example A more common use case is validating an incoming request body against a DTO class. This is often done with libraries like `class-validator` and `class-transformer`. Here is an example of a `ValidationPipe`: ```typescript import { IPipe, ArgumentMetadata } from 'honestjs' import { plainToClass } from 'class-transformer' import { validate } from 'class-validator' import { BadRequestException } from 'http-essentials' export class ValidationPipe implements IPipe { async transform(value: any, { metatype }: ArgumentMetadata) { if (!metatype || !this.toValidate(metatype)) { return value } const object = plainToClass(metatype, value) const errors = await validate(object) if (errors.length > 0) { throw new BadRequestException('Validation failed', { details: errors }) } return value } private toValidate(metatype: Function): boolean { const types: Function[] = [String, Boolean, Number, Array, Object] return !types.includes(metatype) } } ``` This pipe: 1. Checks if the parameter has a specific DTO type. 2. Uses `class-transformer` to convert the plain JavaScript object from the request into an instance of the DTO class. 3. Uses `class-validator` to validate the object based on the decorators in the DTO. 4. Throws an exception if validation fails. ## Applying Pipes Pipes can be applied at the global, controller, or handler level using the `@UsePipes()` decorator. They can also be applied to a specific parameter. ### Global Pipes Global pipes are useful for applying validation to all incoming data. A global `ValidationPipe` can be set to ensure all DTOs are validated automatically. ```typescript [src/main.ts] const { hono } = await Application.create(AppModule, { components: { pipes: [new ValidationPipe()], }, }) ``` ### Parameter-Level Pipes You can also apply pipes to a specific parameter within a route handler. ```typescript import { Body, Param, ParseIntPipe } from 'honestjs' @Controller('/users') export class UsersController { @Get('/:id') async findOne(@Param('id', ParseIntPipe) id: number) { // The `id` will be a number here, not a string. return this.usersService.findById(id) } } ``` In this example, `ParseIntPipe` is applied only to the `id` parameter. ## Execution Order When multiple pipes are applied, they are executed in the following order: 1. Global Pipes 2. Controller-Level Pipes 3. Handler-Level Pipes 4. Parameter-Level Pipes If multiple pipes are applied at the same level (e.g., `@UsePipes(PipeA, PipeB)`), they are executed in the order they are listed. Each pipe's output becomes the next pipe's input. Pipes are a powerful tool for creating robust and type-safe APIs, reducing boilerplate code in route handlers. # Guards A guard is a class that implements the `IGuard` interface. It determines whether a given request should be handled by the route handler based on certain conditions (e.g., permissions, roles). If a guard denies access, HonestJS throws a `ForbiddenException`. ## Use Cases Guards are primarily used for authorization. Common use cases include: - **Authentication:** Checking if a user is logged in. - **Role-Based Access Control (RBAC):** Permitting access only to users with specific roles. - **IP Whitelisting/Blacklisting:** Allowing or blocking requests from certain IP addresses. - **API Key Validation:** Ensuring that a valid API key is present in the request. ## Creating a Guard A guard must implement the `IGuard` interface, which has a single `canActivate` method. This method should return `true` if the request is allowed, and `false` otherwise. It can also be asynchronous and return a `Promise`. The `canActivate` method receives the Hono `Context` as its argument, which gives you access to the request, response, and other context-specific information. **Example:** A simple authentication guard. ```typescript import type { IGuard } from 'honestjs' import type { Context } from 'hono' export class AuthGuard implements IGuard { async canActivate(c: Context): Promise { const authHeader = c.req.header('Authorization') // In a real app, you would validate the token return !!authHeader } } ``` ## Applying Guards Guards can be applied at the global, controller, or handler level using the `@UseGuards()` decorator. ### Global Guards Global guards are applied to every route in your application. **Example:** ```typescript import { Application } from 'honestjs' import { AuthGuard } from './guards/auth.guard' const { hono } = await Application.create(AppModule, { components: { guards: [new AuthGuard()], }, }) ``` ### Controller-level Guards You can apply guards to all routes within a controller. **Example:** ```typescript import { Controller, UseGuards } from 'honestjs' import { RolesGuard } from './guards/roles.guard' @Controller('/admin') @UseGuards(RolesGuard) export class AdminController { // All routes in this controller are protected by the RolesGuard } ``` ### Handler-level Guards You can also apply guards to a specific route handler. **Example:** ```typescript import { Controller, Post, UseGuards } from 'honestjs' import { OwnerGuard } from './guards/owner.guard' @Controller('/posts') export class PostsController { @Post('/:id/delete') @UseGuards(OwnerGuard) deletePost() { // This route is protected by the OwnerGuard } } ``` ## Execution Order When multiple guards are applied to a route, they are executed in the following order: 1. Global Guards 2. Controller-Level Guards 3. Handler-Level Guards If multiple guards are applied at the same level (e.g., `@UseGuards(GuardA, GuardB)`), they are executed in the order they are listed. The request is denied if any guard returns `false`. ## Role-Based Access Control Guards are ideal for implementing role-based access control (RBAC). You can create a `Roles` decorator to associate roles with specific handlers, and a `RolesGuard` to check for those roles. **1. Create a `Roles` decorator:** This decorator will attach role metadata to a route. ::: code-group ```typescript [roles.decorator.ts] export const Roles = (...roles: string[]) => { return (target: any, key: string, descriptor: PropertyDescriptor) => { Reflect.defineMetadata('roles', roles, descriptor.value) } } ``` ::: **2. Create the `RolesGuard`:** This guard will retrieve the roles from the metadata and check if the user has the required role. ::: code-group ```typescript [roles.guard.ts] import type { IGuard } from 'honestjs' import type { Context } from 'hono' export class RolesGuard implements IGuard { async canActivate(c: Context): Promise { const requiredRoles = Reflect.getMetadata('roles', c.handler) if (!requiredRoles) { return true // No roles required, access granted } const user = c.get('user') // Assume user is attached to context return requiredRoles.some((role) => user.roles?.includes(role)) } } ``` ::: **3. Use them together:** ::: code-group ```typescript [admin.controller.ts] import { Controller, Get, UseGuards } from 'honestjs' import { Roles } from '../decorators/roles.decorator' import { RolesGuard } from '../guards/roles.guard' @Controller('/admin') @UseGuards(RolesGuard) export class AdminController { @Get('/data') @Roles('admin') getAdminData() { // This route requires the 'admin' role } } ``` ::: This example demonstrates how you can build a flexible and declarative authorization system with guards. # Middleware Middleware consists of functions executed before the route handler. They can perform a wide range of tasks, such as logging, authentication, and request parsing. ## Use Cases Middleware is versatile and can be used for a variety of cross-cutting concerns, including: - **Logging:** Recording details about incoming requests and outgoing responses. - **Authentication/Authorization:** Validating credentials or permissions before allowing access to a route. - **Request Parsing:** Parsing request bodies (e.g., JSON, form-data). - **Security:** Adding security headers (e.g., CORS, CSRF protection). - **Caching:** Implementing caching strategies to improve performance. - **Rate Limiting:** Protecting your API from abuse. ## Creating Middleware A middleware is a class that implements the `IMiddleware` interface, which has a single `use` method. This method receives the Hono `Context` and a `next` function. **Example:** A simple logger middleware. ```typescript import type { IMiddleware } from 'honestjs' import type { Context, Next } from 'hono' export class LoggerMiddleware implements IMiddleware { async use(c: Context, next: Next) { console.log(`[${c.req.method}] ${c.req.url} - Request received`) await next() console.log(`Response status: ${c.res.status}`) } } ``` ## Applying Middleware Middleware can be applied using the `@UseMiddleware()` decorator or by configuring it globally when creating the application. ### Global Middleware Global middleware is applied to every route in your application. It is useful for cross-cutting concerns like logging, security headers, or request ID generation. Global middleware can be registered in the `Application.create` options. **Example:** ::: code-group ```typescript [main.ts] import { Application } from 'honestjs' import { LoggerMiddleware } from './middleware/logger.middleware' const { hono } = await Application.create(AppModule, { components: { middleware: [new LoggerMiddleware()], }, }) ``` ::: ### Controller-Level Middleware Middleware can be applied to all routes within a specific controller by using the `@UseMiddleware()` decorator on the controller class. **Example:** ```typescript import { Controller } from 'honestjs' import { UseMiddleware } from 'honestjs' import { AuthenticationMiddleware } from './middleware/auth.middleware' @Controller('/profile') @UseMiddleware(AuthenticationMiddleware) export class ProfileController { // All routes in this controller will be protected by the AuthenticationMiddleware. } ``` ### Handler-Level Middleware Middleware can also be applied to a specific route handler. This is useful when a middleware is only needed for one or a few routes. **Example:** ```typescript import { Controller, Get, UseMiddleware } from 'honestjs' import { SpecificTaskMiddleware } from './middleware/specific-task.middleware' @Controller('/tasks') export class TasksController { @Get('/:id') @UseMiddleware(SpecificTaskMiddleware) getTask() { // This route is the only one that uses the SpecificTaskMiddleware. } } ``` ## Execution Order Middleware is executed in the following order: 1. Global Middleware 2. Controller-Level Middleware 3. Handler-Level Middleware If multiple middleware are applied at the same level (e.g., `@UseMiddleware(MiddlewareA, MiddlewareB)`), they are executed in the order they are listed. ## Using Hono Middleware HonestJS is built on Hono, so you can use any existing Hono middleware. The `mvc` example shows how to integrate Hono's `jsxRenderer` middleware. To use a Hono middleware, create a simple wrapper class. ::: code-group ```typescript [src/middleware/hono.middleware.ts] // A wrapper to use Hono's jsxRenderer with HonestJS. import { jsxRenderer } from 'hono/jsx-renderer' import type { IMiddleware } from 'honestjs' import type { Context, Next } from 'hono' export class HonoMiddleware implements IMiddleware { constructor(private middleware: any) {} use(c: Context, next: Next) { return this.middleware(c, next) } } ``` ```typescript [src/main.ts] // In main.ts const { hono } = await Application.create(AppModule, { components: { middleware: [new HonoMiddleware(jsxRenderer(MainLayout))], }, }) ``` ::: This approach allows you to seamlessly integrate the rich ecosystem of Hono middleware into your HonestJS application. # Components Components in HonestJS are reusable building blocks that provide cross-cutting functionality across your application. They include middleware, guards, pipes, filters, and the Layout component for server-side rendering. ## Overview Components are applied to controllers and route handlers to add functionality like authentication, validation, logging, and error handling. They can be applied at different levels: - **Global**: Applied to all routes in the application - **Controller**: Applied to all routes in a specific controller - **Handler**: Applied to a specific route handler ## Available Components ### [Middleware](./middleware.md) Functions that run before the route handler and can modify the request/response. Used for logging, authentication, request parsing, and more. ```typescript @UseMiddleware(LoggerMiddleware, AuthMiddleware) @Controller('users') class UsersController { @UseMiddleware(RateLimitMiddleware) @Get() getUsers() {} } ``` ### [Guards](./guards.md) Functions that determine whether a request should be handled by the route handler. Used for authentication and authorization. ```typescript @UseGuards(AuthGuard, RoleGuard) @Controller('admin') class AdminController { @UseGuards(AdminGuard) @Get('users') getUsers() {} } ``` ### [Pipes](./pipes.md) Functions that transform input data before it reaches the route handler. Used for validation, transformation, and data sanitization. ```typescript @UsePipes(ValidationPipe, TransformPipe) @Controller('users') class UsersController { @UsePipes(CustomPipe) @Post() createUser(@Body() user: UserDto) {} } ``` ### [Filters](./filters.md) Functions that catch and handle exceptions thrown during request processing. Used for error handling and response formatting. ```typescript @UseFilters(HttpExceptionFilter, ValidationExceptionFilter) @Controller('users') class UsersController { @UseFilters(CustomExceptionFilter) @Get() getUsers() {} } ``` ## Component Execution Order Components are executed in the following order: 1. **Global Components** (middleware, guards, pipes, filters) 2. **Controller Components** (middleware, guards, pipes, filters) 3. **Handler Components** (middleware, guards, pipes, filters) 4. **Route Handler** (your controller method) 5. **Exception Filters** (if an exception is thrown) ## Global Configuration You can configure global components when creating your application: ```typescript const { app, hono } = await Application.create(AppModule, { components: { middleware: [new LoggerMiddleware()], guards: [new AuthGuard()], pipes: [new ValidationPipe()], filters: [new HttpExceptionFilter()], }, }) ``` ## Component Decorators Use decorators to apply components to controllers and handlers: ```typescript import { UseMiddleware, UseGuards, UsePipes, UseFilters } from 'honestjs' @Controller('users') @UseMiddleware(LoggerMiddleware) @UseGuards(AuthGuard) @UsePipes(ValidationPipe) @UseFilters(HttpExceptionFilter) class UsersController { @Get() @UseMiddleware(RateLimitMiddleware) @UseGuards(RoleGuard) getUsers() {} } ``` ## Creating Custom Components You can create custom components by implementing the appropriate interfaces: ### Custom Middleware ```typescript import type { IMiddleware } from 'honestjs' import type { Context, Next } from 'hono' export class CustomMiddleware implements IMiddleware { async use(c: Context, next: Next) { console.log(`[${c.req.method}] ${c.req.url}`) await next() } } ``` ### Custom Guard ```typescript import type { IGuard } from 'honestjs' import type { Context } from 'hono' export class CustomGuard implements IGuard { async canActivate(context: Context): Promise { const token = context.req.header('authorization') return !!token } } ``` ### Custom Pipe ```typescript import type { IPipe, ArgumentMetadata } from 'honestjs' export class CustomPipe implements IPipe { transform(value: unknown, metadata: ArgumentMetadata): unknown { // Transform the value return value } } ``` ### Custom Filter ```typescript import type { IFilter } from 'honestjs' import type { Context } from 'hono' export class CustomFilter implements IFilter { async catch(exception: Error, context: Context) { console.error('Custom filter caught:', exception) return context.json({ error: 'Custom error' }, 500) } } ``` ## Best Practices ### 1. Use Appropriate Component Types Choose the right component for your use case: - **Middleware**: For request/response modification, logging, etc. - **Guards**: For authentication and authorization - **Pipes**: For data transformation and validation - **Filters**: For exception handling - **Layout**: For server-side rendering ### 2. Apply Components at the Right Level ```typescript // ✅ Good - Apply authentication globally @UseGuards(AuthGuard) @Controller('api') class ApiController { // All routes require authentication } // ✅ Good - Apply specific logic at handler level @Controller('api') class ApiController { @UseGuards(AdminGuard) @Get('admin') getAdminData() {} } ``` ### 3. Keep Components Focused Each component should have a single responsibility: ```typescript // ✅ Good - Single responsibility export class LoggerMiddleware implements IMiddleware { async use(c: Context, next: Next) { console.log(`[${c.req.method}] ${c.req.url}`) await next() } } // ❌ Avoid - Multiple responsibilities export class LoggerMiddleware implements IMiddleware { async use(c: Context, next: Next) { console.log(`[${c.req.method}] ${c.req.url}`) // Authentication logic - should be in a guard const token = c.req.header('authorization') if (!token) { return c.json({ error: 'Unauthorized' }, 401) } await next() } } ``` ### 4. Handle Errors Gracefully Always handle errors in your components: ```typescript export class CustomMiddleware implements IMiddleware { async use(c: Context, next: Next) { try { await next() } catch (error) { console.error('Middleware error:', error) throw error // Re-throw to let filters handle it } } } ``` ### 5. Use Type Safety Leverage TypeScript for better type safety: ```typescript export class ValidationPipe implements IPipe { transform(value: unknown, metadata: ArgumentMetadata): unknown { if (metadata.type === 'body' && metadata.metatype) { // Validate against the expected type return validate(value, metadata.metatype) } return value } } ``` Components provide a powerful way to add cross-cutting functionality to your HonestJS applications while maintaining clean, organized, and maintainable code. # Routing Routing is the mechanism that maps incoming requests to the correct controller methods. HonestJS uses a combination of decorators on your controller classes and methods to define the routes for your application. ## Controllers Controllers are responsible for handling incoming requests and returning responses. To create a controller, you use the `@Controller()` decorator on a class. The `@Controller()` decorator can take an optional route prefix and configuration options. This prefix will be applied to all routes defined within that controller. **Example:** ```typescript [src/modules/users/users.controller.ts] import { Controller, Get } from 'honestjs' @Controller('users') export class UsersController { @Get() findAll() { return 'This route handles GET requests to /users' } @Get(':id') findOne() { return 'This route handles GET requests to /users/:id' } } ``` In this example, the `@Controller('users')` decorator sets a base path for all routes in `UsersController`. ## Controller Options The `@Controller()` decorator accepts configuration options: ```typescript import { Controller, VERSION_NEUTRAL } from 'honestjs' @Controller('users', { prefix: 'api', // Override global prefix version: 2, // Override global version }) export class UsersController { // Routes will be accessible at /api/v2/users } @Controller('health', { version: VERSION_NEUTRAL, // Accessible with and without version }) export class HealthController { // Routes will be accessible at both /health and /v1/health } ``` ## HTTP Method Decorators To handle specific HTTP methods, HonestJS provides decorators for all standard methods: - `@Get(path?: string, options?: HttpMethodOptions)` - `@Post(path?: string, options?: HttpMethodOptions)` - `@Put(path?: string, options?: HttpMethodOptions)` - `@Delete(path?: string, options?: HttpMethodOptions)` - `@Patch(path?: string, options?: HttpMethodOptions)` - `@Options(path?: string, options?: HttpMethodOptions)` - `@All(path?: string, options?: HttpMethodOptions)` These decorators are used on methods within a controller. They can take an optional path segment and options that will be appended to the controller's prefix. ```typescript [src/modules/users/users.controller.ts] import { Controller, Get, Post, Body, Param } from 'honestjs' import UsersService from './users.service' import type { CreateUserDto, User } from './users.types' @Controller('users') class UsersController { constructor(private readonly usersService: UsersService) {} @Post() async createUser(@Body() body: CreateUserDto): Promise { return await this.usersService.create(body) } @Get() async getUsers(): Promise { return await this.usersService.findAll() } @Get(':id') async getUser(@Param('id') id: string): Promise { return await this.usersService.findById(id) } @Put(':id') async updateUser(@Param('id') id: string, @Body() body: Partial): Promise { return await this.usersService.update(id, body) } @Delete(':id') async deleteUser(@Param('id') id: string): Promise { await this.usersService.delete(id) } } ``` ## Route Parameters You can capture dynamic values from the URL path using route parameters. To define a route parameter, use a colon (`:`) in the path. To access its value, use the `@Param()` decorator in the method signature. ```typescript [src/modules/users/users.controller.ts] import { Controller, Get, Param } from 'honestjs' @Controller('users') class UsersController { @Get(':id') async getUser(@Param('id') id: string): Promise { const user = await this.usersService.findById(id) if (!user) { throw new Error('User not found') } return user } @Get(':userId/posts/:postId') async getUserPost(@Param('userId') userId: string, @Param('postId') postId: string): Promise { return await this.postsService.findByUserAndId(userId, postId) } } ``` In this case, a `GET` request to `/users/123` will call the `getUser` method, and the value of `id` will be `"123"`. ## Route Versioning HonestJS supports flexible API versioning at multiple levels. You can set a global version, a version per controller, or even a version per route. ### Global Versioning You can set a global version prefix when creating your application. ```typescript [src/main.ts] const { app, hono } = await Application.create(AppModule, { routing: { version: 1, }, }) ``` With this configuration, all routes will be prefixed with `/v1`. For example, `GET /users` becomes `GET /v1/users`. ### Controller-level Versioning You can override the global version or set a specific version for a controller. ```typescript import { Controller, VERSION_NEUTRAL } from 'honestjs' @Controller('users', { version: 2 }) export class UsersController { // Routes in this controller will be prefixed with /v2 } @Controller('health', { version: VERSION_NEUTRAL }) export class HealthController { // This controller's routes will be accessible both with and without version } @Controller('legacy', { version: null }) export class LegacyController { // This controller's routes will not have a version prefix, even if global version is set } ``` ### Route-level Versioning You can also specify a version directly on a route decorator, which will override any controller or global settings. ```typescript import { Controller, Get, VERSION_NEUTRAL } from 'honestjs' @Controller('users') export class UsersController { @Get('legacy', { version: 1 }) getLegacyUsers() { // This will be accessible at /v1/users/legacy } @Get('new', { version: 2 }) getNewUsers() { // This will be accessible at /v2/users/new } @Get('status', { version: VERSION_NEUTRAL }) getStatus() { // This will be accessible at both /users/status and /v1/users/status } } ``` ### Multiple Versions You can make routes available at multiple versions simultaneously: ```typescript @Controller('users', { version: [1, 2] }) export class UsersController { @Get() getUsers() { // This will be accessible at both /v1/users and /v2/users } } @Controller('users') export class UsersController { @Get('compatible', { version: [1, 2, 3] }) getCompatibleUsers() { // This will be accessible at /v1/users/compatible, /v2/users/compatible, and /v3/users/compatible } } ``` ### Version-Neutral Routes Sometimes you may want certain routes to be accessible both with and without version prefixes. For this, you can use the `VERSION_NEUTRAL` symbol. ```typescript import { Controller, Get, VERSION_NEUTRAL } from 'honestjs' @Controller('health') export class HealthController { @Get('status', { version: VERSION_NEUTRAL }) getStatus() { // This route will be accessible at both: // - /health/status (without version) // - /v1/health/status (with version, if global version is set) return { status: 'ok' } } } ``` Version-neutral routes are particularly useful for: - Health check endpoints - Status endpoints - Public API endpoints that should remain accessible regardless of versioning - Utility endpoints that don't change between API versions ## Route Prefixes Similar to versioning, you can control route prefixes at multiple levels: ::: code-group ```typescript [main.ts] // Global prefix const { app, hono } = await Application.create(AppModule, { routing: { prefix: 'api', }, }) ``` ```typescript [users.controller.ts] // Controller-level prefix @Controller('users', { prefix: 'v2/api' }) export class UsersController { // Routes will be accessible at /v2/api/users } ``` ```typescript [users.controller.ts] // Route-level prefix @Controller('users') export class UsersController { @Get('data', { prefix: 'internal' }) getInternalData() { // This will be accessible at /internal/users/data } } ``` ::: ## Route Information You can get information about all registered routes in your application: ```typescript const { app, hono } = await Application.create(AppModule) // Get all routes const routes = app.getRoutes() console.log( 'Registered routes:', routes.map((r) => r.fullPath) ) // Get routes by controller const userRoutes = app.getRoutes().filter((r) => r.controller === 'UsersController') // Get routes by method const getRoutes = app.getRoutes().filter((r) => r.method === 'GET') ``` ## Route Examples Here are some comprehensive examples of route definitions: ```typescript import { Controller, Get, Post, Put, Delete, Body, Param, Query } from 'honestjs' @Controller('api/users', { version: 1 }) export class UsersController { // GET /v1/api/users @Get() async findAll(@Query('page') page?: string, @Query('limit') limit?: string) { return await this.usersService.findAll({ page: parseInt(page || '1'), limit: parseInt(limit || '10'), }) } // GET /v1/api/users/:id @Get(':id') async findOne(@Param('id') id: string) { return await this.usersService.findById(id) } // POST /v1/api/users @Post() async create(@Body() createUserDto: CreateUserDto) { return await this.usersService.create(createUserDto) } // PUT /v1/api/users/:id @Put(':id') async update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { return await this.usersService.update(id, updateUserDto) } // DELETE /v1/api/users/:id @Delete(':id') async remove(@Param('id') id: string) { return await this.usersService.delete(id) } // GET /v1/api/users/:id/posts @Get(':id/posts') async getUserPosts(@Param('id') userId: string) { return await this.postsService.findByUserId(userId) } } @Controller('health', { version: VERSION_NEUTRAL }) export class HealthController { // GET /health/status AND GET /v1/health/status @Get('status') async getStatus() { return { status: 'ok', timestamp: new Date().toISOString() } } } ``` By combining these features, you can build well-structured and maintainable routing for your applications with flexible versioning and prefixing strategies. # Parameters Parameter decorators allow you to extract data from the incoming request and inject it directly into your route handler's parameters. This provides a clean and declarative way to access request data with full type safety. ## Built-in Parameter Decorators HonestJS comes with a comprehensive set of built-in decorators for common use cases: ### Request Data Decorators - `@Body(data?: string)`: Extracts the request body. Can optionally extract a specific property from the body. - `@Param(data?: string)`: Extracts route parameters. - `@Query(data?: string)`: Extracts query parameters. - `@Header(data?: string)`: Extracts request headers. ### Context Decorators - `@Req()` or `@Request()`: Injects the entire Hono request object. - `@Res()` or `@Response()`: Injects the Hono response object. - `@Ctx()` or `@Context()`: Injects the Hono `Context` object. - `@Var(data: string)` or `@Variable(data: string)`: Extracts a variable from the context (e.g., set by a middleware). ## Basic Usage Examples ```typescript import { Body, Controller, Ctx, Get, Param, Post, Query, Header } from 'honestjs' import type { Context } from 'hono' import type { CreateUserDto, UpdateUserDto } from './users.types' @Controller('users') export class UsersController { @Post() async createUser(@Body() createUserDto: CreateUserDto) { return await this.usersService.create(createUserDto) } @Get(':id') async findUserById(@Param('id') id: string) { return await this.usersService.findById(id) } @Get() async findAllUsers(@Query('page') page?: string, @Query('limit') limit?: string, @Query('role') role?: string) { return await this.usersService.findAll({ page: parseInt(page || '1'), limit: parseInt(limit || '10'), role, }) } @Put(':id') async updateUser(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { return await this.usersService.update(id, updateUserDto) } @Get('profile') async getProfile(@Header('authorization') auth: string) { // Extract authorization header const token = auth?.replace('Bearer ', '') return await this.usersService.getProfileByToken(token) } @Get('context') async getContext(@Ctx() context: Context) { // Access the full Hono context return { path: context.req.path, method: context.req.method, url: context.req.url, headers: Object.fromEntries(context.req.header()), } } } ``` ## Advanced Parameter Extraction ### Extracting Specific Properties You can extract specific properties from request data: ```typescript @Controller('users') export class UsersController { @Post() async createUser(@Body('name') name: string, @Body('email') email: string, @Body('age') age: number) { // Extract specific properties from request body return await this.usersService.create({ name, email, age }) } @Get(':id/posts') async getUserPosts( @Param('id') userId: string, @Query('category') category?: string, @Query('published') published?: string ) { return await this.postsService.findByUserId(userId, { category, published: published === 'true', }) } @Get('search') async searchUsers(@Query('q') query: string, @Query('fields') fields?: string) { const searchFields = fields ? fields.split(',') : ['name', 'email'] return await this.usersService.search(query, searchFields) } } ``` ### Working with Headers Headers are commonly used for authentication and other metadata: ```typescript @Controller('auth') export class AuthController { @Post('login') async login( @Body() credentials: { email: string; password: string }, @Header('user-agent') userAgent?: string, @Header('x-forwarded-for') clientIP?: string ) { return await this.authService.login(credentials, { userAgent, clientIP, }) } @Get('profile') async getProfile(@Header('authorization') auth: string, @Header('accept-language') language?: string) { const token = auth?.replace('Bearer ', '') return await this.authService.getProfile(token, language) } } ``` ### Context Variables You can extract variables set by middleware: ::: code-group ```typescript [src/middleware/auth.middleware.ts] // Middleware that sets user in context class AuthMiddleware implements IMiddleware { async use(c: Context, next: Next) { const token = c.req.header('authorization')?.replace('Bearer ', '') if (token) { const user = await this.authService.validateToken(token) c.set('user', user) } await next() } } ``` ```typescript [src/controllers/users.controller.ts] @Controller('users') @UseMiddleware(AuthMiddleware) export class UsersController { @Get('me') async getCurrentUser(@Var('user') user: User) { return user } @Get('me/posts') async getMyPosts(@Var('user') user: User) { return await this.postsService.findByUserId(user.id) } } ``` ::: ## Type Safety and Validation Parameter decorators work seamlessly with TypeScript types and can be combined with pipes for validation: ```typescript import { Body, Controller, Get, Param, Post, UsePipes } from 'honestjs' import { ValidationPipe } from './pipes/validation.pipe' import { TransformPipe } from './pipes/transform.pipe' interface CreateUserDto { name: string email: string age: number } interface PaginationQuery { page: number limit: number } @Controller('users') export class UsersController { @Post() @UsePipes(ValidationPipe, TransformPipe) async createUser(@Body() createUserDto: CreateUserDto) { // createUserDto is validated and transformed return await this.usersService.create(createUserDto) } @Get() async findAll(@Query() query: PaginationQuery) { // query is typed as PaginationQuery return await this.usersService.findAll(query) } @Get(':id') async findOne(@Param('id') id: string) { // id is typed as string return await this.usersService.findById(id) } } ``` ## Creating Custom Parameter Decorators You can create your own custom parameter decorators using the `createParamDecorator` helper function. This is useful for abstracting complex logic for extracting data from the request. ### Basic Custom Decorator ```typescript import { createParamDecorator } from 'honestjs' import type { Context } from 'hono' 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') const ip = forwardedFor?.split(',')[0].trim() || realIP || cfIP || 'unknown' return ip }) export const UserAgent = createParamDecorator('userAgent', (_, ctx: Context) => { return ctx.req.header('user-agent') || 'unknown' }) export const RequestId = createParamDecorator('requestId', (_, ctx: Context) => { return ctx.get('requestId') || 'unknown' }) ``` ### Advanced Custom Decorator ```typescript import { createParamDecorator } from 'honestjs' import type { Context } from 'hono' export const CurrentUser = createParamDecorator('user', (_, ctx: Context) => { const token = ctx.req.header('authorization')?.replace('Bearer ', '') if (!token) { return null } // Decode JWT and return user return decodeJWT(token) }) export const Pagination = createParamDecorator('pagination', (_, ctx: Context) => { const page = parseInt(ctx.req.query('page') || '1') const limit = parseInt(ctx.req.query('limit') || '10') return { page: Math.max(1, page), limit: Math.min(100, Math.max(1, limit)), offset: (page - 1) * limit, } }) export const SortOptions = createParamDecorator('sort', (_, ctx: Context) => { const sortBy = ctx.req.query('sortBy') || 'createdAt' const sortOrder = ctx.req.query('sortOrder') || 'desc' return { sortBy, sortOrder: sortOrder === 'asc' ? 'asc' : 'desc', } }) ``` ### Using Custom Decorators ```typescript @Controller('users') export class UsersController { @Get() async findAll( @Pagination() pagination: { page: number; limit: number; offset: number }, @SortOptions() sort: { sortBy: string; sortOrder: string } ) { return await this.usersService.findAll(pagination, sort) } @Get('me') async getCurrentUser(@CurrentUser() user: User | null) { if (!user) { throw new Error('User not authenticated') } return user } @Get('analytics') async getAnalytics(@ClientIP() clientIP: string, @UserAgent() userAgent: string, @RequestId() requestId: string) { return await this.analyticsService.track({ clientIP, userAgent, requestId, }) } } ``` ## Parameter Metadata The framework tracks parameter metadata for each handler method, which is used for: - **Type information**: Parameter types are captured for validation and transformation - **Decorator data**: Additional data passed to decorators is stored - **Factory functions**: Custom transformation logic is preserved - **Context indices**: Special handling for context parameters This metadata enables advanced features like automatic validation, transformation, and documentation generation. ## Best Practices ### 1. Use Type Annotations Always provide type annotations for better type safety: ```typescript // ✅ Good @Get(':id') async findOne(@Param('id') id: string) { return await this.usersService.findById(id) } // ❌ Avoid @Get(':id') async findOne(@Param('id') id) { return await this.usersService.findById(id) } ``` ### 2. Handle Optional Parameters Use optional types for parameters that might not be present: ```typescript @Get() async findAll( @Query('page') page?: string, @Query('limit') limit?: string, @Query('search') search?: string ) { return await this.usersService.findAll({ page: page ? parseInt(page) : 1, limit: limit ? parseInt(limit) : 10, search }) } ``` ### 3. Validate Required Parameters Throw errors for required parameters that are missing: ```typescript @Get(':id') async findOne(@Param('id') id: string) { if (!id) { throw new Error('User ID is required') } return await this.usersService.findById(id) } ``` ### 4. Use Custom Decorators for Complex Logic Extract complex parameter extraction logic into custom decorators: ```typescript // ✅ Good - Custom decorator export const AuthenticatedUser = createParamDecorator('user', (_, ctx: Context) => { const token = ctx.req.header('authorization')?.replace('Bearer ', '') if (!token) { throw new Error('Authentication required') } return decodeJWT(token) }) @Get('me') async getCurrentUser(@AuthenticatedUser() user: User) { return user } // ❌ Avoid - Complex logic in controller @Get('me') async getCurrentUser(@Header('authorization') auth: string) { const token = auth?.replace('Bearer ', '') if (!token) { throw new Error('Authentication required') } const user = decodeJWT(token) return user } ``` ### 5. Combine with Pipes for Validation Use pipes to validate and transform parameters: ```typescript @Post() @UsePipes(ValidationPipe) async createUser(@Body() createUserDto: CreateUserDto) { // createUserDto is validated before reaching this method return await this.usersService.create(createUserDto) } ``` By following these practices, you can create clean, type-safe, and maintainable route handlers that effectively extract and process request data. # Error Handling HonestJS provides a comprehensive error handling system that allows you to catch, process, and respond to errors in a consistent and organized way. ## Overview The error handling system in HonestJS consists of several components: - **Exception Filters**: Classes that catch and handle specific types of exceptions - **Global Error Handlers**: Application-wide error handling configuration - **HTTP Exceptions**: Built-in exception types for HTTP-specific errors - **Error Response Formatting**: Standardized error response structure ## Exception Filters Exception filters are the primary way to handle errors in HonestJS. They catch exceptions thrown during request processing and can return custom responses. ### Creating Exception Filters ```typescript import type { IFilter } from 'honestjs' import type { Context } from 'hono' export class HttpExceptionFilter implements IFilter { async catch(exception: Error, context: Context) { console.error('HTTP Exception:', exception) return context.json( { status: 500, message: 'Internal Server Error', timestamp: new Date().toISOString(), path: context.req.path, }, 500 ) } } export class ValidationExceptionFilter implements IFilter { async catch(exception: Error, context: Context) { console.error('Validation Error:', exception) return context.json( { status: 400, message: 'Validation Error', details: exception.message, timestamp: new Date().toISOString(), path: context.req.path, }, 400 ) } } ``` ### Applying Exception Filters ```typescript import { Controller, Get, UseFilters } from 'honestjs' import { HttpExceptionFilter, ValidationExceptionFilter } from './filters' @Controller('users') @UseFilters(HttpExceptionFilter, ValidationExceptionFilter) class UsersController { @Get(':id') async getUser(@Param('id') id: string) { if (!id) { throw new Error('User ID is required') } const user = await this.usersService.findById(id) if (!user) { throw new Error('User not found') } return user } } ``` ### Filter-Specific Exception Handling You can create filters that handle specific types of exceptions: ```typescript export class DatabaseExceptionFilter implements IFilter { async catch(exception: Error, context: Context) { if (exception.message.includes('database') || exception.message.includes('connection')) { console.error('Database Error:', exception) return context.json( { status: 503, message: 'Service Unavailable', details: 'Database connection error', timestamp: new Date().toISOString(), path: context.req.path, }, 503 ) } // Return undefined to let other filters handle it return undefined } } export class AuthenticationExceptionFilter implements IFilter { async catch(exception: Error, context: Context) { if (exception.message.includes('unauthorized') || exception.message.includes('authentication')) { console.error('Authentication Error:', exception) return context.json( { status: 401, message: 'Unauthorized', details: 'Authentication required', timestamp: new Date().toISOString(), path: context.req.path, }, 401 ) } return undefined } } ``` ## HTTP Exceptions HonestJS integrates with Hono's HTTPException for HTTP-specific errors: ```typescript import { HTTPException } from 'hono/http-exception' import { Controller, Get, Param } from 'honestjs' @Controller('users') class UsersController { @Get(':id') async getUser(@Param('id') id: string) { if (!id) { throw new HTTPException(400, { message: 'User ID is required' }) } const user = await this.usersService.findById(id) if (!user) { throw new HTTPException(404, { message: 'User not found' }) } return user } @Post() async createUser(@Body() userData: CreateUserDto) { if (!userData.email) { throw new HTTPException(422, { message: 'Validation failed', details: { email: 'Email is required' }, }) } return await this.usersService.create(userData) } } ``` ## Global Error Handling You can configure global error handling when creating your application: ```typescript import { Application } from 'honestjs' import type { Context } from 'hono' const { app, hono } = await Application.create(AppModule, { // Custom error handler for unhandled exceptions onError: (error: Error, context: Context) => { console.error('Unhandled error:', error) // Log error details console.error('Stack trace:', error.stack) console.error('Request path:', context.req.path) console.error('Request method:', context.req.method) // Return appropriate response based on environment if (process.env.NODE_ENV === 'production') { return context.json( { status: 500, message: 'Internal Server Error', timestamp: new Date().toISOString(), path: context.req.path, }, 500 ) } else { return context.json( { status: 500, message: error.message, stack: error.stack, timestamp: new Date().toISOString(), path: context.req.path, }, 500 ) } }, // Custom not found handler notFound: (context: Context) => { return context.json( { status: 404, message: 'Not Found', details: `Route ${context.req.path} not found`, timestamp: new Date().toISOString(), suggestions: ['/api/users', '/api/posts', '/api/health'], }, 404 ) }, }) ``` ## Error Response Format HonestJS provides a standardized error response format: ```typescript interface ErrorResponse { status: number message: string timestamp: string path: string requestId?: string code?: string details?: Record errors?: Array<{ property: string; constraints: Record }> } ``` ### Example Error Responses ```json { "status": 400, "message": "Validation Error", "timestamp": "2024-01-01T12:00:00.000Z", "path": "/api/users", "code": "VALIDATION_ERROR", "details": { "email": "Invalid email format" } } ``` ```json { "status": 404, "message": "User not found", "timestamp": "2024-01-01T12:00:00.000Z", "path": "/api/users/123", "code": "NOT_FOUND" } ``` ```json { "status": 500, "message": "Internal Server Error", "timestamp": "2024-01-01T12:00:00.000Z", "path": "/api/users", "requestId": "req-123", "details": { "stack": "Error: Database connection failed..." } } ``` ## Custom Error Classes You can create custom error classes for better error handling: ```typescript export class ValidationError extends Error { constructor(message: string, public field: string, public value: any) { super(message) this.name = 'ValidationError' } } export class BusinessLogicError extends Error { constructor(message: string, public code: string, public details?: Record) { super(message) this.name = 'BusinessLogicError' } } export class DatabaseError extends Error { constructor(message: string, public operation: string, public table?: string) { super(message) this.name = 'DatabaseError' } } ``` ### Using Custom Error Classes ```typescript @Controller('users') class UsersController { @Post() async createUser(@Body() userData: CreateUserDto) { if (!userData.email) { throw new ValidationError('Email is required', 'email', userData.email) } if (!this.isValidEmail(userData.email)) { throw new ValidationError('Invalid email format', 'email', userData.email) } try { return await this.usersService.create(userData) } catch (error) { if (error.message.includes('duplicate')) { throw new BusinessLogicError('User already exists', 'USER_EXISTS', { email: userData.email }) } throw error } } } ``` ### Handling Custom Errors ```typescript export class ValidationExceptionFilter implements IFilter { async catch(exception: Error, context: Context) { if (exception instanceof ValidationError) { return context.json( { status: 400, message: 'Validation Error', code: 'VALIDATION_ERROR', details: { field: exception.field, value: exception.value, message: exception.message, }, timestamp: new Date().toISOString(), path: context.req.path, }, 400 ) } return undefined } } export class BusinessLogicExceptionFilter implements IFilter { async catch(exception: Error, context: Context) { if (exception instanceof BusinessLogicError) { return context.json( { status: 409, message: exception.message, code: exception.code, details: exception.details, timestamp: new Date().toISOString(), path: context.req.path, }, 409 ) } return undefined } } ``` ## Error Handling Best Practices ### 1. Use Appropriate HTTP Status Codes ```typescript // ✅ Good - Use appropriate status codes throw new HTTPException(400, { message: 'Bad Request' }) throw new HTTPException(401, { message: 'Unauthorized' }) throw new HTTPException(403, { message: 'Forbidden' }) throw new HTTPException(404, { message: 'Not Found' }) throw new HTTPException(422, { message: 'Validation Error' }) throw new HTTPException(500, { message: 'Internal Server Error' }) // ❌ Avoid - Don't use generic 500 for client errors throw new HTTPException(500, { message: 'Validation Error' }) ``` ### 2. Provide Meaningful Error Messages ```typescript // ✅ Good - Clear, actionable error messages throw new Error('User ID is required') throw new Error('Email must be a valid email address') throw new Error('Password must be at least 8 characters long') // ❌ Avoid - Vague error messages throw new Error('Invalid input') throw new Error('Error occurred') ``` ### 3. Include Request Context ```typescript export class LoggerExceptionFilter implements IFilter { async catch(exception: Error, context: Context) { console.error('Error details:', { message: exception.message, stack: exception.stack, path: context.req.path, method: context.req.method, headers: Object.fromEntries(context.req.header()), timestamp: new Date().toISOString(), }) return undefined // Let other filters handle the response } } ``` ### 4. Handle Different Environments ```typescript export class ProductionExceptionFilter implements IFilter { async catch(exception: Error, context: Context) { if (process.env.NODE_ENV === 'production') { // Don't expose internal details in production return context.json( { status: 500, message: 'Internal Server Error', timestamp: new Date().toISOString(), path: context.req.path, }, 500 ) } // Show detailed error in development return context.json( { status: 500, message: exception.message, stack: exception.stack, timestamp: new Date().toISOString(), path: context.req.path, }, 500 ) } } ``` ### 5. Use Request IDs for Tracking ```typescript export class RequestIdMiddleware implements IMiddleware { async use(c: Context, next: Next) { const requestId = c.req.header('x-request-id') || generateRequestId() c.set('requestId', requestId) // Add request ID to response headers c.header('x-request-id', requestId) await next() } } export class ErrorTrackingFilter implements IFilter { async catch(exception: Error, context: Context) { const requestId = context.get('requestId') console.error(`[${requestId}] Error:`, { message: exception.message, stack: exception.stack, path: context.req.path, }) return context.json( { status: 500, message: 'Internal Server Error', requestId, timestamp: new Date().toISOString(), path: context.req.path, }, 500 ) } } ``` ### 6. Chain Exception Filters ```typescript @Controller('users') @UseFilters( LoggerExceptionFilter, // Log all errors ValidationExceptionFilter, // Handle validation errors AuthenticationExceptionFilter, // Handle auth errors HttpExceptionFilter // Handle HTTP exceptions ) class UsersController { // Your controller methods } ``` By following these practices, you can create robust error handling that provides clear, actionable feedback to clients while maintaining security and debugging capabilities. # Dependency Injection HonestJS includes a simple and effective dependency injection (DI) container that manages the instantiation of your classes and their dependencies. This allows you to write loosely coupled, testable, and maintainable code. ## Core Concepts The DI system is built around a few key concepts: - **Providers:** These are classes that can be "provided" by the DI container. In HonestJS, the most common providers are **Services**, but any class decorated with `@Service()` can be a provider. - **Consumers:** These are classes that consume providers. **Controllers** are the most common consumers. - **Injection:** This is the process of providing an instance of a dependency to a consumer. HonestJS primarily uses **constructor injection**. - **Container:** The DI container manages the lifecycle of all providers and handles dependency resolution. ## Services Services are the primary candidates for dependency injection. They are typically used to encapsulate business logic, data access, or other concerns. To define a service, use the `@Service()` decorator. **Example:** ::: code-group ```typescript [app.service.ts] import { Service } from 'honestjs' @Service() class AppService { helloWorld(): string { return 'Hello, World!' } } export default AppService ``` ::: The `@Service()` decorator marks the `AppService` class as a provider that can be managed by the DI container. ## Constructor Injection To inject a service into a controller (or another service), you simply declare it as a parameter in the consumer's constructor. **Example:** ::: code-group ```typescript [app.controller.ts] import { Controller, Get } from 'honestjs' import AppService from './app.service' @Controller() class AppController { constructor(private readonly appService: AppService) {} @Get() helloWorld(): string { return this.appService.helloWorld() } } export default AppController ``` ::: When the `AppController` is instantiated, the DI container will: 1. See that `AppController` has a dependency on `AppService`. 2. Look for an existing instance of `AppService`. 3. If no instance exists, it will create a new one. 4. It will then pass the `AppService` instance to the `AppController` constructor. ## How it Works HonestJS's DI container maintains a map of class constructors to their instances. When a class is resolved: - If an instance already exists in the container (i.e., it's a singleton), it's returned immediately. - If not, the container inspects the constructor's parameter types using `reflect-metadata`. - It then recursively resolves each dependency. - Finally, it creates a new instance of the class with the resolved dependencies and stores it for future use. ## Service Registration Services are automatically registered when you use the `@Service()` decorator. However, you can also register them explicitly in your modules: ::: code-group ```typescript [users.module.ts] import { Module } from 'honestjs' import { UsersController } from './users.controller' import { UsersService } from './users.service' import { DatabaseService } from './database.service' @Module({ controllers: [UsersController], services: [UsersService, DatabaseService], }) class UsersModule {} ``` ::: ## Complex Dependency Chains The DI container can handle complex dependency chains automatically: ```typescript import { Service } from 'honestjs' @Service() class DatabaseService { connect() { console.log('Database connected') } } @Service() class LoggerService { log(message: string) { console.log(`[LOG] ${message}`) } } @Service() class UserRepository { constructor(private readonly database: DatabaseService, private readonly logger: LoggerService) {} findAll() { this.logger.log('Finding all users') this.database.connect() return ['user1', 'user2'] } } @Service() class UserService { constructor(private readonly userRepository: UserRepository) {} getUsers() { return this.userRepository.findAll() } } @Controller('users') class UsersController { constructor(private readonly userService: UserService) {} @Get() getUsers() { return this.userService.getUsers() } } ``` In this example, when `UsersController` is instantiated, the container will: 1. Resolve `UserService` 2. Resolve `UserRepository` (dependency of `UserService`) 3. Resolve `DatabaseService` and `LoggerService` (dependencies of `UserRepository`) 4. Create all instances in the correct order 5. Inject them into their respective constructors ## Custom Container You can provide a custom DI container if you need special functionality: ```typescript import type { DiContainer } from 'honestjs' import type { Constructor } from 'honestjs' class CustomContainer implements DiContainer { private instances = new Map() resolve(target: Constructor): T { if (this.instances.has(target)) { return this.instances.get(target) } // Custom resolution logic const instance = new target() this.instances.set(target, instance) return instance } register(target: Constructor, instance: T): void { this.instances.set(target, instance) } } const { app, hono } = await Application.create(AppModule, { container: new CustomContainer(), }) ``` ## Service Lifecycle ### Singleton Scope By default, all registered providers are singletons. This means that the same instance of a service is shared across the entire application: ```typescript @Service() class CounterService { private count = 0 increment() { return ++this.count } getCount() { return this.count } } @Controller('counter1') class CounterController1 { constructor(private counter: CounterService) {} @Get('increment') increment() { return { count: this.counter.increment() } } } @Controller('counter2') class CounterController2 { constructor(private counter: CounterService) {} @Get('count') getCount() { return { count: this.counter.getCount() } } } ``` In this example, both controllers share the same `CounterService` instance, so the count will be shared between them. ## Error Handling ### Circular Dependencies The container can detect and throw an error for circular dependencies: ```typescript @Service() class ServiceA { constructor(private serviceB: ServiceB) {} } @Service() class ServiceB { constructor(private serviceA: ServiceA) {} // This will throw an error } ``` Error message: `Circular dependency detected: ServiceA -> ServiceB -> ServiceA` ### Missing Dependencies If a dependency cannot be resolved, the container will throw a clear error: ```typescript @Controller('users') class UsersController { constructor(private userService: UserService) {} // Error if UserService is not registered } ``` ## Best Practices ### 1. Use Constructor Injection Prefer constructor injection over property injection: ```typescript // ✅ Good @Controller('users') class UsersController { constructor(private readonly userService: UserService) {} } // ❌ Avoid @Controller('users') class UsersController { @Inject() private userService: UserService } ``` ### 2. Keep Services Focused Each service should have a single responsibility: ```typescript // ✅ Good - Single responsibility @Service() class UserService { async findById(id: string) { // User-specific logic } } @Service() class EmailService { async sendEmail(to: string, subject: string) { // Email-specific logic } } // ❌ Avoid - Multiple responsibilities @Service() class UserService { async findById(id: string) { // User logic } async sendEmail(to: string, subject: string) { // Email logic - should be in EmailService } } ``` ### 3. Use Interfaces for Better Testability Define interfaces for your services to make them easier to test: ```typescript interface IUserService { findById(id: string): Promise create(user: CreateUserDto): Promise } @Service() class UserService implements IUserService { async findById(id: string): Promise { // Implementation } async create(user: CreateUserDto): Promise { // Implementation } } @Controller('users') class UsersController { constructor(private readonly userService: IUserService) {} } ``` ### 4. Avoid Circular Dependencies Design your services to avoid circular references: ```typescript // ✅ Good - No circular dependency @Service() class UserService { async findById(id: string) { // User logic } } @Service() class PostService { constructor(private userService: UserService) {} async findByUserId(userId: string) { // Post logic that uses UserService } } // ❌ Avoid - Circular dependency @Service() class UserService { constructor(private postService: PostService) {} } @Service() class PostService { constructor(private userService: UserService) {} } ``` ### 5. Use Module Organization Organize your services into logical modules: ::: code-group ```typescript [modules/users/users.module.ts] @Module({ controllers: [UsersController], services: [UserService, UserRepository], }) class UsersModule {} ``` ```typescript [modules/posts/posts.module.ts] @Module({ controllers: [PostsController], services: [PostService, PostRepository], }) class PostsModule {} ``` ```typescript [app.module.ts] @Module({ imports: [UsersModule, PostsModule], services: [DatabaseService, LoggerService], }) class AppModule {} ``` ::: ## Type Limitations The DI system relies on TypeScript's `emitDecoratorMetadata` feature, which uses `reflect-metadata`. This works well for classes, but it has a limitation: you cannot inject a dependency using an interface as a type hint, because interfaces do not exist at runtime. ```typescript // ❌ This won't work @Controller('users') class UsersController { constructor(private userService: IUserService) {} // Interface not available at runtime } // ✅ This works @Controller('users') class UsersController { constructor(private userService: UserService) {} // Class is available at runtime } ``` By following these principles, you can build robust and well-structured applications with clear separation of concerns and excellent testability.