This is the tiny 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 HonestJS applications follow a well-organized folder structure that promotes maintainability and scalability. Here's the recommended project organization: ``` Project ├── src │ ├── app.module.ts # Root application module │ ├── main.ts # Application entry point │ ├── components/ # Global/shared components │ │ ├── Footer.tsx │ │ └── Header.tsx │ ├── decorators/ # Custom decorators │ │ └── parameter.decorator.ts │ ├── layouts/ # Layout components │ │ └── MainLayout.tsx │ └── modules/ # Feature modules │ └── users/ # Example: Users module │ ├── components/ # Module-specific components │ │ └── UserList.tsx │ ├── dtos/ # Data Transfer Objects │ │ └── create-user.dto.ts │ ├── models/ # Data models │ │ └── user.model.ts │ ├── users.controller.ts │ ├── users.module.ts │ ├── users.service.ts │ ├── users.service.test.ts │ └── users.view.tsx ├── static/ # Static assets │ ├── css/ │ │ ├── main.css # Global styles │ │ └── views/ # View-specific styles │ │ └── users.css │ └── js/ │ ├── main.js # Global scripts │ └── views/ # View-specific scripts │ └── users.js └── tests/ # Test files └── users/ └── users.service.test.ts ``` #### Key Organizational Principles - **Modular Structure**: Each feature is organized into its own module with related components - **Separation of Concerns**: Controllers, services, and views are clearly separated - **Reusable Components**: Global components can be shared across modules - **Static Assets**: CSS and JavaScript files are organized by scope (global vs. view-specific) - **Testing**: Test files are co-located with the code they test ### 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 ## Project Organization Understanding how to organize your HonestJS application is crucial for building maintainable and scalable projects. Let's dive deeper into the folder structure and organizational patterns. > **📚 For a complete guide to project organization, see [Project Organization](../concepts/project-organization.md)** ### Module Organization Each feature in your application should be organized into its own module. A module typically contains: - **Controller**: Handles HTTP requests and responses - **Service**: Contains business logic and data access - **Views**: JSX components for rendering HTML (if using MVC) - **DTOs**: Data Transfer Objects for input validation - **Models**: Data structures and type definitions - **Components**: Module-specific UI components - **Tests**: Unit and integration tests ### Global vs. Module-Specific Components HonestJS supports both global and module-specific components: #### Global Components Global components are available throughout the entire application and are typically defined in the root module or configuration: ```typescript // Global middleware, guards, pipes, and filters const { app, hono } = await Application.create(AppModule, { components: { middleware: [new LoggerMiddleware()], guards: [AuthGuard], pipes: [ValidationPipe], filters: [HttpExceptionFilter], }, }) ``` #### Module-Specific Components Module-specific components are scoped to a particular feature and can be applied at the module, controller, or handler level: ```typescript @Module({ controllers: [UsersController], services: [UsersService], components: { middleware: [UsersMiddleware], guards: [UsersGuard], pipes: [UsersPipe], filters: [UsersFilter], }, }) class UsersModule {} ``` ### Static Asset Organization Static assets are organized to support both global and view-specific styling and scripting: - **Global Assets**: `main.css` and `main.js` contain styles and scripts used across the entire application - **View-Specific Assets**: View-specific CSS and JS files are organized in subdirectories to avoid conflicts and enable lazy loading ### Best Practices 1. **Keep Modules Focused**: Each module should have a single responsibility 2. **Use Consistent Naming**: Follow consistent naming conventions for files and directories 3. **Co-locate Related Files**: Keep related files close together (e.g., service and its tests) 4. **Separate Concerns**: Keep business logic, presentation, and data access separate 5. **Plan for Growth**: Structure your application to accommodate future features ## Next Steps Now that you have a basic application running and understand the project organization, 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 - **[Project Organization](./concepts/project-organization.md)** - Folder structure and organization patterns - **[Configuration](./configuration.md)** - Application configuration options - **[API Reference](./api-reference.md)** - Complete API documentation ### Core Concepts - **[Project Organization](./concepts/project-organization.md)** - Folder structure and organization patterns - **[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. # RPC Plugin The RPC Plugin automatically analyzes your HonestJS controllers and generates a fully-typed TypeScript RPC client with proper parameter typing. ## Installation ```bash npm install @honestjs/rpc-plugin # or yarn add @honestjs/rpc-plugin # or pnpm add @honestjs/rpc-plugin ``` ## Basic Setup ```typescript import { RPCPlugin } from '@honestjs/rpc-plugin' import { Application } from 'honestjs' const app = new Application({ plugins: [RPCPlugin], }) ``` ## Configuration Options ```typescript interface RPCPluginOptions { readonly controllerPattern?: string // Glob pattern for controller files (default: 'src/modules/*/*.controller.ts') readonly tsConfigPath?: string // Path to tsconfig.json (default: 'tsconfig.json') readonly outputDir?: string // Output directory for generated files (default: './generated/rpc') readonly generateOnInit?: boolean // Generate files on initialization (default: true) } ``` ## What It Generates ### TypeScript RPC Client (`client.ts`) The plugin generates a single comprehensive file that includes both the client and all type definitions: - **Controller-based organization**: Methods grouped by controller - **Type-safe parameters**: Path, query, and body parameters with proper typing - **Flexible request options**: Clean separation of params, query, body, and headers - **Error handling**: Built-in error handling with custom ApiError class - **Header management**: Easy custom header management - **Custom fetch support**: Inject custom fetch implementations for testing, middleware, and compatibility - **Integrated types**: All DTOs, interfaces, and utility types included in the same file ```typescript // Generated client usage import { ApiClient } from './generated/rpc/client' // Create client instance with base URL const apiClient = new ApiClient('http://localhost:3000') // Type-safe API calls const user = await apiClient.users.create({ body: { name: 'John', email: 'john@example.com' }, }) const users = await apiClient.users.list({ query: { page: 1, limit: 10 }, }) const user = await apiClient.users.getById({ params: { id: '123' }, }) // Set custom headers apiClient.setDefaultHeaders({ 'X-API-Key': 'your-api-key', Authorization: 'Bearer your-jwt-token', }) ``` The generated `client.ts` file contains everything you need: - **ApiClient class** with all your controller methods - **Type definitions** for requests, responses, and DTOs - **Utility types** like RequestOptions and ApiResponse - **Generated interfaces** from your controller types ## Custom Fetch Functions The RPC client supports custom fetch implementations, which is useful for: - **Testing**: Inject mock fetch functions for unit testing - **Custom Logic**: Add logging, retries, or other middleware - **Environment Compatibility**: Use different fetch implementations (node-fetch, undici, etc.) - **Interceptors**: Wrap requests with custom logic before/after execution ### Basic Custom Fetch Example ```typescript // Simple logging wrapper const loggingFetch = (input: RequestInfo | URL, init?: RequestInit) => { console.log(`[${new Date().toISOString()}] Making ${init?.method || 'GET'} request to:`, input) return fetch(input, init) } const apiClient = new ApiClient('http://localhost:3000', { fetchFn: loggingFetch, }) ``` ### Advanced Custom Fetch Examples ```typescript // Retry logic with exponential backoff const retryFetch = (maxRetries = 3) => { return async (input: RequestInfo | URL, init?: RequestInit) => { for (let i = 0; i <= maxRetries; i++) { try { const response = await fetch(input, init) if (response.ok) return response if (i === maxRetries) return response // Wait with exponential backoff await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 1000)) } catch (error) { if (i === maxRetries) throw error } } throw new Error('Max retries exceeded') } } const apiClientWithRetry = new ApiClient('http://localhost:3000', { fetchFn: retryFetch(3), }) // Request/response interceptor const interceptorFetch = (input: RequestInfo | URL, init?: RequestInit) => { // Pre-request logic const enhancedInit = { ...init, headers: { ...init?.headers, 'X-Request-ID': crypto.randomUUID(), }, } return fetch(input, enhancedInit).then((response) => { // Post-response logic console.log(`Response status: ${response.status}`) return response }) } const apiClientWithInterceptor = new ApiClient('http://localhost:3000', { fetchFn: interceptorFetch, }) ``` ### Testing with Custom Fetch ```typescript // Mock fetch for testing const mockFetch = jest.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ data: { id: '123', name: 'Test User' } }), }) const testApiClient = new ApiClient('http://test.com', { fetchFn: mockFetch, }) // Your test can now verify the mock was called expect(mockFetch).toHaveBeenCalledWith('http://test.com/api/v1/users/123', expect.objectContaining({ method: 'GET' })) ``` ## How It Works ### 1. Route Analysis - Scans your HonestJS route registry - Uses ts-morph to analyze controller source code - Extracts method signatures, parameter types, and return types - Builds comprehensive route metadata ### 2. Schema Generation - Analyzes types used in controller methods - Generates JSON schemas using ts-json-schema-generator - Creates TypeScript interfaces from schemas - Integrates with route analysis for complete type coverage ### 3. Client Generation - Groups routes by controller for organization - Generates type-safe method signatures - Creates parameter validation and typing - Builds the complete RPC client with proper error handling ## Example Generated Output ### Generated Client ```typescript export class ApiClient { get users() { return { create: async ( options: RequestOptions<{ name: string; email: string }, undefined, undefined, undefined> ): Promise> => { return this.request('POST', `/api/v1/users/`, options) }, list: async ( options?: RequestOptions ): Promise> => { return this.request('GET', `/api/v1/users/`, options) }, } } } // RequestOptions type definition export type RequestOptions< TParams = undefined, TQuery = undefined, TBody = undefined, THeaders = undefined > = (TParams extends undefined ? object : { params: TParams }) & (TQuery extends undefined ? object : { query: TQuery }) & (TBody extends undefined ? object : { body: TBody }) & (THeaders extends undefined ? object : { headers: THeaders }) ``` ## Plugin Lifecycle The plugin automatically generates files when your HonestJS application starts up (if `generateOnInit` is true). You can also manually trigger generation: ```typescript const rpcPlugin = new RPCPlugin() await rpcPlugin.analyze() // Manually trigger analysis and generation ``` ## Advanced Usage ### Custom Controller Pattern If your controllers follow a different file structure: ```typescript new RPCPlugin({ controllerPattern: 'src/controllers/**/*.controller.ts', outputDir: './src/generated/api', }) ``` ### Manual Generation Control Disable automatic generation and control when files are generated: ```typescript const rpcPlugin = new RPCPlugin({ generateOnInit: false, }) // Later in your code await rpcPlugin.analyze() ``` ## Integration with HonestJS ### Controller Example Here's how your controllers should be structured for optimal RPC generation: ```typescript import { Controller, Post, Get, Body, Param, Query } from 'honestjs' interface CreateUserDto { name: string email: string } interface ListUsersQuery { page?: number limit?: number } @Controller('/users') export class UsersController { @Post('/') async create(@Body() createUserDto: CreateUserDto): Promise { // Implementation } @Get('/') async list(@Query() query: ListUsersQuery): Promise { // Implementation } @Get('/:id') async getById(@Param('id') id: string): Promise { // Implementation } } ``` ### Module Registration Ensure your controllers are properly registered in modules: ```typescript import { Module } from 'honestjs' import { UsersController } from './users.controller' import { UsersService } from './users.service' @Module({ controllers: [UsersController], providers: [UsersService], }) export class UsersModule {} ``` ## Error Handling The generated client includes comprehensive error handling: ```typescript try { const user = await apiClient.users.create({ body: { name: 'John', email: 'john@example.com' }, }) } catch (error) { if (error instanceof ApiError) { console.error(`API Error ${error.statusCode}: ${error.message}`) } else { console.error('Unexpected error:', error) } } ``` # Project Organization Understanding how to organize your HonestJS application is crucial for building maintainable and scalable projects. This guide covers the recommended folder structure and organizational patterns. ## Recommended Folder Structure HonestJS applications follow a well-organized folder structure that promotes maintainability and scalability: ``` Project ├── src │ ├── app.module.ts # Root application module │ ├── main.ts # Application entry point │ ├── components/ # Global/shared components │ │ ├── Footer.tsx │ │ └── Header.tsx │ ├── decorators/ # Custom decorators │ │ └── parameter.decorator.ts │ ├── layouts/ # Layout components │ │ └── MainLayout.tsx │ └── modules/ # Feature modules │ └── users/ # Example: Users module │ ├── components/ # Module-specific components │ │ └── UserList.tsx │ ├── dtos/ # Data Transfer Objects │ │ └── create-user.dto.ts │ ├── models/ # Data models │ │ └── user.model.ts │ ├── users.controller.ts │ ├── users.module.ts │ ├── users.service.ts │ ├── users.service.test.ts │ └── users.view.tsx ├── static/ # Static assets │ ├── css/ │ │ ├── main.css # Global styles │ │ └── views/ # View-specific styles │ │ └── users.css │ └── js/ │ ├── main.js # Global scripts │ └── views/ # View-specific scripts │ └── users.js └── tests/ # Test files └── users/ └── users.service.test.ts ``` ## Key Organizational Principles - **Modular Structure**: Each feature is organized into its own module with related components - **Separation of Concerns**: Controllers, services, and views are clearly separated - **Reusable Components**: Global components can be shared across modules - **Static Assets**: CSS and JavaScript files are organized by scope (global vs. view-specific) - **Testing**: Test files are co-located with the code they test ## Module Organization Each feature in your application should be organized into its own module. A module typically contains: ### Core Module Files - **Controller**: Handles HTTP requests and responses - **Service**: Contains business logic and data access - **Module**: Defines the module configuration and dependencies - **Views**: JSX components for rendering HTML (if using MVC) ### Supporting Files - **DTOs**: Data Transfer Objects for input validation - **Models**: Data structures and type definitions - **Components**: Module-specific UI components - **Tests**: Unit and integration tests ### Example Module Structure ```typescript // users.module.ts @Module({ controllers: [UsersController], services: [UsersService], components: { middleware: [UsersMiddleware], guards: [UsersGuard], pipes: [UsersPipe], filters: [UsersFilter], }, }) class UsersModule {} // users.controller.ts @Controller('users') class UsersController { constructor(private readonly usersService: UsersService) {} @Get() async getUsers() { return await this.usersService.findAll() } } // users.service.ts @Service() class UsersService { async findAll() { // Business logic here } } ``` ## Component Organization HonestJS supports both global and module-specific components: ### Global Components Global components are available throughout the entire application and are typically defined in the root module or configuration: ```typescript // Global middleware, guards, pipes, and filters const { app, hono } = await Application.create(AppModule, { components: { middleware: [new LoggerMiddleware()], guards: [AuthGuard], pipes: [ValidationPipe], filters: [HttpExceptionFilter], }, }) ``` ### Module-Specific Components Module-specific components are scoped to a particular feature and can be applied at the module, controller, or handler level: ```typescript @Module({ controllers: [UsersController], services: [UsersService], components: { middleware: [UsersMiddleware], guards: [UsersGuard], pipes: [UsersPipe], filters: [UsersFilter], }, }) class UsersModule {} // Or at the controller level @Controller('users') @UseMiddleware(UsersMiddleware) @UseGuards(UsersGuard) class UsersController {} // Or at the handler level @Controller('users') class UsersController { @Get() @UseGuards(AdminGuard) @UsePipes(CustomPipe) getUsers() {} } ``` ## Static Asset Organization Static assets are organized to support both global and view-specific styling and scripting: ### Global Assets - **`main.css`** and **`main.js`** contain styles and scripts used across the entire application - These files are typically loaded on every page ### View-Specific Assets - View-specific CSS and JS files are organized in subdirectories to avoid conflicts - Enables lazy loading and better performance - Example: `static/css/views/users.css` for user-specific styles ## Layout and Component Organization ### Global Components Components that are used across multiple modules should be placed in the global `components/` directory: ```typescript // src/components/Header.tsx export function Header() { return (
) } // src/components/Footer.tsx export function Footer() { return (

© 2024 My App

) } ``` ### Layout Components Layout components define the overall structure of your application: ```typescript // src/layouts/MainLayout.tsx import { Header } from '../components/Header' import { Footer } from '../components/Footer' export function MainLayout({ children, title }: { children: any; title: string }) { return ( {title}
{children}