From software-development
REST API design conventions and best practices for TypeScript/Node.js. Loaded by backend-software-developer-agent when creating endpoints, designing request/response schemas, or structuring API routes.
npx claudepluginhub bartekck/bartek-marketplace --plugin software-developmentThis skill uses the workspace's default tool permissions.
- Use **plural nouns**: `/users`, `/orders`, `/order-items`
Provides REST API design patterns for resource naming, versioning, error responses (RFC 7807), and structure. Use when designing endpoints, pagination, or consistent APIs with TypeScript examples.
Guides REST API design patterns for production-grade endpoints including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting. Use when designing new APIs or reviewing contracts.
Guides RESTful API design and implementation: resource naming, HTTP methods, URL patterns, error responses, versioning, and core principles.
Share bugs, ideas, or general feedback.
/users, /orders, /order-items/users/:userId/orders/payment-methods| Method | Purpose | Success | Body |
|---|---|---|---|
| GET | Read resource(s) | 200 | Resource / array |
| POST | Create resource | 201 | Created resource + Location header |
| PUT | Full replace | 200 | Updated resource |
| PATCH | Partial update | 200 | Updated resource |
| DELETE | Remove | 204 | No body |
| Code | Meaning |
|---|---|
| 400 | Validation / bad input |
| 401 | Not authenticated |
| 403 | Authenticated but not authorized |
| 404 | Resource not found |
| 409 | Conflict (duplicate, stale update) |
| 422 | Semantically invalid (parseable but wrong) |
| 500 | Unexpected server error |
// Shared types
interface ApiError {
status: number;
code: string; // machine-readable: "VALIDATION_ERROR"
message: string; // human-readable
details?: Record<string, string[]>; // field-level errors
}
interface PaginatedResponse<T> {
data: T[];
meta: {
total: number;
page: number;
pageSize: number;
totalPages: number;
};
}
// Resource DTOs
interface CreateUserDto {
email: string;
name: string;
role: "admin" | "member";
}
interface UserResponse {
id: string;
email: string;
name: string;
role: "admin" | "member";
createdAt: string; // ISO 8601
}
Always return consistent error shape:
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": {
"email": ["Must be a valid email address"],
"name": ["Required"]
}
}
Use query parameters: GET /users?page=2&pageSize=20
function paginate<T>(items: T[], total: number, page: number, pageSize: number): PaginatedResponse<T> {
return {
data: items,
meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) },
};
}
For large datasets, prefer cursor-based pagination:
GET /events?cursor=abc123&limit=50
{ data: [...], meta: { nextCursor: "def456", hasMore: true } }
Prefer URL prefix for simplicity: /api/v1/users. Alternatives (Accept header) add complexity with little benefit for most projects.
import { Request, Response, NextFunction } from "express";
interface TypedRequest<B = unknown, Q = unknown> extends Request {
body: B;
query: Q;
}
const createUser = async (
req: TypedRequest<CreateUserDto>,
res: Response<UserResponse | ApiError>,
next: NextFunction
) => {
try {
const user = await userService.create(req.body);
res.status(201).json(user);
} catch (err) {
next(err);
}
};
router.post("/users", validateBody(createUserSchema), createUser);
import { FastifyRequest, FastifyReply } from "fastify";
app.post<{ Body: CreateUserDto; Reply: UserResponse }>(
"/users",
{ schema: { body: createUserSchema, response: { 201: userResponseSchema } } },
async (request, reply) => {
const user = await userService.create(request.body);
reply.status(201).send(user);
}
);
Content-Type: application/json consistently429 with Retry-After header