| name | API Route Structure |
| description | ENFORCE consistent, predictable REST API design. URLs identify resources (nouns). HTTP methods define actions (verbs). Responses follow consistent envelope. Prevent RPC-style endpoints, inconsistent response formats, and unbounded data responses. Trigger: "build an API", "create (a|an|a new) endpoint", "set up (the) backend routes".
|
| category | backend |
| version | 3.0.0 |
| last_updated | 2026-06-28T00:00:00.000Z |
| stacks | ["Express","FastAPI","Next.js 16 (Route Handlers)","Nuxt (Nitro)","Django"] |
| related_skills | ["backend-validation-layers","production-api-error-handling","database-schema-design"] |
API Route Structure
IDENTIFY: When to Activate
Activate when:
- Defining how frontend communicates with backend
- Exposing a public or internal API
- Adding new endpoints to an existing API
- User says "build an API" or "create a new endpoint"
URL DESIGN RULES
Rule 1: Nouns: Never Verbs
✅ GET /api/users , list users
✅ POST /api/users , create user
❌ POST /api/getUsers , verb in URL
❌ GET /api/createUser , verb in URL
Rule 2: Plural Nouns for Collections
✅ /api/users
✅ /api/orders
❌ /api/user
❌ /api/order
Rule 3: Path Parameters for Specific Resources
✅ GET /api/users/abc123
✅ PATCH /api/users/abc123
✅ DELETE /api/users/abc123
Rule 4: Max 1 Level of Nesting
✅ GET /api/users/abc123/orders
❌ GET /api/users/abc123/orders/xyz789/items/def456
For deeper nesting, query the sub-resource directly: GET /api/items/def456
Rule 5: Query Params for Filtering: Sorting, Pagination
GET /api/users?status=active&sort=created_at&page=2&limit=20
HTTP Method Semantics
| Method | Action | Example | Idempotent? | Request Body? |
|---|
GET | List / Read | GET /api/users | Yes | No |
POST | Create | POST /api/users | No | Yes |
PATCH | Partial update | PATCH /api/users/:id | No | Yes |
PUT | Full replace | PUT /api/users/:id | Yes | Yes |
DELETE | Remove | DELETE /api/users/:id | Yes | No |
Response Envelope: ALWAYS Consistent
Success (Collection)
{
"data": [{ "id": "1", "name": "Alice" }],
"meta": { "total": 42, "page": 1, "limit": 20 }
}
Success (Single Resource)
{
"data": { "id": "1", "name": "Alice" }
}
Error: RFC 9457 Problem Details Format
{
"error": { "message": "User not found", "status": 404 }
}
Status Code Selection
| Code | Use For | Never Use For |
|---|
200 OK | Successful GET, PATCH, PUT | Errors (don't return 200 with error body) |
201 Created | Successful POST | Anything else |
204 No Content | Successful DELETE | Responses with a body |
400 Bad Request | Validation failure, malformed input | Server errors (use 500) |
401 Unauthorized | Missing/invalid credentials | Permission errors (use 403) |
403 Forbidden | Valid credentials, insufficient permissions | Missing credentials (use 401) |
404 Not Found | Resource doesn't exist | Validation errors (use 400) |
409 Conflict | Duplicate resource, state conflict | Validation errors (use 400) |
422 Unprocessable | Semantic validation failure | Syntax parsing errors (use 400) |
429 Too Many Requests | Rate limit exceeded | Auth errors |
500 Internal Server Error | Unexpected server failure | Expected errors (use 4xx) |
Pagination: ALWAYS Required
Every collection endpoint MUST support pagination.
Cursor-Based (PREFERRED: stable under high insert volume)
GET /api/users?cursor=abc123&limit=20
Response: { "data": [...], "meta": { "next_cursor": "def456", "has_more": true } }
Offset-Based (acceptable for small: stable datasets)
GET /api/users?page=1&limit=20
Response: { "data": [...], "meta": { "total": 100, "page": 1, "limit": 20 } }
RULES:
- Default limit: 20
- Max limit: 100
- ALWAYS return total count (offset) or has_more flag (cursor)
CRUD Implementation: ALWAYS Implement ALL Operations
For every resource, implement ALL six operations. Never stop at GET and POST:
router.get('/', listUsers);
router.post('/', createUser);
router.get('/:id', getUser);
router.put('/:id', replaceUser);
router.patch('/:id', updateUser);
router.delete('/:id', deleteUser);
Stack-Specific Patterns
Next.js 16 Route Handlers (App Router)
File-based routing:
app/api/users/route.ts → GET /api/users, POST /api/users
app/api/users/[id]/route.ts → GET /api/users/:id, PATCH, DELETE
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '20');
const users = await db.user.findMany({ skip: (page - 1) * limit, take: limit });
const total = await db.user.count();
return NextResponse.json({ data: users, meta: { total, page, limit } });
}
export async function POST(request: NextRequest) {
const body = await request.json();
const validated = .(body);
user = db..({ : validated });
.({ : user }, { : });
}
Express
import { Router } from 'express';
const router = Router();
router.get('/', listUsers);
router.post('/', createUser);
router.get('/:id', getUser);
router.put('/:id', replaceUser);
router.patch('/:id', updateUser);
router.delete('/:id', deleteUser);
FastAPI
from fastapi import APIRouter, Query
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/")
async def list_users(page: int = Query(1, ge=1), limit: int = Query(20, ge=1, le=100)):
pass
@router.post("/", status_code=201)
async def create_user(body: UserCreate):
pass
@router.get("/{user_id}")
async def get_user(user_id: str):
pass
VALIDATE: Quality Gates
ANTI-PATTERNS: ALWAYS Avoid
| Anti-Pattern | Why Wrong | Fix |
|---|
POST /api/getUsers | RPC-style. Breaks HTTP semantics. Confuses caching. | GET /api/users |
200 OK with error body | Misleading. Client checks status, not body. | Return correct 4xx status |
/api/users/:uid/posts/:pid/comments/:cid | Unreadable, fragile nesting | GET /api/comments/:cid |
| 10,000 rows, no pagination | Times out, crashes clients, exhausts memory | Always paginate. Default 20, max 100. |
| Only GET + POST implemented | Missing PUT/PATCH/DELETE | Implement ALL six CRUD operations |
OUTPUT: What This Skill Produces
{
"resources": [{ "name": "string", "endpoints": ["GET /", "POST /", "GET /:id", "PUT /:id", "PATCH /:id", "DELETE /:id"] }],
"pagination": "cursor | offset",
"envelope": { "success": "{ data, meta }", "error": "{ error: { message, status } }" },
"framework": "express | nextjs | fastapi | django"
}