A Cursor Agent Skill for Express 5 + TypeScript layered APIs
Routes → Controllers → Services → Prisma — with Zod validation and auth stubs
- Overview
- Architecture
- Three Modes
- Sample Use Cases
- Project Structure
- Layer Responsibilities
- Install
- Usage
- Tech Stack
- Conventions
- What Is Included
- Repository Files
- License
express-layered-api is an Agent Skill that teaches AI coding agents how to scaffold and extend production-style Express backends. Instead of dumping logic into route files, it enforces a strict layered pattern so every endpoint follows the same predictable flow.
Works with Cursor, Claude Code, Codex, and 68+ other agents via the skills.sh ecosystem.
# One command to install globally for Cursor
npx skills add JayShende/express-layered-api -g -a cursor -yEvery HTTP request travels through a fixed pipeline. Each layer has one job and delegates to the next.
flowchart LR
A[Client] --> B[index.ts]
B --> C["/api/v1"]
C --> D[domain.route.ts]
D --> E[isAuthenticated]
E --> F[requireRole]
F --> G[validate]
G --> H[controller]
H --> I[service]
I --> J[(Prisma / PostgreSQL)]
H --> K["response()"]
K --> A
graph TB
subgraph HTTP["HTTP Layer"]
R[Route]
M[Middleware]
end
subgraph App["Application Layer"]
C[Controller]
V[Validator]
end
subgraph Domain["Domain Layer"]
S[Service]
end
subgraph Data["Data Layer"]
P[Prisma Client]
DB[(PostgreSQL)]
end
R --> M
M --> V
V --> C
C --> S
S --> P
P --> DB
When you ask the agent to add a new endpoint, it always works bottom-up — never skipping layers.
flowchart TD
Start([User: Add an API]) --> Parse[Parse domain, method, auth, validation]
Parse --> Exists{Domain exists?}
Exists -->|No| Create[Create 4 domain files + register route]
Exists -->|Yes| Extend[Extend existing files]
Create --> Prisma
Extend --> Prisma
Prisma[1. schema.prisma + migrate] --> Validator[2. validator.ts - Zod schemas]
Validator --> Service[3. service.ts - business logic]
Service --> Controller[4. controller.ts - try/catch + response]
Controller --> Route[5. route.ts - middleware chain]
Route --> Verify[6. Verify checklist]
Verify --> Done([Endpoint live at /api/v1/...])
The skill picks the right workflow based on what you ask.
| Mode | You say | Agent does |
|---|---|---|
| Existing project | "I have an app ready", "adopt this pattern", "create API files in my project" | Scans your app, adds missing infra, then creates APIs in your structure |
| Scaffold | "Set up backend", "scaffold API", "new project" | Creates a full Express 5 + Prisma + TypeScript project from templates |
| Add API | "Add an API that...", "create endpoint for...", "add CRUD for..." | Adds a new endpoint across all layers (validator → service → controller → route) |
Use this when you already have a Node/Express app and want API files created inside it.
@express-layered-api I have an existing Node app in ./backend.
Read the project structure first, match existing conventions.
Then add GET /api/v1/orders with pagination. Admin only.
The agent will:
- Read
index.ts, existing routes, controllers, services - Add missing infrastructure only if absent (
api-error,reponses,validate, auth stubs) - Create or extend the 4 domain files for your API
- Match your conventions (
/v1vs/api/v1, existing auth, etc.)
Produces a minimal, API-only starter:
- Express 5 app with CORS, JSON parsing, trust proxy
- Prisma 7 + PostgreSQL with a sample
SystemPingmodel - Auth middleware stubs (
isAuthenticated,requireRole) ready to wire later - A working test route:
GET /api/v1/test/loggerAPI
The day-to-day mode. Example prompt:
"Add an API to create and list products. Name and price required. Admin only."
The agent produces:
| Layer | File | What gets added |
|---|---|---|
| Database | schema.prisma |
Product model + migration |
| Validation | product.validator.ts |
createProduct, listProducts Zod schemas |
| Business logic | product.service.ts |
createProduct, listProducts |
| HTTP handler | product.controller.ts |
create, list with try/catch |
| Routing | product.route.ts |
POST / and GET / with auth + role + validate |
| Registry | routes/index.ts |
{ path: "/products", route: productRoute } |
Result: POST /api/v1/products and GET /api/v1/products
Scaffolded projects follow this layout:
backend/
├── prisma/
│ └── schema.prisma # Database models
├── prisma.config.ts
├── generated/prisma/ # Prisma client output (gitignored)
├── src/
│ ├── index.ts # App bootstrap
│ ├── routes/
│ │ ├── index.ts # Route registry
│ │ └── {domain}.route.ts
│ ├── controllers/
│ │ └── {domain}.controller.ts
│ ├── services/
│ │ └── {domain}.service.ts
│ ├── validators/
│ │ └── {domain}.validator.ts
│ ├── middlewares/
│ │ ├── auth-middleware.ts # Auth stubs
│ │ └── validate.ts
│ ├── lib/
│ │ └── prisma.ts
│ ├── utils/
│ │ ├── api-error.ts
│ │ └── reponses.ts
│ └── types/express/
│ └── index.d.ts
├── .env.example
├── package.json
└── tsconfig.json
| Layer | Does | Does NOT |
|---|---|---|
| Route | Mount path, chain middleware, call controller | Business logic, Prisma, try/catch |
| Middleware | Auth, validation, request augmentation | Database access |
| Controller | Extract req data, call service, map errors to HTTP, response() |
Prisma, role checks |
| Service | Business rules, Prisma calls, throw ApiError |
Send HTTP responses |
| Validator | Zod schemas { body?, query?, params? } |
Runtime logic |
router.post(
"/",
isAuthenticated, // omit if public
requireRole("ADMIN"), // omit if any authenticated user
validate(domainValidator.createDomain), // always validate input
domainController.create,
);| Source | Shape |
|---|---|
| Controllers (success/error) | { message, data } |
| Validation middleware | { success: false, message: "Validation Error", errors: string[] } |
| Auth middleware stubs | { error: "Unauthorized" } or { error: "Forbidden: ..." } |
Available in every project:
npx skills add JayShende/express-layered-api -g -a cursor -yCommitted with your team repo:
npx skills add JayShende/express-layered-api -a cursor -ynpx skills update express-layered-api -g -yReplace -a cursor with your agent: claude-code, codex, opencode, etc.
npx skills add JayShende/express-layered-api -g -a claude-code -a cursor -y/express-layered-api
Or attach with @express-layered-api in chat.
Existing app — add an API:
@express-layered-api I have an existing Node app in ./backend.
Read the project first, match conventions.
Add full CRUD for products. Name and price required. Admin only.
Scaffold a new backend:
@express-layered-api scaffold a new backend in ./backend
Add a single endpoint:
@express-layered-api add GET /api/v1/users/:id with authentication
Extend an existing domain:
@express-layered-api add PATCH /api/v1/products/:id/archive. Admin only.
Read existing product files and extend them.
Full copy-paste examples for every scenario are in examples.md.
| Scenario | Example prompt |
|---|---|
| New project | @express-layered-api scaffold a new backend in ./backend |
| Existing app setup | @express-layered-api My app in ./api has no controllers folder yet. Add infra + health endpoint. |
| Full CRUD | @express-layered-api add full CRUD for categories. Admin write, user read. |
| Public endpoint | @express-layered-api add GET /api/v1/health. Public. |
| Action endpoint | @express-layered-api add POST /api/v1/orders/:id/cancel. Admin only. |
| Match existing codebase | @express-layered-api @Backend add GET /api/v1/sessions. Match vip.route.ts style. |
Prompt formula:
@express-layered-api [app path] [action] [method/path] [fields] [auth/roles] [match existing style]
| Layer | Choice |
|---|---|
| Runtime | Node.js, pnpm |
| Framework | Express 5 |
| Language | TypeScript — ESM, module: NodeNext, .js extensions on imports |
| ORM | Prisma 7 + PostgreSQL (@prisma/adapter-pg) |
| Validation | Zod 4 |
| HTTP codes | http-status package |
| Artifact | Pattern | Example |
|---|---|---|
| Route file | {domain}.route.ts |
product.route.ts |
| Controller | {domain}.controller.ts |
product.controller.ts |
| Service | {domain}.service.ts |
product.service.ts |
| Validator | {domain}.validator.ts |
product.validator.ts |
| Handler fn | camelCase verb | create, list, getById |
| URL segments | kebab-case plural | /api/v1/deleted-items |
| Exports | export default { fn1, fn2 } |
Controllers, services, validators |
| HTTP | Path | Handler | Validator | Service |
|---|---|---|---|---|
| POST | / |
create |
create{Domain} |
create{Domain} |
| GET | / |
list |
list{Domain}s |
list{Domain}s |
| GET | /:id |
getById |
get{Domain}ById |
get{Domain}ById |
| PATCH | /:id |
update |
update{Domain} |
update{Domain} |
| DELETE | /:id |
remove |
remove{Domain} |
remove{Domain} |
Static paths before dynamic params:
GET /suggest <-- first
GET /:id <-- after static routes
| Included | Not included (unless you ask) |
|---|---|
| Layered route/controller/service pattern | Better Auth / JWT implementation |
| Zod request validation | Geolocation / geo gate middleware |
| Prisma 7 + PostgreSQL setup | Cron jobs, email, S3, Excel export |
| Auth middleware stubs | Global error handler middleware |
| Per-controller try/catch error handling | constants/ folder |
| Sample test route + Prisma model |
This skill repo contains only the agent instructions — not a runnable backend.
express-layered-api/
├── SKILL.md # Main agent workflow and conventions
├── reference.md # Copy-paste templates for every scaffold file
├── examples.md # Sample prompts for all three modes
├── README.md # This file
└── LICENSE # MIT
- SKILL.md — Full workflow: existing-project, scaffold, and add-API modes
- reference.md — Complete file templates (
package.json,schema.prisma, controllers, services, validators, middleware, etc.) - examples.md — Copy-paste prompt examples for new projects, existing apps, CRUD, auth patterns, and more
MIT — see LICENSE.