AgentSkillsCN

create_api_endpoint

创建全新的 RESTful API 端点,并配备完善的验证机制、错误处理逻辑及详尽的文档说明

SKILL.md
--- frontmatter
name: create_api_endpoint
description: Create a new RESTful API endpoint with proper validation, error handling, and documentation
category: backend
agent_affinity: backend_core

Create API Endpoint Skill

Purpose

This skill guides you through creating a robust, secure, and well-documented RESTful API endpoint following industry best practices.

When to Use

  • Creating new API endpoints
  • Exposing backend functionality to frontend
  • Building RESTful services
  • Need standardized endpoint structure

Prerequisites

  • Clear understanding of the endpoint's purpose
  • Data models defined (coordinate with Database_Architect)
  • Authentication requirements
  • Technology stack (Express, FastAPI, ASP.NET, etc.)

Step-by-Step Process

1. Define API Contract

yaml
# OpenAPI/Swagger Specification
paths:
  /api/users:
    post:
      summary: Create a new user
      tags:
        - Users
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
                - password
                - name
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  minLength: 8
                name:
                  type: string
      responses:
        '201':
          description: User created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          description: Validation error
        '409':
          description: User already exists
        '500':
          description: Server error

Contract Checklist:

  • HTTP method (GET, POST, PUT, PATCH, DELETE)
  • URL path with parameters
  • Request body schema
  • Response schemas (success and errors)
  • Authentication requirements
  • Rate limiting needs

2. Implement Request Validation

typescript
// Node.js/Express with Zod
import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email('Invalid email format'),
  password: z.string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Password must contain uppercase letter')
    .regex(/[0-9]/, 'Password must contain number'),
  name: z.string().min(1, 'Name is required').max(100),
});

type CreateUserInput = z.infer<typeof createUserSchema>;
python
# Python/FastAPI with Pydantic
from pydantic import BaseModel, EmailStr, Field, validator

class CreateUserInput(BaseModel):
    email: EmailStr
    password: str = Field(..., min_length=8)
    name: str = Field(..., min_length=1, max_length=100)
    
    @validator('password')
    def validate_password(cls, v):
        if not any(c.isupper() for c in v):
            raise ValueError('Password must contain uppercase letter')
        if not any(c.isdigit() for c in v):
            raise ValueError('Password must contain number')
        return v

Validation Checklist:

  • Required fields validated
  • Data types validated
  • Format validation (email, URL, etc.)
  • Length constraints
  • Pattern matching (regex)
  • Custom business rules

3. Implement Endpoint Logic

typescript
// Example: Node.js/Express
import { Router } from 'express';
import { authenticate } from '../middleware/auth';
import { validateRequest } from '../middleware/validation';
import { UserService } from '../services/user.service';

const router = Router();

router.post(
  '/api/users',
  authenticate, // Middleware: Check authentication
  validateRequest(createUserSchema), // Middleware: Validate request
  async (req, res, next) => {
    try {
      const input: CreateUserInput = req.body;
      
      // Business logic
      const userService = new UserService();
      const user = await userService.createUser(input);
      
      // Success response
      res.status(201).json({
        success: true,
        data: user,
        message: 'User created successfully',
      });
    } catch (error) {
      next(error); // Pass to error handler
    }
  }
);

export default router;

Implementation Checklist:

  • Use appropriate HTTP status codes
  • Implement proper error handling
  • Call service layer (don't put logic in controller)
  • Return consistent response format
  • Add logging
  • Handle async operations properly

4. Implement Service Layer

typescript
// user.service.ts
import bcrypt from 'bcrypt';
import { UserRepository } from '../repositories/user.repository';
import { ConflictError } from '../errors/ConflictError';

export class UserService {
  private userRepository: UserRepository;

  constructor() {
    this.userRepository = new UserRepository();
  }

  async createUser(input: CreateUserInput) {
    // Check if user exists
    const existingUser = await this.userRepository.findByEmail(input.email);
    if (existingUser) {
      throw new ConflictError('User with this email already exists');
    }

    // Hash password
    const hashedPassword = await bcrypt.hash(input.password, 10);

    // Create user
    const user = await this.userRepository.create({
      email: input.email,
      password: hashedPassword,
      name: input.name,
    });

    // Don't return password
    const { password, ...userWithoutPassword } = user;
    return userWithoutPassword;
  }
}

Service Layer Checklist:

  • Separate business logic from HTTP concerns
  • Use repository pattern for data access
  • Implement proper error handling
  • Don't leak sensitive data (passwords, tokens)
  • Add transaction support if needed
  • Log important operations

5. Implement Error Handling

typescript
// errors/ConflictError.ts
export class ConflictError extends Error {
  statusCode = 409;
  
  constructor(message: string) {
    super(message);
    this.name = 'ConflictError';
  }
}

// middleware/errorHandler.ts
export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
  // Log error
  console.error('Error:', {
    name: err.name,
    message: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
  });

  // Determine status code
  const statusCode = (err as any).statusCode || 500;

  // Send error response
  res.status(statusCode).json({
    success: false,
    error: {
      type: err.name,
      message: statusCode === 500 ? 'Internal server error' : err.message,
    },
  });
}

Error Handling Checklist:

  • Custom error classes for different scenarios
  • Appropriate HTTP status codes
  • Don't expose internal errors to clients
  • Log errors with context
  • Consistent error response format

6. Add Authentication & Authorization

typescript
// middleware/auth.ts
import jwt from 'jsonwebtoken';
import { UnauthorizedError } from '../errors/UnauthorizedError';

export async function authenticate(req: Request, res: Response, next: NextFunction) {
  try {
    // Extract token from header
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      throw new UnauthorizedError('Missing or invalid authorization header');
    }

    const token = authHeader.substring(7);

    // Verify token
    const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;

    // Attach user to request
    req.user = payload;
    next();
  } catch (error) {
    next(new UnauthorizedError('Invalid or expired token'));
  }
}

// Authorization example
export function requireRole(...roles: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user || !roles.includes(req.user.role)) {
      return next(new ForbiddenError('Insufficient permissions'));
    }
    next();
  };
}

Auth Checklist:

  • Validate authentication token
  • Check user permissions/roles
  • Handle expired tokens
  • Protect sensitive endpoints
  • Use secure token storage

7. Add Rate Limiting

typescript
import rateLimit from 'express-rate-limit';

const createUserLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // Max 5 requests per window
  message: 'Too many accounts created, please try again later',
  standardHeaders: true,
  legacyHeaders: false,
});

router.post('/api/users', createUserLimiter, ...);

Rate Limiting Checklist:

  • Protect against brute force
  • Set appropriate limits
  • Provide clear error messages
  • Consider different limits for authenticated users

8. Write Tests

typescript
// user.controller.test.ts
import request from 'supertest';
import { app } from '../app';

describe('POST /api/users', () => {
  it('creates a new user successfully', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({
        email: 'test@example.com',
        password: 'Password123',
        name: 'Test User',
      });

    expect(response.status).toBe(201);
    expect(response.body.success).toBe(true);
    expect(response.body.data.email).toBe('test@example.com');
    expect(response.body.data.password).toBeUndefined(); // Password should not be returned
  });

  it('returns 400 for invalid email', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({
        email: 'invalid-email',
        password: 'Password123',
        name: 'Test User',
      });

    expect(response.status).toBe(400);
  });

  it('returns 409 for duplicate email', async () => {
    // Create first user
    await request(app).post('/api/users').send({
      email: 'duplicate@example.com',
      password: 'Password123',
      name: 'First User',
    });

    // Try to create duplicate
    const response = await request(app)
      .post('/api/users')
      .send({
        email: 'duplicate@example.com',
        password: 'Password456',
        name: 'Second User',
      });

    expect(response.status).toBe(409);
  });

  it('requires authentication', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ email: 'test@example.com', password: 'Password123', name: 'Test' });

    expect(response.status).toBe(401);
  });
});

Testing Checklist:

  • Test successful cases
  • Test validation errors
  • Test business logic errors
  • Test authentication/authorization
  • Test edge cases
  • Test error handling

9. Document the Endpoint

markdown
# Create User Endpoint

## POST /api/users

Creates a new user account.

### Authentication
Requires bearer token authentication.

### Request Body
\`\`\`json
{
  "email": "user@example.com",
  "password": "Password123",
  "name": "John Doe"
}
\`\`\`

### Responses

#### 201 Created
\`\`\`json
{
  "success": true,
  "data": {
    "id": "123",
    "email": "user@example.com",
    "name": "John Doe",
    "createdAt": "2024-01-01T00:00:00Z"
  },
  "message": "User created successfully"
}
\`\`\`

#### 400 Bad Request
Invalid input data.

#### 409 Conflict
User with this email already exists.

#### 500 Internal Server Error
Server error occurred.

### Example
\`\`\`bash
curl -X POST https://api.example.com/api/users \\
  -H "Content-Type: application/json" \\
  -H "Authorization: Bearer YOUR_TOKEN" \\
  -d '{
    "email": "user@example.com",
    "password": "Password123",
    "name": "John Doe"
  }'
\`\`\`

HTTP Status Codes Reference

CodeMeaningWhen to Use
200OKSuccessful GET, PUT, PATCH
201CreatedSuccessful POST (resource created)
204No ContentSuccessful DELETE
400Bad RequestValidation error
401UnauthorizedMissing/invalid authentication
403ForbiddenInsufficient permissions
404Not FoundResource doesn't exist
409ConflictResource conflict (duplicate)
422Unprocessable EntitySemantic validation error
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer error

Response Format Standard

typescript
// Success response
{
  success: true,
  data: { /* response data */ },
  message?: string, // Optional success message
}

// Error response
{
  success: false,
  error: {
    type: string, // Error type/code
    message: string, // Human-readable message
    details?: any, // Additional error details
  }
}

Best Practices

  1. RESTful Design: Use proper HTTP methods and status codes
  2. Validation First: Validate all input before processing
  3. Security: Always authenticate/authorize protected endpoints
  4. Error Handling: Handle all errors gracefully
  5. Logging: Log important operations and errors
  6. Documentation: Document all endpoints thoroughly
  7. Testing: Test success and failure scenarios
  8. Performance: Consider caching and pagination

Security Checklist

  • Input validation and sanitization
  • Authentication required for protected endpoints
  • Authorization checks for role-based access
  • Rate limiting to prevent abuse
  • SQL injection prevention (use parameterized queries)
  • XSS prevention (sanitize output)
  • CSRF protection
  • Secure password hashing (bcrypt, argon2)
  • No sensitive data in logs or responses
  • HTTPS only in production

Common Pitfalls to Avoid

Don't put business logic in controllers
Don't return sensitive data (passwords, tokens)
Don't use string concatenation for SQL queries
Don't ignore error handling
Don't expose internal error details to clients
Don't skip input validation

Related Skills

  • validate_auth_flow - Validate authentication implementation
  • review_security - Security review for endpoints
  • design_database_schema - If endpoint needs new data models