Awesome-omni-skill api-design-patterns
Provides REST and GraphQL API design patterns for Node.js, Flask, and FastAPI. Use when designing endpoints, request/response structures, API architecture, pagination, authentication, rate limiting, or when working in /api/ or /routes/ directories.
install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/api-design-patterns" ~/.claude/skills/diegosouzapw-awesome-omni-skill-api-design-patterns-ef68b2 && rm -rf "$T"
manifest:
skills/development/api-design-patterns/SKILL.mdsource content
API Design Patterns Skill
Best practices for designing RESTful and GraphQL APIs.
Resources
For detailed code examples, see:
- Node.js + Express patternsreferences/express-examples.md
- FastAPI (Python) patternsreferences/fastapi-examples.md
- Flask (Python) patternsreferences/flask-examples.md
Core Principles
- Consistency - Use consistent naming, structure, and behavior
- RESTful - Follow REST conventions for resource-based APIs
- Versioning - Plan for API evolution
- Documentation - APIs should be self-documenting
- Error Handling - Consistent, informative error responses
- Security - Authentication, authorization, input validation
- Performance - Pagination, caching, rate limiting
URL Structure
GET /api/v1/users # List users GET /api/v1/users/:id # Get specific user POST /api/v1/users # Create user PUT /api/v1/users/:id # Update user (full) PATCH /api/v1/users/:id # Update user (partial) DELETE /api/v1/users/:id # Delete user GET /api/v1/users/:id/posts # Nested resource
Avoid:
- Verbs in URLs (
)/getUsers - Redundant paths (
)/user/create - Deep nesting (>2 levels)
HTTP Status Codes
| Code | Meaning | When to Use |
|---|---|---|
| 200 | OK | Success (GET, PUT, PATCH) |
| 201 | Created | Resource created (POST) |
| 204 | No Content | Success with no body (DELETE) |
| 400 | Bad Request | Invalid input |
| 401 | Unauthorized | Not authenticated |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate or state conflict |
| 422 | Unprocessable | Validation failed |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Error | Server error |
Response Format
Success Response
{ "data": { "id": "123", "name": "John Doe" }, "meta": { "timestamp": "2025-10-26T10:00:00Z" } }
List Response (Paginated)
{ "data": [ { "id": "1", "name": "User 1" } ], "meta": { "total": 100, "page": 1, "perPage": 20, "totalPages": 5 }, "links": { "first": "/api/v1/users?page=1", "prev": null, "next": "/api/v1/users?page=2", "last": "/api/v1/users?page=5" } }
Error Response
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid input data", "details": [ { "field": "email", "message": "Invalid email format" } ] } }
API Versioning
// URL versioning (recommended) app.use('/api/v1', routesV1); app.use('/api/v2', routesV2); // Deprecation headers res.setHeader('Deprecation', 'true'); res.setHeader('Sunset', 'Wed, 11 Nov 2025 11:11:11 GMT');
GraphQL Patterns
Schema Design
type User { id: ID! name: String! email: String! posts: [Post!]! # Resolver handles N+1 with DataLoader createdAt: DateTime! } type Post { id: ID! title: String! content: String! author: User! } type Query { user(id: ID!): User users(first: Int, after: String): UserConnection! } type Mutation { createUser(input: CreateUserInput!): User! updateUser(id: ID!, input: UpdateUserInput!): User! deleteUser(id: ID!): Boolean! } input CreateUserInput { name: String! email: String! }
Resolver Patterns (Node.js)
const resolvers = { Query: { user: async (_, { id }, { dataSources }) => { return dataSources.usersAPI.getUser(id); }, users: async (_, { first, after }, { dataSources }) => { return dataSources.usersAPI.getUsers({ first, after }); }, }, Mutation: { createUser: async (_, { input }, { dataSources }) => { return dataSources.usersAPI.createUser(input); }, }, User: { // Field resolver with DataLoader to prevent N+1 posts: async (user, _, { loaders }) => { return loaders.postsByUserId.load(user.id); }, }, };
Error Handling
// Throw typed errors import { GraphQLError } from 'graphql'; throw new GraphQLError('User not found', { extensions: { code: 'NOT_FOUND', http: { status: 404 }, }, }); // Common error codes // UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, INTERNAL_ERROR
Pagination (Relay Cursor-Based)
type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! } type UserEdge { node: User! cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }
GraphQL vs REST - When to Use
| Use GraphQL | Use REST |
|---|---|
| Multiple clients with different data needs | Simple CRUD operations |
| Deeply nested data in single request | Caching critical (HTTP caching) |
| Rapid iteration, evolving schema | Public API with stability guarantees |
| Mobile apps (minimize requests) | File uploads, streaming |
Quick Reference
Do
- Use plural nouns for collections (
)/users - Return appropriate status codes
- Validate all inputs
- Implement pagination for lists
- Use consistent error format
- Version your APIs
- Document with OpenAPI/Swagger
- Implement rate limiting
- Use HTTPS in production
Don't
- Use verbs in URLs
- Nest resources more than 2 levels
- Return sensitive data
- Use HTTP 200 for errors
- Expose internal error details
- Skip input validation
- Return huge lists without pagination
Input Validation
Always validate using schemas:
TypeScript (Zod):
const createUserSchema = z.object({ name: z.string().min(1).max(100), email: z.string().email(), age: z.number().int().min(0).max(150).optional(), });
Python (Pydantic):
class UserCreate(BaseModel): name: str = Field(..., min_length=1, max_length=100) email: EmailStr age: Optional[int] = Field(None, ge=0, le=150)
Authentication Pattern
- Extract token from
headerAuthorization: Bearer <token> - Verify token signature and expiration
- Load user from token payload
- Attach user to request context
- Return 401 for invalid/missing tokens
Authorization Pattern
- Check user role/permissions after authentication
- Use middleware/decorators for role checks
- Return 403 for insufficient permissions
Rate Limiting
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // requests per window message: { error: { code: 'RATE_LIMIT_EXCEEDED', message: 'Too many requests', }, }, });
Testing APIs
describe('GET /api/v1/users', () => { it('returns paginated users', async () => { const response = await request(app) .get('/api/v1/users?page=1') .expect(200); expect(response.body).toHaveProperty('data'); expect(response.body).toHaveProperty('meta'); }); it('returns 401 without auth', async () => { await request(app).get('/api/v1/users').expect(401); }); });
Good API design makes your API intuitive, consistent, and easy to use.
Version
- v1.1.0 (2025-12-05): Enriched trigger keywords in description
- v1.0.0 (2025-11-15): Initial version