rest-api-design

📁 aj-geddes/useful-ai-prompts 📅 Jan 21, 2026
176
总安装量
177
周安装量
#1513
全站排名
安装命令
npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill rest-api-design

Agent 安装分布

opencode 132
gemini-cli 126
codex 125
github-copilot 106
cursor 90

Skill 文档

REST API Design

Overview

Design REST APIs that are intuitive, consistent, and follow industry best practices for resource-oriented architecture.

When to Use

  • Designing new RESTful APIs
  • Creating endpoint structures
  • Defining request/response formats
  • Implementing API versioning
  • Documenting API specifications
  • Refactoring existing APIs

Instructions

1. Resource Naming

✅ Good Resource Names (Nouns, Plural)
GET    /api/users
GET    /api/users/123
GET    /api/users/123/orders
POST   /api/products
DELETE /api/products/456

❌ Bad Resource Names (Verbs, Inconsistent)
GET    /api/getUsers
POST   /api/createProduct
GET    /api/user/123  (inconsistent singular/plural)

2. HTTP Methods & Operations

# CRUD Operations
GET    /api/users          # List all users (Read collection)
GET    /api/users/123      # Get specific user (Read single)
POST   /api/users          # Create new user (Create)
PUT    /api/users/123      # Replace user completely (Update)
PATCH  /api/users/123      # Partial update user (Partial update)
DELETE /api/users/123      # Delete user (Delete)

# Nested Resources
GET    /api/users/123/orders       # Get user's orders
POST   /api/users/123/orders       # Create order for user
GET    /api/users/123/orders/456   # Get specific order

3. Request Examples

Creating a Resource

POST /api/users
Content-Type: application/json

{
  "email": "john@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "role": "admin"
}

Response: 201 Created
Location: /api/users/789
{
  "id": "789",
  "email": "john@example.com",
  "firstName": "John",
  "lastName": "Doe",
  "role": "admin",
  "createdAt": "2025-01-15T10:30:00Z",
  "updatedAt": "2025-01-15T10:30:00Z"
}

Updating a Resource

PATCH /api/users/789
Content-Type: application/json

{
  "firstName": "Jonathan"
}

Response: 200 OK
{
  "id": "789",
  "email": "john@example.com",
  "firstName": "Jonathan",
  "lastName": "Doe",
  "role": "admin",
  "updatedAt": "2025-01-15T11:00:00Z"
}

4. Query Parameters

# Filtering
GET /api/products?category=electronics&inStock=true

# Sorting
GET /api/users?sort=lastName,asc

# Pagination
GET /api/users?page=2&limit=20

# Field Selection
GET /api/users?fields=id,email,firstName

# Search
GET /api/products?q=laptop

# Multiple filters combined
GET /api/orders?status=pending&customer=123&sort=createdAt,desc&limit=50

5. Response Formats

Success Response

{
  "data": {
    "id": "123",
    "email": "user@example.com",
    "firstName": "John"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "version": "1.0"
  }
}

Collection Response with Pagination

{
  "data": [
    { "id": "1", "name": "Product 1" },
    { "id": "2", "name": "Product 2" }
  ],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 145,
    "totalPages": 8,
    "hasNext": true,
    "hasPrev": true
  },
  "links": {
    "self": "/api/products?page=2&limit=20",
    "first": "/api/products?page=1&limit=20",
    "prev": "/api/products?page=1&limit=20",
    "next": "/api/products?page=3&limit=20",
    "last": "/api/products?page=8&limit=20"
  }
}

Error Response

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": [
      {
        "field": "email",
        "message": "Email format is invalid"
      },
      {
        "field": "age",
        "message": "Must be at least 18"
      }
    ]
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "requestId": "abc-123-def"
  }
}

6. HTTP Status Codes

Success:
200 OK              - Successful GET, PATCH, DELETE
201 Created         - Successful POST (resource created)
204 No Content      - Successful DELETE (no response body)

Client Errors:
400 Bad Request     - Invalid request format/data
401 Unauthorized    - Missing or invalid authentication
403 Forbidden       - Authenticated but not authorized
404 Not Found       - Resource doesn't exist
409 Conflict        - Resource conflict (e.g., duplicate email)
422 Unprocessable   - Validation errors
429 Too Many Requests - Rate limit exceeded

Server Errors:
500 Internal Server Error - Generic server error
503 Service Unavailable   - Temporary unavailability

7. API Versioning

# URL Path Versioning (Recommended)
GET /api/v1/users
GET /api/v2/users

# Header Versioning
GET /api/users
Accept: application/vnd.myapi.v1+json

# Query Parameter (Not recommended)
GET /api/users?version=1

8. Authentication & Security

# JWT Bearer Token
GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# API Key
GET /api/users
X-API-Key: your-api-key-here

# Always use HTTPS in production
https://api.example.com/v1/users

9. Rate Limiting Headers

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 995
X-RateLimit-Reset: 1642262400

10. OpenAPI Documentation

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
  description: User management API

paths:
  /users:
    get:
      summary: List all users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'

    post:
      summary: Create a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserInput'
      responses:
        '201':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          description: Invalid input
        '409':
          description: Email already exists

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        firstName:
          type: string
        lastName:
          type: string
        createdAt:
          type: string
          format: date-time

    UserInput:
      type: object
      required:
        - email
        - firstName
        - lastName
      properties:
        email:
          type: string
          format: email
        firstName:
          type: string
        lastName:
          type: string

Best Practices

✅ DO

  • Use nouns for resources, not verbs
  • Use plural names for collections
  • Be consistent with naming conventions
  • Return appropriate HTTP status codes
  • Include pagination for collections
  • Provide filtering and sorting options
  • Version your API
  • Document thoroughly with OpenAPI
  • Use HTTPS
  • Implement rate limiting
  • Provide clear error messages
  • Use ISO 8601 for dates

❌ DON’T

  • Use verbs in endpoint names
  • Return 200 for errors
  • Expose internal IDs unnecessarily
  • Over-nest resources (max 2 levels)
  • Use inconsistent naming
  • Forget authentication
  • Return sensitive data
  • Break backward compatibility without versioning

Complete Example: Express.js

const express = require('express');
const app = express();

app.use(express.json());

// List users with pagination
app.get('/api/v1/users', async (req, res) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 20;
    const offset = (page - 1) * limit;

    const users = await User.findAndCountAll({
      limit,
      offset,
      attributes: ['id', 'email', 'firstName', 'lastName']
    });

    res.json({
      data: users.rows,
      pagination: {
        page,
        limit,
        total: users.count,
        totalPages: Math.ceil(users.count / limit)
      }
    });
  } catch (error) {
    res.status(500).json({
      error: {
        code: 'INTERNAL_ERROR',
        message: 'An error occurred while fetching users'
      }
    });
  }
});

// Get single user
app.get('/api/v1/users/:id', async (req, res) => {
  try {
    const user = await User.findByPk(req.params.id);

    if (!user) {
      return res.status(404).json({
        error: {
          code: 'NOT_FOUND',
          message: 'User not found'
        }
      });
    }

    res.json({ data: user });
  } catch (error) {
    res.status(500).json({
      error: {
        code: 'INTERNAL_ERROR',
        message: 'An error occurred'
      }
    });
  }
});

// Create user
app.post('/api/v1/users', async (req, res) => {
  try {
    const { email, firstName, lastName } = req.body;

    // Validation
    if (!email || !firstName || !lastName) {
      return res.status(400).json({
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Missing required fields',
          details: [
            !email && { field: 'email', message: 'Email is required' },
            !firstName && { field: 'firstName', message: 'First name is required' },
            !lastName && { field: 'lastName', message: 'Last name is required' }
          ].filter(Boolean)
        }
      });
    }

    const user = await User.create({ email, firstName, lastName });

    res.status(201)
       .location(`/api/v1/users/${user.id}`)
       .json({ data: user });
  } catch (error) {
    if (error.name === 'SequelizeUniqueConstraintError') {
      return res.status(409).json({
        error: {
          code: 'CONFLICT',
          message: 'Email already exists'
        }
      });
    }
    res.status(500).json({
      error: {
        code: 'INTERNAL_ERROR',
        message: 'An error occurred'
      }
    });
  }
});

app.listen(3000);