home / skills / trantuananh-17 / product-reviews / api-design

api-design skill

/.claude/skills/api-design

This skill helps you design REST APIs with best practices, validation, and standardized response formats for robust backend endpoints.

npx playbooks add skill trantuananh-17/product-reviews --skill api-design

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.5 KB
---
name: api-design
description: Use this skill when the user asks to "create an API endpoint", "build a REST API", "add a controller", "design an API", "implement CRUD operations", "add validation", "handle API errors", or any backend API development work. Provides REST API design patterns, response formats, validation, and best practices.
---

# REST API Design (packages/functions)

> For **security patterns**, see `security` skill

## Directory Structure

```
packages/functions/src/
├── routes/              # Route definitions
│   ├── api.js           # Admin API routes
│   ├── restApiV2.js     # Public REST API v2
│   └── apiHookV1.js     # Webhook routes
├── controllers/         # Request handlers
├── middleware/          # Auth, validation, rate limiting
├── validations/         # Yup schemas
└── helpers/
    └── restApiResponse.js  # Response helpers
```

---

## Response Format

### Response Helpers

```javascript
import {
  successResponse,
  errorResponse,
  paginatedResponse,
  itemResponse
} from '../helpers/restApiResponse';

// Single item
ctx.body = itemResponse(customer);

// Paginated list
ctx.body = paginatedResponse(customers, pageInfo, total);

// Error
ctx.status = 400;
ctx.body = errorResponse('Invalid email', 'VALIDATION_ERROR', 400);
```

### Response Structure

| Type | Format |
|------|--------|
| Success | `{success: true, data, meta, timestamp}` |
| Error | `{success: false, error: {message, code, statusCode}, timestamp}` |
| Paginated | `{success: true, data: [], meta: {pagination: {...}}}` |

---

## HTTP Status Codes

| Code | When to Use |
|------|-------------|
| 200 | Successful GET, PUT |
| 201 | Successful POST (created) |
| 204 | Successful DELETE |
| 400 | Validation error, malformed request |
| 401 | Missing/invalid authentication |
| 403 | Authenticated but not authorized |
| 404 | Resource not found |
| 422 | Business logic error |
| 429 | Rate limit exceeded |
| 500 | Server error |

---

## Route Design

### RESTful Conventions

| Action | Method | Route |
|--------|--------|-------|
| List | GET | `/resources` |
| Get one | GET | `/resources/:id` |
| Create | POST | `/resources` |
| Update | PUT | `/resources/:id` |
| Delete | DELETE | `/resources/:id` |
| Action | POST | `/resources/:id/action` |

### Route Organization

```javascript
import Router from 'koa-router';

const router = new Router({prefix: '/api/v2'});

router.use(verifyAuthenticate);
router.use(verifyPlanAccess);

// Resources
router.get('/customers', validateQuery(paginationSchema), getCustomers);
router.get('/customers/:id', getCustomer);
router.post('/customers', validateInput(createSchema), createCustomer);
router.put('/customers/:id', validateInput(updateSchema), updateCustomer);

// Sub-resources
router.get('/customers/:id/rewards', getCustomerRewards);

// Actions
router.post('/customers/:id/points/award', awardPoints);
```

---

## Input Validation

### Yup Schemas

```javascript
import * as Yup from 'yup';

export const createCustomerSchema = Yup.object({
  email: Yup.string().email().required(),
  firstName: Yup.string().max(100).optional(),
  points: Yup.number().positive().optional()
});

export const paginationSchema = Yup.object({
  limit: Yup.number().min(1).max(100).default(20),
  cursor: Yup.string().optional()
});
```

### Validation Middleware

```javascript
export function validateInput(schema) {
  return async (ctx, next) => {
    try {
      ctx.request.body = await schema.validate(ctx.request.body, {
        stripUnknown: true
      });
      await next();
    } catch (error) {
      ctx.status = 400;
      ctx.body = errorResponse(error.message, 'VALIDATION_ERROR', 400);
    }
  };
}
```

---

## Controller Pattern

```javascript
export async function getOne(ctx) {
  try {
    const {shop} = ctx.state;
    const {id} = ctx.params;

    const resource = await repository.getById(shop.id, id);

    if (!resource) {
      ctx.status = 404;
      ctx.body = errorResponse('Not found', 'NOT_FOUND', 404);
      return;
    }

    ctx.body = itemResponse(pick(resource, publicFields));
  } catch (error) {
    console.error('Error:', error);
    ctx.status = 500;
    ctx.body = errorResponse('Server error', 'INTERNAL_ERROR', 500);
  }
}
```

---

## Pagination

### Cursor-Based (Preferred)

```javascript
// Request
GET /api/customers?limit=20&cursor=eyJpZCI6IjEyMyJ9

// Response
{
  "data": [...],
  "meta": {
    "pagination": {
      "hasNext": true,
      "nextCursor": "eyJpZCI6IjE0MyJ9",
      "limit": 20
    }
  }
}
```

---

## Error Codes

| Code | When |
|------|------|
| `UNAUTHORIZED` | Missing/invalid credentials |
| `FORBIDDEN` | No permission |
| `PLAN_RESTRICTED` | Feature not in plan |
| `VALIDATION_ERROR` | Invalid input |
| `NOT_FOUND` | Resource doesn't exist |
| `RATE_LIMITED` | Too many requests |
| `INTERNAL_ERROR` | Server error |

---

## Best Practices

| Do | Don't |
|----|-------|
| Use response helpers | Return raw objects |
| Set correct status codes | Return 200 for errors |
| Validate all inputs | Trust user input |
| Pick response fields | Expose internal fields |
| Scope queries by shopId | Query without shop filter |
| Use cursor pagination | Use offset at scale |

---

## Checklist

```
□ Uses response helpers (successResponse/errorResponse)
□ Correct HTTP status codes
□ Input validated with Yup schema
□ Queries scoped by shopId
□ Response fields picked (no internal data)
□ Error handling with try-catch
□ Rate limiting applied
□ Authentication middleware
```

Overview

This skill provides pragmatic REST API design patterns, response formats, validation, and controller structure for building robust backend endpoints. It focuses on consistent responses, correct HTTP status codes, input validation, pagination, and error handling. Use it to implement CRUD endpoints, actions, and webhook handlers with predictable behavior.

How this skill works

The skill defines route organization, controller patterns, and response helpers to standardize API output (success, error, paginated, item). It recommends Yup schemas and validation middleware to sanitize input and return structured validation errors. Controllers follow a try/catch pattern, scope queries by tenant/shop, and use response helpers to set status and body consistently.

When to use it

  • Creating new CRUD endpoints or controller actions
  • Designing API response formats and error codes
  • Adding input validation and sanitization
  • Implementing pagination for list endpoints
  • Standardizing status codes and error handling

Best practices

  • Use response helpers (successResponse, errorResponse, paginatedResponse, itemResponse) for consistent output
  • Validate all inputs with Yup and strip unknown fields before processing
  • Return correct HTTP status codes (201 for create, 204 for delete, 400/422 for client errors, 500 for server errors)
  • Scope queries by tenant/shopId and pick only public response fields
  • Prefer cursor-based pagination for large data sets and include pagination meta

Example use cases

  • Add POST /customers with createCustomerSchema validation and return 201 with itemResponse
  • Implement GET /customers?limit=&cursor= using cursor pagination and paginatedResponse
  • Protect routes with authentication middleware and return UNAUTHORIZED or FORBIDDEN error codes
  • Handle resource lookup in controllers and return 404 with errorResponse when not found
  • Add an action endpoint POST /customers/:id/points/award that validates payload and returns updated item

FAQ

How should errors be structured?

Return {success:false, error:{message, code, statusCode}, timestamp} and set the corresponding HTTP status code.

When to use 422 vs 400?

Use 400 for malformed requests or validation failures and 422 for business-logic errors where the request is syntactically valid but cannot be processed.