Claude-skill-registry create-controller
Create a controller for handling HTTP requests. Use when creating a controller to connect routes to services. Triggers on "create controller", "controller for", "HTTP handler".
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/create-controller" ~/.claude/skills/majiayu000-claude-skill-registry-create-controller && rm -rf "$T"
manifest:
skills/data/create-controller/SKILL.mdsource content
Create Controller
Creates a controller that handles HTTP requests and responses. Controllers are thin layers that extract data from requests, call services, and return responses.
Quick Reference
Location:
src/controllers/{entity-name}.controller.ts
Naming: Singular, kebab-case (e.g., note.controller.ts, user.controller.ts)
Prerequisites
Before creating a controller, ensure you have:
- Schema created with request/response types (
)src/schemas/{entity-name}.schema.ts - Service created (
)src/services/{entity-name}.service.ts
Instructions
Step 1: Create the Controller File
Create
src/controllers/{entity-name}.controller.ts
Step 2: Import Dependencies
import type { Context } from "hono"; import { {Entity}Service } from "@/services/{entity-name}.service"; import type { EntityIdParamType } from "@/schemas/shared.schema"; import type { Create{Entity}Type, {Entity}QueryParamsType, Update{Entity}Type, } from "@/schemas/{entity-name}.schema"; import type { AppEnv } from "@/schemas/app-env.schema"; import type { AuthenticatedUserContextType } from "@/schemas/user.schemas"; import { NotFoundError } from "@/errors";
Step 3: Create the Controller Class
export class {Entity}Controller { private {entity}Service: {Entity}Service; constructor({entity}Service?: {Entity}Service) { if ({entity}Service) { this.{entity}Service = {entity}Service; } else { this.{entity}Service = new {Entity}Service(); } } // Handler methods... }
Step 4: Implement CRUD Handlers
getAll
getAll = async (c: Context<AppEnv>): Promise<Response> => { const user = c.var.user as AuthenticatedUserContextType; const query = c.var.validatedQuery as {Entity}QueryParamsType; const {entities} = await this.{entity}Service.getAll(query, user); return c.json({entities}); };
getById
getById = async (c: Context<AppEnv>): Promise<Response> => { const user = c.var.user as AuthenticatedUserContextType; const { id } = c.var.validatedParams as EntityIdParamType; const {entity} = await this.{entity}Service.getById(id, user); if (!{entity}) throw new NotFoundError(); return c.json({entity}); };
create
create = async (c: Context<AppEnv>): Promise<Response> => { const user = c.var.user as AuthenticatedUserContextType; const body = c.var.validatedBody as Create{Entity}Type; const {entity} = await this.{entity}Service.create(body, user); return c.json({entity}); };
update
update = async (c: Context<AppEnv>): Promise<Response> => { const user = c.var.user as AuthenticatedUserContextType; const { id } = c.var.validatedParams as EntityIdParamType; const body = c.var.validatedBody as Update{Entity}Type; const {entity} = await this.{entity}Service.update(id, body, user); if (!{entity}) throw new NotFoundError(); return c.json({entity}); };
delete
delete = async (c: Context<AppEnv>): Promise<Response> => { const user = c.var.user as AuthenticatedUserContextType; const { id } = c.var.validatedParams as EntityIdParamType; const success = await this.{entity}Service.delete(id, user); if (!success) throw new NotFoundError(); return c.json({ message: "{Entity} deleted successfully" }); };
Patterns & Rules
Handler Method Pattern
Use arrow functions assigned to class properties for handlers:
// Correct - arrow function maintains `this` binding getAll = async (c: Context<AppEnv>): Promise<Response> => { // ... }; // Wrong - regular method loses `this` when passed as callback async getAll(c: Context<AppEnv>): Promise<Response> { // ... }
Context Variables
Data is pre-validated by middleware and stored in
c.var:
// User from auth middleware const user = c.var.user as AuthenticatedUserContextType; // Validated query params from validation middleware const query = c.var.validatedQuery as {Entity}QueryParamsType; // Validated request body from validation middleware const body = c.var.validatedBody as Create{Entity}Type; // Validated URL params from validation middleware const { id } = c.var.validatedParams as EntityIdParamType;
Dependency Injection
Allow service injection for testing:
constructor({entity}Service?: {Entity}Service) { if ({entity}Service) { this.{entity}Service = {entity}Service; } else { this.{entity}Service = new {Entity}Service(); } }
Error Handling
Controllers throw domain errors - global error handler converts to HTTP:
// Service returns null for not found const {entity} = await this.{entity}Service.getById(id, user); if (!{entity}) throw new NotFoundError(); // Service throws UnauthorizedError for permission denied // Let it propagate - global handler catches it
Response Format
Use
c.json() for all responses:
// Return entity return c.json({ entity }); // Return paginated result return c.json({ entities }); // { data: [...], total: n, page: 1, ... } // Return success message return c.json({ message: "{Entity} deleted successfully" });
AppEnv Type
Always type Context with
AppEnv:
import type { AppEnv } from "@/schemas/app-env.schema"; getAll = async (c: Context<AppEnv>): Promise<Response> => { // c.var is properly typed };
The
AppEnv interface provides types for:
- Authenticated user contextc.var.user
- Validated query parametersc.var.validatedQuery
- Validated request bodyc.var.validatedBody
- Validated URL parametersc.var.validatedParams
Complete Example
See REFERENCE.md for a complete
NoteController implementation.
What NOT to Do
- Do NOT validate input in controllers (use validation middleware)
- Do NOT access
directly (usec.req.json()
)c.var.validatedBody - Do NOT catch errors (let global error handler catch them)
- Do NOT return HTTP status codes manually (use domain errors)
- Do NOT put business logic in controllers (that's for services)
- Do NOT use regular methods (use arrow functions for
binding)this
See Also
- Wire controller handlers to routes with middlewarecreate-routes
- Create validation and auth middlewarecreate-middleware
- Test controller handlerstest-controller