Overview
The bootstrap pattern orchestrates dependency injection across layers. It creates instances in the correct order: adapters → use cases → routes.
bootstrap/
├── index.ts # Main orchestration
├── adapters.bootstrap.ts # Infrastructure adapters
└── use-cases.bootstrap.ts # Application use cases
The unified route system handles the presentation layer wiring automatically through handlers.
Main Orchestration
The entry point creates all dependencies in order:
// bootstrap/index.ts
import { createAdapters } from './adapters.bootstrap';
import { createUseCases } from './use-cases.bootstrap';
import { createProjectManagementRoutes } from '../presentation/http/handlers';
export function bootstrapProjectManagement() {
// 1. Create adapters (infrastructure layer)
const adapters = createAdapters();
// 2. Create use cases with adapters (application layer)
const useCases = createUseCases(adapters);
// 3. Create routes with handlers (presentation layer)
const routes = createProjectManagementRoutes(useCases);
return { adapters, useCases, routes };
}
export type ProjectManagementModule = ReturnType<typeof bootstrapProjectManagement>;
Adapters Bootstrap
Creates infrastructure adapters (repositories, external services):
// bootstrap/adapters.bootstrap.ts
import { ProjectRepository } from '../infra/persistence/project.repository';
import { ProjectQueryRepository } from '../infra/persistence/project-query.repository';
export function createAdapters() {
return {
projectRepository: new ProjectRepository(),
projectQueryRepository: new ProjectQueryRepository(),
};
}
export type Adapters = ReturnType<typeof createAdapters>;
Use Cases Bootstrap
Creates use cases with injected adapters:
// bootstrap/use-cases.bootstrap.ts
import type { Adapters } from './adapters.bootstrap';
import { CreateProjectUseCase } from '../app/use-cases/create-project.command';
import { GetProjectQuery } from '../app/use-cases/get-project.query';
import { ListProjectsQuery } from '../app/use-cases/list-projects.query';
export function createUseCases(adapters: Adapters) {
return {
createProjectUseCase: new CreateProjectUseCase(adapters.projectRepository),
getProjectQuery: new GetProjectQuery(adapters.projectQueryRepository),
listProjectsQuery: new ListProjectsQuery(adapters.projectQueryRepository),
};
}
export type UseCases = ReturnType<typeof createUseCases>;
Routes (Handlers)
Create handlers using the serverRoutes() builder:
// presentation/http/handlers/index.ts
import { serverRoutes } from '@cosmneo/onion-lasagna/http/server';
import { projectManagementRouter } from '../router';
import type { UseCases } from '../../../bootstrap/use-cases.bootstrap';
export function createProjectManagementRoutes(useCases: UseCases) {
return serverRoutes(projectManagementRouter)
.handle('projects.create', {
requestMapper: (req, ctx) => ({
name: req.body.name,
description: req.body.description,
createdBy: ctx.userId,
}),
useCase: useCases.createProjectUseCase,
responseMapper: (output) => ({
status: 201 as const,
body: { projectId: output.projectId },
}),
})
.handle('projects.list', {
requestMapper: (req) => ({
page: req.query?.page ?? 1,
pageSize: req.query?.pageSize ?? 20,
}),
useCase: useCases.listProjectsQuery,
responseMapper: (output) => ({
status: 200 as const,
body: output,
}),
})
.handle('projects.get', {
requestMapper: (req) => ({
projectId: req.pathParams.projectId,
}),
useCase: useCases.getProjectQuery,
responseMapper: (output) => ({
status: 200 as const,
body: output,
}),
})
.build();
}
The serverRoutes() builder:
- Validates requests against route schemas automatically
- Provides full type inference for
reqandctxparameters - Handles error conversion to HTTP responses
Using the Bootstrap
In your application entry point:
// src/index.ts
import { Hono } from 'hono';
import { registerHonoRoutes, onionErrorHandler } from '@cosmneo/onion-lasagna/http/frameworks/hono';
import { bootstrapProjectManagement } from '@repo/backend/bounded-contexts/project-management';
// Bootstrap the bounded context
const { routes } = bootstrapProjectManagement();
// Create Hono app
const app = new Hono();
// Error handler
app.onError((err, c) => onionErrorHandler(err, c));
// Register routes with middleware and context extraction
registerHonoRoutes(app, routes, {
middlewares: [authMiddleware],
contextExtractor: (c) => ({
userId: c.get('jwtPayload')?.sub,
}),
});
export default app;
Directory Structure
The bootstrap folder should be at the root of your bounded context:
bounded-contexts/project-management/
├── bootstrap/ # Dependency wiring
│ ├── index.ts # Main orchestration
│ ├── adapters.bootstrap.ts # Infrastructure adapters
│ └── use-cases.bootstrap.ts # Application use cases
├── app/ # Application layer
│ ├── ports/ # Use case interfaces
│ └── use-cases/ # Use case implementations
├── domain/ # Domain layer
│ ├── entities/
│ ├── value-objects/
│ └── events/
├── infra/ # Infrastructure layer
│ └── persistence/ # Repository implementations
└── presentation/ # Presentation layer
└── http/
├── routes/ # Route definitions
├── handlers/ # Route handlers
└── router.ts # Combined router
Splitting Handlers
For large bounded contexts, split handlers into separate files:
// presentation/http/handlers/projects.handlers.ts
export function createProjectHandlers(useCases: UseCases) {
return serverRoutes(projectManagementRouter)
.handle('projects.create', { ... })
.handle('projects.list', { ... })
.buildPartial(); // Returns partial routes
}
// presentation/http/handlers/tasks.handlers.ts
export function createTaskHandlers(useCases: UseCases) {
return serverRoutes(projectManagementRouter)
.handle('projects.tasks.add', { ... })
.handle('projects.tasks.list', { ... })
.buildPartial();
}
// presentation/http/handlers/index.ts
export function createProjectManagementRoutes(useCases: UseCases) {
return [
...createProjectHandlers(useCases),
...createTaskHandlers(useCases),
];
}
Benefits
- Clear dependency flow: Adapters → Use Cases → Routes
- Simplified wiring: No separate controller or validator bootstrap needed
- Type safety: Full type inference from route definitions to handlers
- Testability: Each layer can be tested independently with mocks
- Single entry point:
bootstrapProjectManagement()returns everything needed