api-design

Design clean, scalable, and maintainable REST and GraphQL APIs following industry best practices. Use when designing public or internal APIs, planning endpoint structures, defining request/response contracts, establishing versioning strategies, implementing authentication patterns, designing data models, creating API documentation, ensuring consistent error handling, optimizing for performance, or establishing service contracts between microservices.

$ 설치

git clone https://github.com/korallis/Droidz /tmp/Droidz && cp -r /tmp/Droidz/droidz_installer/payloads/droid_cli/default/skills/api-design ~/.claude/skills/Droidz

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


name: api-design description: Design clean, scalable, and maintainable REST and GraphQL APIs following industry best practices. Use when designing public or internal APIs, planning endpoint structures, defining request/response contracts, establishing versioning strategies, implementing authentication patterns, designing data models, creating API documentation, ensuring consistent error handling, optimizing for performance, or establishing service contracts between microservices.

API Design - Building Clean, Scalable REST & GraphQL APIs

When to use this skill

  • Designing new REST or GraphQL APIs from scratch
  • Planning endpoint structures and URL patterns
  • Defining request/response contracts and data schemas
  • Establishing API versioning and deprecation strategies
  • Implementing authentication and authorization patterns
  • Creating API documentation (OpenAPI/Swagger)
  • Designing error response formats and status codes
  • Planning pagination, filtering, and sorting strategies
  • Establishing rate limiting and throttling policies
  • Designing webhooks or event-driven integrations
  • Creating service contracts for microservices
  • Optimizing API performance and caching strategies

When to use this skill

  • Designing public or internal APIs, planning endpoints, defining contracts between services.
  • When working on related tasks or features
  • During development that requires this expertise

Use when: Designing public or internal APIs, planning endpoints, defining contracts between services.

Core Principles

  1. Consistency - Predictable patterns across endpoints
  2. Simplicity - Easy to understand and use
  3. Versioning - Support evolution without breaking clients
  4. Security - Authentication, authorization, rate limiting
  5. Documentation - Clear, up-to-date API specs

REST API Design

1. Resource-Based URLs

✅ Good - Nouns, not verbs
GET    /users              - List users
GET    /users/:id          - Get specific user
POST   /users              - Create user
PUT    /users/:id          - Update user (full replace)
PATCH  /users/:id          - Update user (partial)
DELETE /users/:id          - Delete user

GET    /users/:id/posts    - Get user's posts
POST   /users/:id/posts    - Create post for user

❌ Bad - Verbs in URLs
GET    /getUsers
POST   /createUser
POST   /users/delete/:id

2. HTTP Methods & Status Codes

// ✅ Proper HTTP method usage
app.get('/users', async (req, res) => {
  const users = await db.users.findAll();
  res.json(users); // 200 OK
});

app.post('/users', async (req, res) => {
  const user = await db.users.create(req.body);
  res.status(201) // 201 Created
     .location(`/users/${user.id}`)
     .json(user);
});

app.put('/users/:id', async (req, res) => {
  const user = await db.users.update(req.params.id, req.body);
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }
  res.json(user); // 200 OK
});

app.delete('/users/:id', async (req, res) => {
  await db.users.delete(req.params.id);
  res.status(204).send(); // 204 No Content
});

// ✅ Common status codes
// 2xx Success
200 OK                  - Request succeeded
201 Created             - Resource created
204 No Content          - Succeeded, no response body

// 4xx Client Errors
400 Bad Request         - Invalid input
401 Unauthorized        - Not authenticated
403 Forbidden           - Authenticated but no permission
404 Not Found           - Resource doesn't exist
409 Conflict            - Duplicate or conflicting state
422 Unprocessable       - Validation failed
429 Too Many Requests   - Rate limited

// 5xx Server Errors
500 Internal Error      - Something broke on server
503 Service Unavailable - Temporarily down

3. Request/Response Format

// ✅ Consistent response structure
interface ApiResponse<T> {
  data: T;
  meta?: {
    page?: number;
    limit?: number;
    total?: number;
  };
  links?: {
    self: string;
    next?: string;
    prev?: string;
  };
}

// Success response
{
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com"
  }
}

// List response with pagination
{
  "data": [
    { "id": "1", "name": "User 1" },
    { "id": "2", "name": "User 2" }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 100
  },
  "links": {
    "self": "/users?page=1",
    "next": "/users?page=2"
  }
}

// Error response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid email format",
    "details": [
      {
        "field": "email",
        "message": "Must be valid email"
      }
    ]
  }
}

4. Filtering, Sorting, Pagination

// ✅ Query parameters for filtering
GET /users?status=active&role=admin
GET /posts?author=john&tags=tech,programming
GET /products?minPrice=10&maxPrice=100

app.get('/users', async (req, res) => {
  const { status, role, page = 1, limit = 20, sort = 'createdAt' } = req.query;
  
  const query = {};
  if (status) query.status = status;
  if (role) query.role = role;
  
  const users = await db.users.findMany({
    where: query,
    skip: (page - 1) * limit,
    take: limit,
    orderBy: { [sort]: 'desc' }
  });
  
  const total = await db.users.count({ where: query });
  
  res.json({
    data: users,
    meta: { page, limit, total },
    links: {
      self: `/users?page=${page}`,
      next: page * limit < total ? `/users?page=${page + 1}` : null
    }
  });
});

// ✅ Sorting
GET /users?sort=name           - Sort by name ascending
GET /users?sort=-createdAt     - Sort by createdAt descending
GET /users?sort=role,-createdAt - Multiple sort fields

// ✅ Field selection (sparse fieldsets)
GET /users?fields=id,name,email - Only return specified fields

5. Versioning

// ✅ URL versioning (most common)
GET /api/v1/users
GET /api/v2/users

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

// ✅ Header versioning
GET /api/users
Headers: { "Accept-Version": "v2" }

// ✅ Deprecation warnings
app.use('/api/v1', (req, res, next) => {
  res.set('X-API-Deprecation', 'v1 will be deprecated on 2024-12-31');
  res.set('X-API-Upgrade', 'See /api/v2 for latest version');
  next();
});

6. Authentication & Authorization

// ✅ Bearer token authentication
GET /api/users
Headers: { "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }

function authenticate(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
}

// ✅ Rate limiting
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100
});

app.use('/api/', limiter);

// ✅ API keys for service-to-service
GET /api/users
Headers: { "X-API-Key": "sk_live_abc123..." }

function requireApiKey(req, res, next) {
  const apiKey = req.get('X-API-Key');
  
  if (!isValidApiKey(apiKey)) {
    return res.status(401).json({ error: 'Invalid API key' });
  }
  
  next();
}

7. HATEOAS (Hypermedia)

// ✅ Include related resource links
{
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com"
  },
  "links": {
    "self": "/users/123",
    "posts": "/users/123/posts",
    "followers": "/users/123/followers"
  }
}

// ✅ Action links for state transitions
{
  "data": {
    "id": "456",
    "status": "pending",
    "amount": 100
  },
  "actions": {
    "approve": {
      "method": "POST",
      "href": "/orders/456/approve"
    },
    "cancel": {
      "method": "DELETE",
      "href": "/orders/456"
    }
  }
}

GraphQL API Design

1. Schema Definition

# ✅ Clear, typed schema
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  published: Boolean!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  post(id: ID!): Post
  posts(authorId: ID, published: Boolean): [Post!]!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
  
  createPost(input: CreatePostInput!): Post!
  publishPost(id: ID!): Post!
}

input CreateUserInput {
  name: String!
  email: String!
}

input UpdateUserInput {
  name: String
  email: String
}

input CreatePostInput {
  title: String!
  content: String!
  authorId: ID!
}

2. Resolvers

// ✅ Efficient resolvers with DataLoader
import DataLoader from 'dataloader';

const userLoader = new DataLoader(async (ids) => {
  const users = await db.users.findMany({
    where: { id: { in: ids } }
  });
  
  // Return in same order as input IDs
  return ids.map(id => users.find(u => u.id === id));
});

const resolvers = {
  Query: {
    user: async (_, { id }) => {
      return await userLoader.load(id);
    },
    
    users: async (_, { limit = 20, offset = 0 }) => {
      return await db.users.findMany({
        take: limit,
        skip: offset
      });
    }
  },
  
  Mutation: {
    createUser: async (_, { input }) => {
      return await db.users.create(input);
    },
    
    updateUser: async (_, { id, input }) => {
      return await db.users.update(id, input);
    }
  },
  
  User: {
    // Resolver for User.posts field
    posts: async (user) => {
      return await db.posts.findMany({
        where: { authorId: user.id }
      });
    }
  }
};

3. Error Handling

// ✅ GraphQL error handling
import { GraphQLError } from 'graphql';

const resolvers = {
  Query: {
    user: async (_, { id }) => {
      const user = await db.users.findById(id);
      
      if (!user) {
        throw new GraphQLError('User not found', {
          extensions: {
            code: 'USER_NOT_FOUND',
            id
          }
        });
      }
      
      return user;
    }
  },
  
  Mutation: {
    createUser: async (_, { input }) => {
      try {
        return await db.users.create(input);
      } catch (error) {
        if (error.code === 'P2002') { // Unique constraint
          throw new GraphQLError('Email already exists', {
            extensions: {
              code: 'DUPLICATE_EMAIL',
              field: 'email'
            }
          });
        }
        throw error;
      }
    }
  }
};

API Documentation

1. OpenAPI/Swagger (REST)

// ✅ OpenAPI specification
/**
 * @swagger
 * /users:
 *   get:
 *     summary: List all users
 *     tags: [Users]
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *         description: Page number
 *     responses:
 *       200:
 *         description: Success
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 data:
 *                   type: array
 *                   items:
 *                     $ref: '#/components/schemas/User'
 */
app.get('/users', async (req, res) => {
  // Implementation
});

// components/schemas/User
/**
 * @swagger
 * components:
 *   schemas:
 *     User:
 *       type: object
 *       required:
 *         - id
 *         - email
 *       properties:
 *         id:
 *           type: string
 *           example: "123"
 *         name:
 *           type: string
 *           example: "John Doe"
 *         email:
 *           type: string
 *           format: email
 *           example: "john@example.com"
 */

2. GraphQL Documentation

# ✅ Descriptions in schema (auto-documented)
"""
Represents a user in the system
"""
type User {
  """Unique identifier"""
  id: ID!
  
  """User's full name"""
  name: String!
  
  """User's email address"""
  email: String!
  
  """Posts authored by this user"""
  posts: [Post!]!
}

"""
Create a new user
"""
createUser(
  """User details"""
  input: CreateUserInput!
): User!

API Best Practices

1. Idempotency

// ✅ Idempotent operations (safe to retry)
app.put('/users/:id', async (req, res) => {
  // PUT is idempotent - same result on repeat
  const user = await db.users.upsert({
    where: { id: req.params.id },
    create: req.body,
    update: req.body
  });
  res.json(user);
});

// ✅ Idempotency keys for POST
app.post('/payments', async (req, res) => {
  const idempotencyKey = req.get('Idempotency-Key');
  
  if (!idempotencyKey) {
    return res.status(400).json({ error: 'Idempotency-Key required' });
  }
  
  // Check if already processed
  const existing = await cache.get(`payment:${idempotencyKey}`);
  if (existing) {
    return res.json(existing); // Return cached result
  }
  
  const payment = await processPayment(req.body);
  await cache.set(`payment:${idempotencyKey}`, payment, 24 * 60 * 60);
  
  res.status(201).json(payment);
});

2. Caching

// ✅ HTTP caching headers
app.get('/products/:id', async (req, res) => {
  const product = await db.products.findById(req.params.id);
  
  // Generate ETag from content
  const etag = generateETag(product);
  
  // Check if client has current version
  if (req.get('If-None-Match') === etag) {
    return res.status(304).send(); // Not Modified
  }
  
  res.set({
    'ETag': etag,
    'Cache-Control': 'public, max-age=300', // 5 minutes
    'Last-Modified': product.updatedAt.toUTCString()
  });
  
  res.json(product);
});

3. Webhooks

// ✅ Webhook system for async events
interface WebhookPayload {
  event: string;
  data: any;
  timestamp: string;
  signature: string; // HMAC for verification
}

async function sendWebhook(url: string, event: string, data: any) {
  const payload = {
    event,
    data,
    timestamp: new Date().toISOString()
  };
  
  // Sign payload
  const signature = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Webhook-Signature': signature
    },
    body: JSON.stringify({ ...payload, signature })
  });
}

// Trigger webhooks on events
app.post('/users', async (req, res) => {
  const user = await db.users.create(req.body);
  
  // Send webhook asynchronously
  sendWebhook(
    'https://customer.com/webhooks',
    'user.created',
    user
  ).catch(console.error);
  
  res.status(201).json(user);
});

API Design Checklist

REST:
□ Resource-based URLs (nouns, not verbs)
□ Proper HTTP methods and status codes
□ Consistent response format
□ Pagination for lists
□ Filtering, sorting, field selection
□ API versioning strategy
□ Authentication (Bearer tokens, API keys)
□ Rate limiting configured
□ CORS properly configured
□ Error responses standardized

GraphQL:
□ Clear, typed schema
□ Efficient resolvers (DataLoader)
□ Pagination implemented (cursor or offset)
□ Error handling with codes
□ Authentication & authorization
□ Query complexity limits
□ Depth limiting
□ Introspection disabled in production

Documentation:
□ OpenAPI/GraphQL schema published
□ Example requests/responses
□ Authentication docs
□ Error codes documented
□ Changelog maintained
□ Migration guides for breaking changes

Performance:
□ Database queries optimized
□ N+1 queries eliminated
□ Response caching
□ Compression enabled (gzip)
□ CDN for static responses

Security:
□ Input validation on all endpoints
□ SQL injection prevention
□ Rate limiting per endpoint
□ API key rotation supported
□ Audit logging for sensitive operations

Resources


Remember: Great APIs are predictable, well-documented, and easy to use. Design for your API consumers, not just your implementation.

Repository

korallis
korallis
Author
korallis/Droidz/droidz_installer/payloads/droid_cli/default/skills/api-design
49
Stars
6
Forks
Updated4d ago
Added6d ago