create-controller
About
This skill generates HTTP controller files that handle request/response logic and connect routes to services. It creates TypeScript controllers with proper imports, method stubs, and validation based on existing schemas and services. Use it when you need to implement API endpoints after setting up your schemas and service layer.
Quick Install
Claude Code
Recommended/plugin add https://github.com/majiayu000/claude-skill-registrygit clone https://github.com/majiayu000/claude-skill-registry.git ~/.claude/skills/create-controllerCopy and paste this command in Claude Code to install this skill
Documentation
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:
c.var.user- Authenticated user contextc.var.validatedQuery- Validated query parametersc.var.validatedBody- Validated request bodyc.var.validatedParams- Validated URL parameters
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
c.req.json()directly (usec.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
thisbinding)
See Also
create-routes- Wire controller handlers to routes with middlewarecreate-middleware- Create validation and auth middlewaretest-controller- Test controller handlers
GitHub Repository
Related Skills
algorithmic-art
MetaThis Claude Skill creates original algorithmic art using p5.js with seeded randomness and interactive parameters. It generates .md files for algorithmic philosophies, plus .html and .js files for interactive generative art implementations. Use it when developers need to create flow fields, particle systems, or other computational art while avoiding copyright issues.
subagent-driven-development
DevelopmentThis skill executes implementation plans by dispatching a fresh subagent for each independent task, with code review between tasks. It enables fast iteration while maintaining quality gates through this review process. Use it when working on mostly independent tasks within the same session to ensure continuous progress with built-in quality checks.
executing-plans
DesignUse the executing-plans skill when you have a complete implementation plan to execute in controlled batches with review checkpoints. It loads and critically reviews the plan, then executes tasks in small batches (default 3 tasks) while reporting progress between each batch for architect review. This ensures systematic implementation with built-in quality control checkpoints.
cost-optimization
OtherThis Claude Skill helps developers optimize cloud costs through resource rightsizing, tagging strategies, and spending analysis. It provides a framework for reducing cloud expenses and implementing cost governance across AWS, Azure, and GCP. Use it when you need to analyze infrastructure costs, right-size resources, or meet budget constraints.
