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.md
source 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:

  1. Schema created with request/response types (
    src/schemas/{entity-name}.schema.ts
    )
  2. 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 context
  • c.var.validatedQuery
    - Validated query parameters
  • c.var.validatedBody
    - Validated request body
  • c.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 (use
    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
    this
    binding)

See Also

  • create-routes
    - Wire controller handlers to routes with middleware
  • create-middleware
    - Create validation and auth middleware
  • test-controller
    - Test controller handlers