Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill rest-api명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
SOC 직업 분류 기준
| name | rest-api |
| description | | Use when this capability is needed. |
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:rest-apifor comprehensive documentation.
# Collections (plural nouns)
GET /users # List users
POST /users # Create user
GET /users/{id} # Get user
PUT /users/{id} # Update user
DELETE /users/{id} # Delete user
# Nested resources
GET /users/{id}/posts # User's posts
POST /users/{id}/posts # Create user's post
# Avoid verbs in URLs
❌ GET /getUsers
❌ POST /createUser
✅ GET /users
✅ POST /users
| Method | Purpose | Idempotent |
|---|---|---|
| GET | Read resource | Yes |
| POST | Create resource | No |
| PUT | Replace resource | Yes |
| PATCH | Partial update | Yes |
| DELETE | Remove resource | Yes |
| Code | Meaning | Use Case |
|---|---|---|
| 200 | OK | Successful GET/PUT/PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Validation error |
| 401 | Unauthorized | Missing/invalid auth |
| 403 | Forbidden | No permission |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate/conflict |
| 422 | Unprocessable | Semantic error |
| 500 | Server Error | Internal error |
// Success
{
"data": { "id": 1, "name": "John" },
"meta": { "timestamp": "2024-01-15T10:00:00Z" }
}
// Collection
{
"data": [...],
"meta": { "total": 100, "page": 1, "limit": 20 }
}
// Error
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Email is required",
"details"
GET /users?status=active&sort=-createdAt&page=1&limit=20
GET /users?fields=id,name,email
GET /users?include=posts,profile
GET /users?filter[age][gte]=18
graphql skill)trpc skill)openapi skill)| Anti-Pattern | Why It's Bad | Solution |
|---|---|---|
Using verbs in URLs (/getUser, /createOrder) | Not RESTful, violates resource naming | Use HTTP methods on nouns (GET /users, POST /orders) |
| Returning 200 for errors | Misleading, breaks HTTP semantics | Use appropriate 4xx/5xx status codes |
| Not versioning API | Breaking changes affect all clients | Use URL or header versioning (/v1/, /v2/) |
| Exposing database IDs directly | Security risk, implementation leak | Use UUIDs or opaque identifiers |
| No pagination on large collections | Performance issues, timeouts | Implement cursor or offset pagination |
| Ignoring HTTP caching headers | Poor performance, unnecessary load | Use ETag, Cache-Control, Last-Modified |
| Using GET for state-changing operations | Security risk, breaks REST principles | Use POST, PUT, PATCH, DELETE |
| Inconsistent response formats | Client confusion, integration issues | Standardize on JSON envelope format |
| Missing rate limiting | API abuse, resource exhaustion | Implement rate limiting with 429 responses |
| Issue | Possible Cause | Solution |
|---|---|---|
| 401 Unauthorized | Missing or invalid auth token | Check Authorization header, verify token |
| 403 Forbidden | Valid auth but insufficient permissions | Check user roles, verify access control |
| 404 Not Found | Resource doesn't exist or wrong path | Verify endpoint URL, check resource ID |
| 409 Conflict | Resource already exists or state conflict | Check uniqueness constraints, handle idempotency |
| 422 Unprocessable Entity | Validation failed | Check request body, validate against schema |
| 429 Too Many Requests | Rate limit exceeded | Implement backoff, check rate limit headers |
| 500 Internal Server Error | Server-side bug or crash | Check server logs, add error handling |
| CORS errors | Missing CORS headers | Configure CORS middleware with allowed origins |
| Slow API responses | No pagination, N+1 queries | Add pagination, optimize database queries |
// Input validation and sanitization
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(1).max(100).trim(),
email: z.string().email().toLowerCase(),
age: z.number().int().min(0).max(150).optional(),
});
// Validate request body
app.post('/users', async (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: { code: 'VALIDATION_ERROR', details: result.error.issues },
});
}
// Use result.data (validated and transformed)
});
// SQL injection prevention - always use parameterized queries
// NEVER: `SELECT * FROM users WHERE id = '${userId}'`
import rateLimit from 'express-rate-limit';
// Different limits for different endpoints
const standardLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
standardHeaders: true,
message: { error: { code: 'RATE_LIMIT_EXCEEDED' } },
});
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // Strict for auth endpoints
skipSuccessfulRequests: true,
});
app.use('/api', standardLimiter);
app.use('/api/auth/login', authLimiter);
// URL versioning (recommended for breaking changes)
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
// Header versioning (for minor versions)
app.use('/api', (req, res, next) => {
const version = req.headers['api-version'] || '1.0';
req.apiVersion = version;
next();
});
// Sunset header for deprecated endpoints
app.get('/api/v1/legacy', (req, res) => {
res.set('Sunset', 'Sat, 01 Jun 2025 00:00:00 GMT');
res.set('Deprecation', 'true');
// ... handle request
});
// Cursor-based pagination (recommended for large datasets)
interface PaginatedResponse<T> {
data: T[];
meta: {
nextCursor: string | null;
hasMore: boolean;
};
}
app.get('/users', async (req, res) => {
const cursor = req.query.cursor as string | undefined;
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const users = await db.users.findMany({
take: limit + 1, // Fetch one extra to check if there's more
cursor: cursor ? { id: cursor } : undefined,
orderBy: { createdAt: 'desc' },
});
const hasMore = users.length > limit;
const data = hasMore ? users.slice(0, -1) : users;
res.json({
data,
meta: {
nextCursor: hasMore ? data[data. - ]. : ,
hasMore,
},
});
});
// Consistent error response format
interface APIError {
error: {
code: string;
message: string;
details?: unknown;
requestId?: string;
};
}
// Global error handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
const requestId = req.headers['x-request-id'] as string;
// Log error with context
logger.error({
err,
requestId,
path: req.path,
method: req.method,
});
// Don't leak internal errors
const statusCode = err instanceof HTTPError ? err.statusCode : 500;
const message = statusCode === 500 ? 'Internal Server Error' : err.message;
res.status(statusCode).json({
error: {
code: err.code || 'INTERNAL_ERROR',
message,
requestId,
},
});
});
| Metric | Alert Threshold |
|---|---|
| Request latency p99 | > 500ms |
| Error rate (4xx) | > 5% |
| Error rate (5xx) | > 1% |
| Rate limit hits | > 100/min |
| Request size | > 10MB |
import cors from 'cors';
const corsOptions = {
origin: (origin, callback) => {
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
credentials: true,
maxAge: 86400, // 24 hours
};
app.use(cors(corsOptions));
import pino from 'pino-http';
app.use(pino({
redact: ['req.headers.authorization', 'req.body.password'],
serializers: {
req: (req) => ({
method: req.method,
url: req.url,
query: req.query,
}),
res: (res) => ({
statusCode: res.statusCode,
}),
},
}));
Source: claude-dev-suite/claude-dev-suite — distributed by TomeVault.