Backend engineering with Modular Monolith, bounded contexts, and Hono. **ALWAYS use when implementing ANY backend code within contexts, Hono APIs, HTTP routes, or service layer logic.** Use proactively for context isolation, minimal shared kernel, and API design. Examples - "create API in context", "implement repository", "add use case", "context structure", "Hono route", "API endpoint", "context communication", "DI container".
Implements backend APIs using Modular Monolith architecture with bounded contexts and Hono. Proactively triggers when creating context-specific routes, services, or repositories.
/plugin marketplace add marcioaltoe/claude-craftkit/plugin install architecture-design@claude-craftkitThis skill inherits all available tools. When active, it can use any tool Claude has access to.
You are an expert Backend Engineer specializing in Modular Monoliths with bounded contexts, Clean Architecture within each context, and modern TypeScript/Bun backend development with Hono framework. You follow "Duplication Over Coupling", KISS, and YAGNI principles.
You should proactively assist when:
For Modular Monolith principles, bounded contexts, and minimal shared kernel rules, see clean-architecture skill
apps/nexus/src/
├── contexts/ # Bounded contexts
│ ├── auth/ # Auth context (complete vertical slice)
│ │ ├── domain/ # Auth-specific domain
│ │ ├── application/ # Auth-specific use cases
│ │ └── infrastructure/ # Auth-specific infrastructure
│ │
│ ├── tax/ # Tax context (complete vertical slice)
│ │ ├── domain/ # Tax-specific domain
│ │ ├── application/ # Tax-specific use cases
│ │ └── infrastructure/ # Tax-specific infrastructure
│ │
│ └── [other contexts]/
│
└── shared/ # Minimal shared kernel
├── domain/
│ └── value-objects/ # ONLY UUIDv7 and Timestamp!
└── infrastructure/
├── container/ # DI Container
├── http/ # HTTP Server
└── database/ # Database Client
For complete backend tech stack details, see project-standards skill
Quick Reference:
→ Use project-standards skill for comprehensive tech stack information
This section provides practical implementation examples. For architectural principles, dependency rules, and testing strategies, see clean-architecture skill
┌─────────────────────────────────────────┐
│ Infrastructure Layer │
│ (repositories, adapters, container) │
│ │
│ ├── HTTP Layer (framework-specific) │
│ │ ├── server/ (Hono adapter) │
│ │ ├── controllers/ (self-register) │
│ │ ├── schemas/ (Zod validation) │
│ │ ├── middleware/ │
│ │ └── plugins/ │
└────────────────┬────────────────────────┘
│ depends on ↓
┌────────────────▼────────────────────────┐
│ Application Layer │
│ (use cases, DTOs) │
└────────────────┬────────────────────────┘
│ depends on ↓
┌────────────────▼────────────────────────┐
│ Domain Layer │
│ (entities, value objects, ports) │
│ (NO DEPENDENCIES) │
└─────────────────────────────────────────┘
Contains: Entities, Value Objects, Ports (interfaces), Domain Services
Example: Value Object
// domain/value-objects/email.value-object.ts
export class Email {
private constructor(private readonly value: string) {}
static create(value: string): Email {
if (!value) {
throw new Error("Email is required");
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
throw new Error(`Invalid email format: ${value}`);
}
return new Email(value.toLowerCase());
}
equals(other: Email): boolean {
return this.value === other.value;
}
toString(): string {
return this.value;
}
}
Example: Entity
// domain/entities/user.entity.ts
import type { Email } from "@/domain/value-objects/email.value-object";
export class User {
private _isActive: boolean = true;
private readonly _createdAt: Date;
constructor(
private readonly _id: string, // UUIDv7 string generated by Bun.randomUUIDv7()
private _email: Email,
private _name: string,
private _hashedPassword: string
) {
this._createdAt = new Date();
}
// Domain behavior
deactivate(): void {
if (!this._isActive) {
throw new Error(`User ${this._id} is already inactive`);
}
this._isActive = false;
}
changeEmail(newEmail: Email): void {
if (this._email.equals(newEmail)) {
return;
}
this._email = newEmail;
}
// Getters (no setters - controlled behavior)
get id(): string {
return this._id;
}
get email(): Email {
return this._email;
}
get name(): string {
return this._name;
}
get isActive(): boolean {
return this._isActive;
}
get createdAt(): Date {
return this._createdAt;
}
}
Example: Port (Interface)
// domain/ports/repositories/user.repository.ts
import type { User } from "@/domain/entities/user.entity";
import type { Result } from "@/domain/shared/result";
// NO "I" prefix
export interface UserRepository {
findById(id: string): Promise<Result<User | null>>; // id is UUIDv7 string
findByEmail(email: string): Promise<Result<User | null>>;
save(user: User): Promise<Result<void>>;
update(user: User): Promise<Result<void>>;
delete(id: string): Promise<Result<void>>; // id is UUIDv7 string
}
Contains: Use Cases, DTOs, Mappers
Example: Use Case
// application/use-cases/create-user.use-case.ts
import type { UserRepository } from "@/domain/ports";
import type { CacheService } from "@/domain/ports";
import type { Logger } from "@/domain/ports";
import { User } from "@/domain/entities";
import { Email } from "@/domain/value-objects";
import type { CreateUserDto, UserResponseDto } from "@/application/dtos";
export class CreateUserUseCase {
constructor(
private readonly userRepository: UserRepository,
private readonly cacheService: CacheService,
private readonly logger: Logger
) {}
async execute(dto: CreateUserDto): Promise<UserResponseDto> {
this.logger.info("Creating user", { email: dto.email });
// 1. Validate business rules
const existingUser = await this.userRepository.findByEmail(dto.email);
if (existingUser.isSuccess && existingUser.value) {
throw new Error(`User with email ${dto.email} already exists`);
}
// 2. Create domain objects
const id = Bun.randomUUIDv7(); // Generate UUIDv7 using Bun native API
const email = Email.create(dto.email);
const user = new User(id, email, dto.name, dto.hashedPassword);
// 3. Persist
const saveResult = await this.userRepository.save(user);
if (saveResult.isFailure) {
throw new Error(`Failed to save user: ${saveResult.error}`);
}
// 4. Invalidate cache
await this.cacheService.del(`user:${email.toString()}`);
// 5. Return DTO
return {
id: user.id.toString(),
email: user.email.toString(),
name: user.name,
isActive: user.isActive,
createdAt: user.createdAt.toISOString(),
};
}
}
Example: DTO
// application/dtos/user.dto.ts
import { z } from "zod";
export const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(2).max(100),
});
export type CreateUserDto = z.infer<typeof createUserSchema>;
export interface UserResponseDto {
id: string;
email: string;
name: string;
isActive: boolean;
createdAt: string;
}
Contains: Repositories (database), Adapters (external services), Container (DI)
Example: Repository Implementation
// infrastructure/repositories/user.repository.impl.ts
import { eq } from "drizzle-orm";
import type { DatabaseConnection } from "@gesttione-solutions/neptunus";
import type { UserRepository } from "@/domain/ports/repositories/user.repository";
import type { User } from "@/domain/entities/user.entity";
import { Result } from "@/domain/shared/result";
import { users } from "@/infrastructure/database/drizzle/schema/users.schema";
export class UserRepositoryImpl implements UserRepository {
constructor(private readonly db: DatabaseConnection) {}
async findById(id: string): Promise<Result<User | null>> {
// id is UUIDv7 string
try {
const [row] = await this.db
.select()
.from(users)
.where(eq(users.id, id))
.limit(1);
if (!row) {
return Result.ok(null);
}
return Result.ok(this.toDomain(row));
} catch (error) {
return Result.fail(`Failed to find user: ${error}`);
}
}
async save(user: User): Promise<Result<void>> {
try {
await this.db.insert(users).values({
id: user.id, // UUIDv7 string
email: user.email.toString(),
name: user.name,
isActive: user.isActive,
createdAt: user.createdAt,
});
return Result.ok(undefined);
} catch (error) {
return Result.fail(`Failed to save user: ${error}`);
}
}
private toDomain(row: typeof users.$inferSelect): User {
// Reconstruct domain entity from database row
const id = row.id; // UUIDv7 string from database
const email = Email.create(row.email);
return new User(id, email, row.name, row.hashedPassword);
}
}
Example: Adapter (External Service)
// infrastructure/adapters/cache.service.impl.ts
import { Redis } from "ioredis";
import type { CacheService } from "@/domain/ports/cache.service";
import type { EnvConfig } from "@/domain/ports/env-config.port";
export class CacheServiceImpl implements CacheService {
private redis: Redis;
constructor(config: EnvConfig) {
this.redis = new Redis({
host: config.REDIS_HOST,
port: config.REDIS_PORT,
});
}
async set(
key: string,
value: string,
expirationInSeconds?: number
): Promise<void> {
if (expirationInSeconds) {
await this.redis.set(key, value, "EX", expirationInSeconds);
} else {
await this.redis.set(key, value);
}
}
async get(key: string): Promise<string | null> {
return await this.redis.get(key);
}
async del(key: string): Promise<void> {
await this.redis.del(key);
}
async flushAll(): Promise<void> {
await this.redis.flushall();
}
}
Location: infrastructure/http/
Contains: Server, Controllers (self-registering), Schemas (Zod validation), Middleware, Plugins
Example: Schema
// infrastructure/http/schemas/user.schema.ts
import { z } from "zod";
export const createUserRequestSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(2).max(100),
});
export const userResponseSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string(),
isActive: z.boolean(),
createdAt: z.string().datetime(),
});
Example: Self-Registering Controller
// infrastructure/http/controllers/user.controller.ts
import type { HttpServer } from "@/domain/ports/http-server";
import { HttpMethod } from "@/domain/ports/http-server";
import type { CreateUserUseCase } from "@/application/use-cases/create-user.use-case";
import type { GetUserUseCase } from "@/application/use-cases/get-user.use-case";
/**
* UserController
*
* Infrastructure layer (HTTP) - handles HTTP requests.
* Thin layer that delegates to use cases.
*
* Responsibilities:
* 1. Register routes in constructor
* 2. Validate requests (Zod schemas)
* 3. Delegate to use cases
* 4. Format responses (return DTOs)
*
* NO business logic here! Controllers should be thin.
*
* Pattern: Constructor Injection + Auto-registration
*/
export class UserController {
constructor(
private readonly httpServer: HttpServer, // ✅ HttpServer port injected
private readonly createUserUseCase: CreateUserUseCase, // ✅ Use case injected
private readonly getUserUseCase: GetUserUseCase // ✅ Use case injected
) {
this.registerRoutes(); // ✅ Auto-register routes in constructor
}
private registerRoutes(): void {
// POST /users - Create new user
this.httpServer.route(HttpMethod.POST, "/users", async (context) => {
try {
const dto = context.req.valid("json"); // Validated by middleware
const user = await this.createUserUseCase.execute(dto);
return context.json(user, 201);
} catch (error) {
console.error("Error creating user:", error);
return context.json({ error: "Internal server error" }, 500);
}
});
// GET /users/:id - Get user by ID
this.httpServer.route(HttpMethod.GET, "/users/:id", async (context) => {
try {
const { id } = context.req.param();
const user = await this.getUserUseCase.execute(id);
return context.json(user, 200);
} catch (error) {
console.error("Error getting user:", error);
return context.json({ error: "User not found" }, 404);
}
});
}
}
Example: HttpServer Port (Domain Layer)
// domain/ports/http-server.ts
export enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
PATCH = "PATCH",
}
export type HttpHandler = (context: unknown) => Promise<Response | unknown>;
export interface HttpServer {
route(method: HttpMethod, url: string, handler: HttpHandler): void;
listen(port: number): void;
}
Example: HonoHttpServer Implementation (Infrastructure Layer)
// infrastructure/http/server/hono-http-server.adapter.ts
import type { Context } from "hono";
import { Hono } from "hono";
import {
type HttpHandler,
HttpMethod,
type HttpServer,
} from "@/domain/ports/http-server";
export class HonoHttpServer implements HttpServer {
private readonly app: Hono;
constructor() {
this.app = new Hono();
}
route(method: HttpMethod, url: string, handler: HttpHandler): void {
const honoHandler = async (c: Context) => {
try {
const result = await handler(c);
return result instanceof Response ? result : (result as Response);
} catch (error) {
console.error("Error handling request:", error);
return c.json({ error: "Internal server error" }, 500);
}
};
switch (method) {
case HttpMethod.GET:
this.app.get(url, honoHandler);
break;
case HttpMethod.POST:
this.app.post(url, honoHandler);
break;
case HttpMethod.PUT:
this.app.put(url, honoHandler);
break;
case HttpMethod.DELETE:
this.app.delete(url, honoHandler);
break;
case HttpMethod.PATCH:
this.app.patch(url, honoHandler);
break;
default:
throw new Error(`Unsupported HTTP method: ${method}`);
}
}
listen(port: number): void {
console.log(`Server is running on http://localhost:${port}`);
Bun.serve({
fetch: this.app.fetch,
port,
});
}
getApp(): Hono {
return this.app;
}
}
Example: Bootstrap (Entry Point)
// main.ts
import { getAppContainer, TOKENS } from "@/infrastructure/di";
const DEFAULT_PORT = 3000;
/**
* Application Bootstrap
*
* 1. Get application container (DI)
* 2. Initialize controllers (they auto-register routes in constructor)
* 3. Start HTTP server
*/
async function bootstrap() {
// Get application container (singleton)
const container = getAppContainer();
// Initialize controllers (they auto-register routes in constructor)
container.resolve(TOKENS.systemController);
container.resolve(TOKENS.userController);
// Resolve and start HTTP server
const server = container.resolve(TOKENS.httpServer);
const port = Number(process.env.PORT) || DEFAULT_PORT;
server.listen(port);
}
// Entry point with error handling
bootstrap().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});
Key Benefits:
// infrastructure/container/container.ts
export type Lifetime = "singleton" | "scoped" | "transient";
export type Token<T> = symbol & { readonly __type?: T };
export interface Provider<T> {
lifetime: Lifetime;
useValue?: T;
useFactory?: (c: Container) => T;
}
export class Container {
private readonly registry: Map<Token<unknown>, Provider<unknown>>;
private readonly singletons: Map<Token<unknown>, unknown>;
private readonly scopedCache: Map<Token<unknown>, unknown>;
private constructor(
registry: Map<Token<unknown>, Provider<unknown>>,
singletons: Map<Token<unknown>, unknown>,
scopedCache?: Map<Token<unknown>, unknown>
) {
this.registry = registry;
this.singletons = singletons;
this.scopedCache = scopedCache ?? new Map();
}
static createRoot(): Container {
return new Container(new Map(), new Map(), new Map());
}
createScope(): Container {
return new Container(this.registry, this.singletons, new Map());
}
register<T>(token: Token<T>, provider: Provider<T>): void {
if (this.registry.has(token as Token<unknown>)) {
throw new Error(
`Provider already registered for token: ${token.description}`
);
}
this.registry.set(token as Token<unknown>, provider as Provider<unknown>);
}
resolve<T>(token: Token<T>): T {
const provider = this.registry.get(token as Token<unknown>);
if (!provider) {
throw new Error(`No provider registered for token: ${token.description}`);
}
// useValue
if ("useValue" in provider && provider.useValue !== undefined) {
return provider.useValue as T;
}
// singleton cache
if (provider.lifetime === "singleton") {
if (this.singletons.has(token as Token<unknown>)) {
return this.singletons.get(token as Token<unknown>) as T;
}
const instance = (provider as Provider<T>).useFactory!(this);
this.singletons.set(token as Token<unknown>, instance);
return instance;
}
// scoped cache
if (provider.lifetime === "scoped") {
if (this.scopedCache.has(token as Token<unknown>)) {
return this.scopedCache.get(token as Token<unknown>) as T;
}
const instance = (provider as Provider<T>).useFactory!(this);
this.scopedCache.set(token as Token<unknown>, instance);
return instance;
}
// transient
return (provider as Provider<T>).useFactory!(this);
}
}
// infrastructure/container/tokens.ts
import type { UserRepository } from "@/domain/ports/repositories/user.repository";
import type { CacheService } from "@/domain/ports/cache.service";
import type { Logger } from "@/domain/ports/logger.service";
import type { CreateUserUseCase } from "@/application/use-cases/create-user.use-case";
import type { UserController } from "@/infrastructure/http/controllers/user.controller";
export const TOKENS = {
// Core
Logger: Symbol("Logger") as Token<Logger>,
Config: Symbol("Config") as Token<EnvConfig>,
DatabaseConnection: Symbol("DatabaseConnection") as Token<DatabaseConnection>,
// Repositories
UserRepository: Symbol("UserRepository") as Token<UserRepository>,
// Services
CacheService: Symbol("CacheService") as Token<CacheService>,
// Use Cases
CreateUserUseCase: Symbol("CreateUserUseCase") as Token<CreateUserUseCase>,
// Controllers
UserController: Symbol("UserController") as Token<UserController>,
} as const;
// infrastructure/container/registers/register.infrastructure.ts
export function registerInfrastructure(container: Container): void {
container.register(TOKENS.Logger, {
lifetime: "singleton",
useValue: logger,
});
container.register(TOKENS.DatabaseConnection, {
lifetime: "singleton",
useValue: dbConnection,
});
container.register(TOKENS.Config, {
lifetime: "singleton",
useValue: Config.getInstance().env,
});
}
// infrastructure/container/registers/register.repositories.ts
export function registerRepositories(container: Container): void {
container.register(TOKENS.UserRepository, {
lifetime: "singleton",
useFactory: () =>
new UserRepositoryImpl(container.resolve(TOKENS.DatabaseConnection)),
});
}
// infrastructure/container/registers/register.use-cases.ts
export function registerUseCases(container: Container): void {
container.register(TOKENS.CreateUserUseCase, {
lifetime: "scoped", // Per-request
useFactory: (scope) =>
new CreateUserUseCase(
scope.resolve(TOKENS.UserRepository),
scope.resolve(TOKENS.CacheService),
scope.resolve(TOKENS.Logger)
),
});
}
// infrastructure/container/registers/register.controllers.ts
export function registerControllers(container: Container): void {
container.register(TOKENS.UserController, {
lifetime: "singleton",
useFactory: (scope) =>
new UserController(scope.resolve(TOKENS.CreateUserUseCase)),
});
}
// infrastructure/container/main.ts
export function createRootContainer(): Container {
const c = Container.createRoot();
registerInfrastructure(c);
registerRepositories(c);
registerUseCases(c);
registerControllers(c);
return c;
}
let rootContainer: Container | null = null;
export function getAppContainer(): Container {
if (!rootContainer) {
rootContainer = createRootContainer();
}
return rootContainer;
}
export function createRequestScope(root: Container): Container {
return root.createScope();
}
// infrastructure/http/app.ts
import { Hono } from "hono";
import {
getAppContainer,
createRequestScope,
} from "@/infrastructure/container/main";
import { TOKENS } from "@/infrastructure/container/tokens";
// Note: With self-registering controllers, route registration is handled by controllers themselves
const app = new Hono();
// Middleware: Create scoped container per request
app.use("*", async (c, next) => {
const rootContainer = getAppContainer();
const requestScope = createRequestScope(rootContainer);
c.set("container", requestScope);
await next();
});
// Register routes
const userController = app.get("container").resolve(TOKENS.UserController);
registerUserRoutes(app, userController);
export default app;
For complete error handling patterns (Result/Either types, Exception Hierarchy, Retry Logic, Circuit Breaker, Validation Strategies), see error-handling-patterns skill
// domain/events/user-created.event.ts
export class UserCreatedEvent {
constructor(
public readonly userId: string,
public readonly email: string,
public readonly occurredAt: Date = new Date()
) {}
}
// In Use Case
async execute(dto: CreateUserDto): Promise<UserResponseDto> {
// ... create user ...
await this.eventBus.publish(new UserCreatedEvent(user.id.toString(), user.email.toString()));
return response;
}
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.