Skip to content
Back to skills

Ts Ddd Controller

ASecurity

Create, review, or guide HTTP controllers in a TypeScript + DDD api. Use when the request involves `*.controller.ts` under `apps/api/src/<bc>/presentation/controllers/`, route definition, `ApiKeyGuard` on mutations, `ZodValidationPipe` against `@acme/<bc>-contracts` schemas, use case orchestration, mapping `Result` to HTTP via `mapResultToHttp`, signed-URL enrichment through a response mapper, HTTP status conventions (200/201/204), or controller tests using `@nestjs/testing` + `supertest` wit...

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 4, 2026
ai-agentstypescriptgoexpresstestingapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 10 files and shows the line behind each finding

Scanned September 4, 2026

npx -y skills add llodev/skills --skill ts-ddd-controller --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ts Ddd Controller?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ts Ddd Controller
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/llodev-ts-ddd-controller/badge)](https://www.skillsdirectory.com/skills/llodev-ts-ddd-controller)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ts-ddd-controller
description: >-
  Create, review, or guide HTTP controllers in a TypeScript + DDD api. Use when
  the request involves `*.controller.ts` under
  `apps/api/src/<bc>/presentation/controllers/`, route definition, `ApiKeyGuard`
  on mutations, `ZodValidationPipe` against `@acme/<bc>-contracts` schemas, use
  case orchestration, mapping `Result` to HTTP via `mapResultToHttp`, signed-URL
  enrichment through a response mapper, HTTP status conventions (200/201/204),
  or controller tests using `@nestjs/testing` + `supertest` with an in-memory
  repository.
metadata:
  version: 0.1.0
---

# TypeScript DDD Controller

**MANDATORY — READ ENTIRE FILE**: before writing or reviewing any controller, read
[`references/controller-pattern.md`](references/controller-pattern.md) completely.

Then load the framework reference:

- **NestJS 11** (this repo) → also read [`references/nestjs.md`](references/nestjs.md) completely.
- Other frameworks (Express, Fastify, Hono…) are not yet documented; apply the principles from `controller-pattern.md`.

**Do NOT load** sibling DDD skills (`ts-ddd-entity`, `ts-ddd-use-case`, `ts-ddd-repository`, `ts-ddd-dto`, `ts-ddd-value-object`) unless the request explicitly asks for them.

---

## When to trigger this skill

- File path matches `apps/api/src/<bc>/presentation/controllers/*.controller.ts`.
- Request mentions `@Controller`, `@Get/@Post/@Put/@Delete`, `@UseGuards(ApiKeyGuard)`, `ZodValidationPipe`, `mapResultToHttp`, or a response mapper.
- Adding/changing routes for a bounded context (e.g. `celebrations`).
- Wiring controllers into a `<bc>.module.ts`.
- Writing a controller test under `apps/api/test/<bc>/presentation/controllers/*.controller.test.ts`.

## Where controllers live

```
apps/api/src/<bc>/
  presentation/
    controllers/<name>.controller.ts
    guards/api-key.guard.ts
    mappers/<name>-response.mapper.ts
    index.ts                  ← barrel per leaf folder
  application/
    usecases/                 ← injected here
  <bc>.module.ts              ← registers controllers, mappers, guard, use cases
```

Every leaf folder exports through an `index.ts` barrel. Imports across layers use the path aliases `@<bc>/*` (e.g. `@celebrations/application/usecases`) and `@shared/*` (`apps/api/src/shared/*`); inside the same folder use relative paths. Cross-package types come from `@acme/<bc>-contracts` and `@acme/shared` — never `@ddd/shared` or any older name.

## Before You Start

Answer these before writing a handler:

- **Contract**: status + payload? (`200` payload, `201 CREATED`, `204 NO_CONTENT`, `200` for explicit OK on actions like `/publish`).
- **Mutation?**: If yes, `@UseGuards(ApiKeyGuard)` is required (writes are 401 without `x-api-key`, 503 `MUTATIONS_DISABLED` when `API_WRITE_KEY` is unset — see `references/controller-pattern.md`).
- **Wire shape**: Which Zod schema in the contracts package validates `@Body` / `@Param`? Don't redefine wire types locally.
- **Failure mapping**: Is the error code already in `apps/api/src/shared/http/error-codes.ts`? If new, add it there with the right HTTP status before throwing it from a use case.
- **Response shape**: Does the entity need enrichment (signed Storage URLs, ISO dates)? Then go through the response mapper, not raw entity props.

---

## Core Rules

- Controllers are **thin translators** between HTTP and the application layer. No business logic, no domain conditionals, no repository calls.
- **Inject** use cases and mappers via the constructor — never `new UseCase(...)`.
- **Validate at the boundary** with `ZodValidationPipe(schema)` over schemas exported by the BC's contracts package.
- **Map `Result` → HTTP** with `mapResultToHttp(result, ok => …)` from `@shared/http`. Don't throw NestJS exceptions directly from controller handlers.
- **Mutations** carry `@UseGuards(ApiKeyGuard)`. Reads stay public.
- **Enrich** entity → response DTO inside a dedicated mapper (e.g. `CelebrationResponseMapper`) — that's where signed URLs, date serialization, and tagged-union variant handling live.

## NEVER

- **NEVER** apply `@UseGuards(ApiKeyGuard)` at the class level. **Why**: it 401s every read. The canonical `CelebrationsController` mixes public reads (`GET /celebrations`, `GET /celebrations/:slug`) with per-method-guarded writes; a class-level guard breaks that mix and silently breaks the public web client. Always attach `@UseGuards(ApiKeyGuard)` to each mutating handler individually.
- **NEVER** `throw` a NestJS `HttpException` (or its subclasses like `BadRequestException` / `NotFoundException`) directly from a controller. **Why**: the error-code catalog in `apps/api/src/shared/http/error-codes.ts` is the single source of truth for `code → HTTP status` mapping. Throwing inline forks that mapping, defeats the `Result` pattern in use cases, and produces envelopes that don't match `HttpExceptionFilter`. Always pipe `Result` failures through `mapResultToHttp`.
- **NEVER** put domain logic, branching, or repository access inside a controller method.
- **NEVER** instantiate use cases or mappers with `new` — breaks DI and tests.
- **NEVER** remove `ApiKeyGuard` on a write to "fix" a 503; the 503 is the intentional `MUTATIONS_DISABLED` contract when `API_WRITE_KEY` is unset.
- **NEVER** redefine wire-shape interfaces in the controller; import the DTO type + Zod schema from `@acme/<bc>-contracts`.
- **NEVER** leak entity internals (e.g. `Date` objects, raw `storagePath` without signed URL) into the response — funnel through the mapper.

---

## References

- [`references/controller-pattern.md`](references/controller-pattern.md) — repo-grounded: folder layout, `ZodValidationPipe`, `ApiKeyGuard` semantics, `mapResultToHttp`, response mapper, test strategy (enum members in fixtures).
- [`references/nestjs.md`](references/nestjs.md) — NestJS 11 patterns actually used here: DI symbol tokens (`FIREBASE_APP`), `@Inject(SYMBOL)`, `@Global()` modules (`FirebaseModule`, `AppConfigModule`), Zod-validated `ConfigModule.forRoot`, `@nestjs/testing` + `supertest` test bed.
- [`examples/product.controller.nestjs.ts`](examples/product.controller.nestjs.ts) — runnable-style mirror of `CelebrationsController`.

Files in this skill

  • CHANGELOG.md1012 B
  • LICENSE1.1 KB
  • README.md4.7 KB
  • SKILL.md6.1 KB
  • docs/i18n/README.es-ES.md5.1 KB
  • docs/i18n/README.pt-BR.md5 KB
  • examples/product.controller.nestjs.ts4.1 KB
  • package.json818 B
  • references/controller-pattern.md19.1 KB
  • references/nestjs.md11.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.