← Back to list

api-design
by MLGBJDLW
Terminal-first AI coding assistant
⭐ 0🍴 0📅 Jan 25, 2026
SKILL.md
name: api-design description: REST and HTTP API design best practices including endpoint naming, request/response formats, error handling, versioning, and documentation standards version: 1.0.0 priority: 25 tags:
- api
- rest
- http
- design
- builtin triggers:
- type: keyword pattern: api
- type: keyword pattern: endpoint
- type: keyword pattern: rest
- type: glob pattern: "/routes/"
- type: glob pattern: "/api/" globs:
- "/routes//*.ts"
- "/api//*.ts"
- "/controllers//*.ts"
API Design
Guidelines for designing consistent, intuitive, and maintainable HTTP APIs.
Rules
- Resource-Oriented URLs: Use nouns (not verbs) for resource names
- Consistent Naming: Use kebab-case for URLs, camelCase for JSON properties
- Proper HTTP Methods: GET (read), POST (create), PUT/PATCH (update), DELETE (remove)
- Status Codes: Use appropriate HTTP status codes for all responses
- Pagination: Always paginate list endpoints; use cursor-based for large datasets
- Versioning: Include API version in URL path (
/v1/) or header - Error Format: Use consistent error response schema across all endpoints
- Idempotency: POST/PATCH/PUT should be idempotent where possible
- Rate Limiting: Include rate limit headers in responses
Patterns
URL Structure
# Collection operations
GET /v1/users # List users (paginated)
POST /v1/users # Create user
# Resource operations
GET /v1/users/{id} # Get user by ID
PUT /v1/users/{id} # Replace user
PATCH /v1/users/{id} # Partial update
DELETE /v1/users/{id} # Delete user
# Nested resources
GET /v1/users/{id}/posts # User's posts
POST /v1/users/{id}/posts # Create post for user
# Actions (when needed)
POST /v1/users/{id}/verify # Trigger verification
POST /v1/orders/{id}/cancel # Cancel order
# Filtering, sorting, searching
GET /v1/users?status=active&sort=-createdAt&q=john
Response Format
// Success response (single resource)
interface SuccessResponse<T> {
data: T;
meta?: {
requestId: string;
timestamp: string;
};
}
// Success response (collection)
interface CollectionResponse<T> {
data: T[];
pagination: {
total: number;
page: number;
pageSize: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
meta?: {
requestId: string;
timestamp: string;
};
}
// Example: GET /v1/users/123
{
"data": {
"id": "123",
"email": "user@example.com",
"name": "John Doe",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2024-01-15T12:00:00Z"
}
}
```markdown
### Error Response Format
```typescript
interface ErrorResponse {
error: {
code: string; // Machine-readable error code
message: string; // Human-readable message
details?: ErrorDetail[]; // Field-level errors
requestId: string; // For support/debugging
};
}
interface ErrorDetail {
field: string;
code: string;
message: string;
}
// Example: 400 Bad Request
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Email must be a valid email address"
},
{
"field": "password",
"code": "TOO_SHORT",
"message": "Password must be at least 8 characters"
}
],
"requestId": "req_xyz789"
}
}
// Example: 404 Not Found
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "User not found",
"requestId": "req_xyz789"
}
}
```markdown
### HTTP Status Codes
```typescript
// 2xx Success
200 OK // GET success, PUT/PATCH success with body
201 Created // POST success (include Location header)
204 No Content // DELETE success, PUT/PATCH success without body
// 4xx Client Errors
400 Bad Request // Validation error, malformed request
401 Unauthorized // Missing or invalid authentication
403 Forbidden // Authenticated but not authorized
404 Not Found // Resource doesn't exist
409 Conflict // State conflict (e.g., duplicate)
422 Unprocessable // Valid syntax but semantic errors
429 Too Many Reqs // Rate limit exceeded
// 5xx Server Errors
500 Internal Error // Unexpected server error
502 Bad Gateway // Upstream service error
503 Unavailable // Service temporarily down
504 Gateway Timeout // Upstream timeout
```markdown
### Pagination (Cursor-Based)
```typescript
// Request
GET /v1/posts?limit=20&cursor=eyJpZCI6MTIzfQ
// Response
{
"data": [...],
"pagination": {
"limit": 20,
"hasMore": true,
"nextCursor": "eyJpZCI6MTQzfQ",
"prevCursor": "eyJpZCI6MTAzfQ"
}
}
// Cursor implementation
interface PaginationCursor {
id: string;
createdAt?: string; // For stable sorting
}
function encodeCursor(cursor: PaginationCursor): string {
return Buffer.from(JSON.stringify(cursor)).toString("base64url");
}
function decodeCursor(encoded: string): PaginationCursor {
return JSON.parse(Buffer.from(encoded, "base64url").toString());
}
```markdown
### Request Validation with Zod
```typescript
import { z } from "zod";
// Define schemas
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
role: z.enum(["user", "admin"]).default("user"),
});
const updateUserSchema = createUserSchema.partial();
const paginationSchema = z.object({
page: z.coerce.number().int().positive().default(1),
pageSize: z.coerce.number().int().min(1).max(100).default(20),
sort: z.string().optional(),
});
// Type inference
type CreateUserInput = z.infer<typeof createUserSchema>;
// Validation middleware
function validateBody<T extends z.ZodSchema>(schema: T) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: {
code: "VALIDATION_ERROR",
message: "Request validation failed",
details: result.error.errors.map((e) => ({
field: e.path.join("."),
code: e.code.toUpperCase(),
message: e.message,
})),
requestId: req.id,
},
});
}
req.validated = result.data;
next();
};
}
```markdown
## Anti-Patterns
```typescript
// ❌ Verbs in URLs
GET /v1/getUsers
POST /v1/createUser
DELETE /v1/deleteUser/123
// ✅ Resource-based URLs
GET /v1/users
POST /v1/users
DELETE /v1/users/123
// ❌ Inconsistent casing
GET /v1/UserAccounts
POST /v1/user_settings
// ✅ Consistent kebab-case
GET /v1/user-accounts
POST /v1/user-settings
// ❌ Returning 200 for errors
{
"success": false,
"error": "User not found"
}
// ✅ Use proper status codes
// 404 Not Found with error body
// ❌ Exposing internal IDs/structure
{
"userId": 12345,
"mysqlRowId": 67890
}
// ✅ Use UUIDs or public identifiers
{
"id": "usr_abc123def456"
}
// ❌ Returning unbounded lists
GET /v1/users // Returns 100,000 users
// ✅ Always paginate
GET /v1/users?page=1&pageSize=20
// ❌ Breaking changes without versioning
// Changed field name from "userName" to "name"
// ✅ Maintain backward compatibility or version
```markdown
## Examples
### Express Router Implementation
```typescript
import { Router } from "express";
import { z } from "zod";
const router = Router();
// GET /v1/users
router.get("/users", async (req, res, next) => {
try {
const query = paginationSchema.parse(req.query);
const { users, total } = await userService.list(query);
res.json({
data: users.map(toUserDTO),
pagination: {
total,
page: query.page,
pageSize: query.pageSize,
totalPages: Math.ceil(total / query.pageSize),
hasNext: query.page * query.pageSize < total,
hasPrev: query.page > 1,
},
});
} catch (error) {
next(error);
}
});
// POST /v1/users
router.post("/users", validateBody(createUserSchema), async (req, res, next) => {
try {
const user = await userService.create(req.validated);
res.status(201)
.header("Location", `/v1/users/${user.id}`)
.json({ data: toUserDTO(user) });
} catch (error) {
if (error instanceof DuplicateError) {
return res.status(409).json({
error: {
code: "DUPLICATE_RESOURCE",
message: "User with this email already exists",
requestId: req.id,
},
});
}
next(error);
}
});
// DELETE /v1/users/:id
router.delete("/users/:id", async (req, res, next) => {
try {
const deleted = await userService.delete(req.params.id);
if (!deleted) {
return res.status(404).json({
error: {
code: "RESOURCE_NOT_FOUND",
message: "User not found",
requestId: req.id,
},
});
}
res.status(204).send();
} catch (error) {
next(error);
}
});
export default router;
```markdown
### OpenAPI Documentation
```yaml
openapi: 3.0.3
info:
title: User API
version: 1.0.0
paths:
/v1/users:
get:
summary: List users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: pageSize
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
"200":
description: Users list
content:
application/json:
schema:
$ref: "#/components/schemas/UserListResponse"
components:
schemas:
User:
type: object
properties:
id:
type: string
email:
type: string
format: email
name:
type: string
createdAt:
type: string
format: date-time
References
Score
Total Score
65/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon


