# API Designer **Description**: Design clean, consistent, and developer-friendly RESTful and GraphQL APIs ## Core Principles You are an API design expert who creates intuitive, well-documented, and scalable APIs that developers love to use. ## RESTful API Design ### 1. **Resource-Oriented URLs** ``` ✅ Good: Noun-based, resource-oriented GET /api/users # List users GET /api/users/123 # Get specific user POST /api/users # Create user PUT /api/users/123 # Update user (full) PATCH /api/users/123 # Update user (partial) DELETE /api/users/123 # Delete user GET /api/users/123/posts # Get user's posts POST /api/users/123/posts # Create post for user ❌ Bad: Verb-based, action-oriented GET /api/getUsers POST /api/createUser POST /api/updateUser POST /api/deleteUser ``` ### 2. **HTTP Methods (Verbs) Properly** ``` GET - Retrieve resources (safe, idempotent) POST - Create new resources PUT - Replace entire resource (idempotent) PATCH - Partial update DELETE - Remove resource (idempotent) Safe: No side effects (can cache) Idempotent: Same request = same result (can retry safely) ``` ### 3. **HTTP Status Codes** ``` ✅ Use appropriate status codes Success (2xx): 200 OK - Successful GET, PUT, PATCH, DELETE 201 Created - Successful POST (resource created) 204 No Content - Successful DELETE (no response body) Client Errors (4xx): 400 Bad Request - Invalid syntax, validation error 401 Unauthorized - Authentication required 403 Forbidden - Authenticated but not authorized 404 Not Found - Resource doesn't exist 409 Conflict - Conflict with current state (e.g., duplicate) 422 Unprocessable - Validation error (semantic) 429 Too Many Requests - Rate limiting Server Errors (5xx): 500 Internal Server Error - Generic server error 502 Bad Gateway - Upstream service error 503 Service Unavailable - Temporary unavailability ❌ Avoid: Always returning 200 with error in body ``` ### 4. **Request/Response Format** ```json // ✅ Good: Consistent structure // Success response { "data": { "id": "123", "name": "John Doe", "email": "john@example.com", "createdAt": "2025-01-15T10:30:00Z" }, "meta": { "timestamp": "2025-01-15T10:30:00Z", "version": "1.0" } } // List response with pagination { "data": [ { "id": "1", "name": "User 1" }, { "id": "2", "name": "User 2" } ], "meta": { "page": 1, "perPage": 20, "total": 150, "totalPages": 8 }, "links": { "self": "/api/users?page=1", "next": "/api/users?page=2", "last": "/api/users?page=8" } } // Error response { "error": { "code": "VALIDATION_ERROR", "message": "Invalid input data", "details": [ { "field": "email", "message": "Invalid email format" } ] }, "meta": { "timestamp": "2025-01-15T10:30:00Z", "requestId": "req_abc123" } } ``` ### 5. **Filtering, Sorting, Pagination** ``` ✅ Good: Query parameters for operations // Filtering GET /api/products?category=electronics&minPrice=100&maxPrice=500 // Sorting GET /api/products?sort=price # Ascending GET /api/products?sort=-price # Descending GET /api/products?sort=category,-price # Multi-field // Pagination (offset-based) GET /api/products?page=2&perPage=20 // Pagination (cursor-based for large datasets) GET /api/products?cursor=abc123&limit=20 // Field selection (sparse fieldsets) GET /api/users?fields=id,name,email // Search GET /api/products?q=laptop ``` ### 6. **Versioning** ``` ✅ Option 1: URL path (most common) GET /api/v1/users GET /api/v2/users ✅ Option 2: Header GET /api/users Headers: API-Version: 1 ✅ Option 3: Content negotiation GET /api/users Accept: application/vnd.myapi.v1+json ❌ Bad: No versioning (breaking changes break clients) ``` ## API Design Patterns ### 1. **Nested Resources** ``` // One level is fine GET /api/users/123/posts // Multiple levels get messy ❌ GET /api/users/123/posts/456/comments/789 // Better: Top-level access with filters ✅ GET /api/comments/789 ✅ GET /api/comments?postId=456 ✅ GET /api/comments?userId=123 ``` ### 2. **Bulk Operations** ``` // Bulk create POST /api/users/bulk { "data": [ { "name": "User 1", "email": "user1@example.com" }, { "name": "User 2", "email": "user2@example.com" } ] } // Bulk update PATCH /api/users/bulk { "data": [ { "id": "1", "name": "Updated 1" }, { "id": "2", "name": "Updated 2" } ] } // Response includes success and failures { "data": { "successful": [ { "id": "1", "name": "Updated 1" } ], "failed": [ { "id": "2", "error": "Validation error", "details": "Name too long" } ] } } ``` ### 3. **Actions on Resources** ``` // When REST verbs don't fit, use sub-resources // ❌ Avoid creating new endpoints POST /api/approveDocument POST /api/cancelOrder // ✅ Better: Actions as sub-resources POST /api/documents/123/approve POST /api/orders/456/cancel // Or state transition PATCH /api/documents/123 { "status": "approved" } PATCH /api/orders/456 { "status": "cancelled" } ``` ### 4. **Idempotency** ``` // For retry safety, use idempotency keys POST /api/payments Headers: Idempotency-Key: unique-request-id-123 Body: { "amount": 100, "currency": "USD" } // Multiple identical requests = same result // Server tracks idempotency key and returns cached response ``` ### 5. **Rate Limiting** ``` // Include rate limit info in headers Headers: X-RateLimit-Limit: 1000 # Max requests per window X-RateLimit-Remaining: 234 # Requests remaining X-RateLimit-Reset: 1642262400 # When limit resets (Unix timestamp) // When exceeded Status: 429 Too Many Requests Headers: Retry-After: 3600 # Seconds until retry Body: { "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Too many requests, try again later" } } ``` ## Security Best Practices ### 1. **Authentication & Authorization** ``` // JWT Bearer Token (most common) Authorization: Bearer eyJhbGciOiJIUzI1NiIs... // API Key (for server-to-server) X-API-Key: your-api-key-here // OAuth 2.0 (for third-party access) Authorization: Bearer {access_token} // Always use HTTPS in production ``` ### 2. **Input Validation** ```javascript // Validate all inputs function createUser(req, res) { const { email, password, name } = req.body; // Type validation if (typeof email !== 'string') { return res.status(400).json({ error: { code: 'INVALID_TYPE', field: 'email' } }); } // Format validation if (!isValidEmail(email)) { return res.status(400).json({ error: { code: 'INVALID_FORMAT', field: 'email' } }); } // Length validation if (password.length < 8) { return res.status(400).json({ error: { code: 'TOO_SHORT', field: 'password' } }); } // Business rule validation if (await userExists(email)) { return res.status(409).json({ error: { code: 'DUPLICATE', field: 'email' } }); } // Sanitize inputs const sanitizedName = sanitizeHtml(name); // Continue with creation... } ``` ### 3. **Prevent Common Vulnerabilities** ``` ✅ Use parameterized queries (prevent SQL injection) ✅ Sanitize HTML inputs (prevent XSS) ✅ Validate content types ✅ Implement CORS properly ✅ Use CSRF tokens for state-changing operations ✅ Rate limit all endpoints ✅ Log security events ``` ## Documentation ### OpenAPI/Swagger Example ```yaml openapi: 3.0.0 info: title: User API version: 1.0.0 paths: /api/users: get: summary: List users parameters: - name: page in: query schema: type: integer default: 1 - name: perPage in: query schema: type: integer default: 20 maximum: 100 responses: '200': description: Success content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/User' meta: $ref: '#/components/schemas/PaginationMeta' post: summary: Create user requestBody: required: true content: application/json: schema: type: object required: - email - name properties: email: type: string format: email name: type: string minLength: 1 maxLength: 100 responses: '201': description: Created content: application/json: schema: $ref: '#/components/schemas/User' '400': description: Validation error content: application/json: schema: $ref: '#/components/schemas/Error' components: schemas: User: type: object properties: id: type: string email: type: string name: type: string createdAt: type: string format: date-time ``` ## Testing APIs ### 1. **Test Structure** ```javascript describe('POST /api/users', () => { describe('Success Cases', () => { it('should create user with valid data', async () => { const response = await request(app) .post('/api/users') .send({ email: 'test@example.com', name: 'Test' }) .expect(201); expect(response.body.data).toHaveProperty('id'); expect(response.body.data.email).toBe('test@example.com'); }); }); describe('Validation Errors', () => { it('should return 400 for invalid email', async () => { const response = await request(app) .post('/api/users') .send({ email: 'invalid', name: 'Test' }) .expect(400); expect(response.body.error.code).toBe('VALIDATION_ERROR'); }); it('should return 400 for missing required field', async () => { await request(app) .post('/api/users') .send({ name: 'Test' }) .expect(400); }); }); describe('Business Logic Errors', () => { it('should return 409 for duplicate email', async () => { await createUser({ email: 'existing@example.com' }); await request(app) .post('/api/users') .send({ email: 'existing@example.com', name: 'Test' }) .expect(409); }); }); describe('Authorization', () => { it('should return 401 without auth token', async () => { await request(app) .post('/api/users') .send({ email: 'test@example.com', name: 'Test' }) .expect(401); }); }); }); ``` ## Performance Optimization ### 1. **Caching** ``` // Cache headers Cache-Control: public, max-age=3600 # Cache for 1 hour ETag: "abc123" # Version identifier // Conditional requests If-None-Match: "abc123" # Client sends ETag Response: 304 Not Modified # If unchanged // Vary header (cache based on header) Vary: Accept-Language, Authorization ``` ### 2. **Compression** ``` // Enable gzip/brotli compression Content-Encoding: gzip ``` ### 3. **Field Selection** ``` // Only return requested fields GET /api/users?fields=id,name,email // Reduces payload size and database load ``` ## When to Use This Skill - Designing new APIs from scratch - Refactoring existing APIs - Reviewing API specifications - Implementing API endpoints - Writing API documentation - Debugging API issues --- **Remember**: Good API design is about consistency, predictability, and developer experience. Make your API intuitive and well-documented!