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.

$ Installieren

git clone https://github.com/benshapyro/cadre-devkit-claude /tmp/cadre-devkit-claude && cp -r /tmp/cadre-devkit-claude/skills/api-design-patterns ~/.claude/skills/cadre-devkit-claude

// tip: Run this command in your terminal to install the skill


name: api-design-patterns description: 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.

API Design Patterns Skill

Best practices for designing RESTful and GraphQL APIs.

Resources

For detailed code examples, see:

  • references/express-examples.md - Node.js + Express patterns
  • references/fastapi-examples.md - FastAPI (Python) patterns
  • references/flask-examples.md - Flask (Python) patterns

Core Principles

  1. Consistency - Use consistent naming, structure, and behavior
  2. RESTful - Follow REST conventions for resource-based APIs
  3. Versioning - Plan for API evolution
  4. Documentation - APIs should be self-documenting
  5. Error Handling - Consistent, informative error responses
  6. Security - Authentication, authorization, input validation
  7. 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

CodeMeaningWhen to Use
200OKSuccess (GET, PUT, PATCH)
201CreatedResource created (POST)
204No ContentSuccess with no body (DELETE)
400Bad RequestInvalid input
401UnauthorizedNot authenticated
403ForbiddenAuthenticated but not authorized
404Not FoundResource doesn't exist
409ConflictDuplicate or state conflict
422UnprocessableValidation failed
429Too Many RequestsRate limit exceeded
500Internal ErrorServer 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 GraphQLUse REST
Multiple clients with different data needsSimple CRUD operations
Deeply nested data in single requestCaching critical (HTTP caching)
Rapid iteration, evolving schemaPublic 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

  1. Extract token from Authorization: Bearer <token> header
  2. Verify token signature and expiration
  3. Load user from token payload
  4. Attach user to request context
  5. Return 401 for invalid/missing tokens

Authorization Pattern

  1. Check user role/permissions after authentication
  2. Use middleware/decorators for role checks
  3. 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