| name | backend-rest-api |
| description | Build secure RESTful APIs with authentication, validation, and PostgreSQL integration. Use when the user needs to: (1) design CRUD endpoints with consistent response envelopes, (2) implement JWT authentication with protected routes, (3) validate and sanitize request input with Zod, (4) design PostgreSQL schemas with soft delete (deleted_at), (5) add cursor or offset-based pagination with filtering and sorting, or (6) apply rate limiting (100 req/min/IP). Triggers on keywords like: REST API, CRUD, JWT, auth, pagination, rate limit, soft delete, Express router, validation, filtering, sorting. |
Backend REST API
Build secure, consistent, and observable REST APIs.
Build Workflow
1. Define the resource schema (PostgreSQL) with soft-delete columns
2. Write the Zod validation schemas (body, query, params)
3. Implement auth middleware (JWT verify + attach user)
4. Build CRUD route handlers — use soft delete on DELETE
5. Add pagination/filtering/sorting to list endpoints
6. Apply rate limiter middleware globally
7. Wire error handler last (4-param middleware)
Constraints — enforce always:
- DELETE routes set
deleted_at = NOW(), never DELETE FROM
- All queries filter
WHERE deleted_at IS NULL by default
- Rate limit: 100 requests/minute/IP (global); stricter on auth routes (10/min)
Boilerplate Template
A complete starter scaffold is in assets/api-template/. Files included:
api-template/
├── src/
│ ├── index.ts App wiring + server
│ ├── db.ts pg Pool singleton
│ ├── config/env.ts Env validation (Zod)
│ ├── middleware/
│ │ ├── auth.ts JWT verify middleware
│ │ ├── rateLimiter.ts express-rate-limit (100/min global, 10/min auth)
│ │ ├── validate.ts Zod request validator factory
│ │ └── errorHandler.ts Centralized error handler
│ └── routes/
│ └── resource.ts CRUD template with soft delete + pagination
└── migrations/
└── 001_base_schema.sql Base table with audit + soft-delete columns
1. Soft Delete — Non-Negotiable Pattern
deleted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
router.delete('/:id', authenticate, async (req, res, next) => {
await db.query(
'UPDATE resources SET deleted_at = NOW(), updated_at = NOW() WHERE id = $1 AND deleted_at IS NULL',
[req.params.id]
);
res.status(204).send();
});
const BASE_FILTER = 'WHERE r.deleted_at IS NULL';
2. Response Envelope
Use consistent structure across all endpoints:
res.json({ data: resource, meta: { requestId: req.id } });
res.json({ data: items, meta: { total, page, limit, requestId: req.id } });
res.status(status).json({ error: CODE, message: '...', requestId: req.id });
3. Rate Limiting Setup
import rateLimit from 'express-rate-limit';
export const globalLimiter = rateLimit({
windowMs: 60_000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'RATE_LIMIT_EXCEEDED', message: 'Too many requests' },
});
export const authLimiter = rateLimit({ windowMs: 60_000, max: 10, ...});
app.use(globalLimiter);
app.use('/auth', authLimiter);
Resources
- references/auth.md — JWT signing/verification, protected route middleware, refresh tokens, password hashing
- references/crud-patterns.md — Full CRUD templates, pagination (offset + cursor), filtering, sorting, error codes
assets/api-template/ — Complete boilerplate to copy and adapt
When NOT to Use This Skill
- GraphQL APIs — this skill generates REST endpoints; use a dedicated GraphQL scaffolding skill for graph-based APIs
- Event-driven microservices — for services that communicate via messages rather than HTTP, use
event-streaming or fastapi-dapr-agent
- Serverless functions — if the API will be deployed as AWS Lambda or Vercel functions, the project structure this skill generates needs significant modification
Common Mistakes
- Not versioning the API from the start (e.g.,
/api/v1/) — adding versioning later requires breaking changes for all existing clients
- Returning raw database error messages to clients — exposes schema details and internal structure; always map errors to safe, generic responses
- Not implementing pagination on list endpoints — returning unbounded lists causes timeouts and OOM errors at scale
Related Skills