Add a new API endpoint for $ARGUMENTS (e.g. POST /orders, GET /users/:id) by mirroring an existing endpoint's structure, not by inventing a new pattern.
If $ARGUMENTS is empty, ask for the HTTP method, path, and one sentence on what the endpoint does before writing any code.
Step 1 — Learn the pattern from a sibling
- Find the endpoint most similar to the requested one (same resource, or same shape: read vs. write, list vs. single). Read it end to end.
- Trace its full path: route/controller registration -> input validation/DTO -> service or use-case -> repository/data access -> response mapper -> error handling. Note the exact file locations, naming, and layering so the new code lands in the same places.
- Identify the concrete tools in use: router (Express/Fastify/Nest/FastAPI/etc.), validator (zod/class-validator/pydantic/joi), ORM/query layer, error type, and the test framework + how existing endpoint tests are written. Do not assume — read to confirm.
Step 2 — Implement, layer by layer
- Route: register the method/path exactly like siblings (same middleware, auth guard, versioning prefix). Reuse existing auth/authorization — do not hand-roll it.
- Input validation: define/extend a schema or DTO covering every field, with types, required/optional, and bounds. Reject unknown fields. Parse and validate before any business logic runs; pass only the typed, validated object downward.
- Service/repository: put business logic in the service layer and data access in the repository, matching the sibling's separation. Reuse existing repository methods where they exist instead of writing raw queries in the route.
- Atomicity: if the write touches more than one row/table (e.g.
POST /ordersinserting order + line items + stock decrement), wrap it in a single transaction using the project's existing transaction/unit-of-work mechanism, matching how a sibling write endpoint does it, so a mid-operation failure rolls back cleanly. Read-only endpoints need no transaction. - Response mapping: map domain/entity objects to a response DTO or serializer. Return only the fields the sibling endpoints expose.
- Errors: use the project's existing error types and central handler. Map not-found, validation, and conflict/duplicate cases to the same status codes siblings use (e.g. 404, 422, 409). Never let raw exceptions or stack traces reach the client.
- Registration/docs: register the route in any central route index/registry the project uses, and update the API contract siblings expose — OpenAPI/Swagger spec,
.http/collection files, or generated-client sources — so the new endpoint is documented exactly like its neighbors. Skip only after confirming the project has no such file.
Step 3 — Test
- Add a test alongside existing endpoint tests, copying their setup (fixtures, auth, DB/mocking strategy). Cover: the success case (correct status + body shape), one validation-failure case (bad/missing input -> correct 4xx), the conflict/duplicate case for write endpoints (e.g. re-creating a unique resource -> 409/422), and the not-found/unauthorized case if applicable.
- Run the new test file in isolation first and confirm it passes; then run the full suite plus the linter/type-checker, and fix all failures before reporting done.
Hard rules
- NEVER return ORM entities, models, or DB rows directly — always go through a response mapper/DTO.
- NEVER trust client input: validate and coerce every field at the boundary; no unvalidated value reaches the service.
- NEVER put SQL or ORM queries in the route/controller; keep data access in the repository layer.
- ALWAYS match the sibling endpoint's file layout, naming, status codes, and error handling — consistency over personal preference.
- Do not add new dependencies, config, or migrations unless the endpoint genuinely requires them; if a migration is needed, state it explicitly and follow the project's migration workflow.
Report
List the files created/changed, the validation schema and response DTO used, the sibling endpoint you mirrored, and the test command you ran with its result.