# Stoplight Studio API Designer: Visual API Development

# Stoplight Studio API Designer: Visual API Development

## The API That Broke Everything (And How Documentation Could Have Saved Us)

Our undocumented API changes broke 50 client apps. Here's what we learned the hard way.

## Table of Contents
- API Development 2026
- Design Principles
- 5 Essential Patterns
- Documentation Strategy
- Testing Approach
- Security Considerations
- Monitoring
- FAQ
- Best Practices

## API Development in 2026

APIs are the backbone of modern apps.

### API-First Approach

```yaml
# OpenAPI specification first
openapi: 3.1.0
info:
  title: User API
  version: 1.0.0
paths:
  /users:
    get:
      summary: List users
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
```

### Design-First Benefits

```typescript
// Generated TypeScript types
interface User {
  id: string;
  email: string;
  name: string;
  createdAt: string;
}

// Auto-generated client
const users = await api.users.list();
// TypeScript knows the response type!
```

### Business Impact

Good APIs = happy developers = more integrations.

## Design Principles

Build APIs that last.

### RESTful Conventions

```typescript
// Standard HTTP methods
app.get('/api/users', listUsers);          // Read all
app.get('/api/users/:id', getUser);        // Read one
app.post('/api/users', createUser);        // Create
app.put('/api/users/:id', updateUser);     // Update (full)
app.patch('/api/users/:id', patchUser);    // Update (partial)
app.delete('/api/users/:id', deleteUser);  // Delete
```

### Resource Naming

```typescript
// ✅ Good: Plural nouns
GET /api/users
GET /api/orders/123/items

// ❌ Bad: Verbs or singular
GET /api/getUser
GET /api/user
```

### Error Responses

```typescript
// Consistent error format
interface APIError {
  error: {
    code: string;
    message: string;
    details?: any;
    requestId: string;
  };
}

app.use((err, req, res, next) => {
  res.status(err.status || 500).json({
    error: {
      code: err.code || 'INTERNAL_ERROR',
      message: err.message,
      requestId: req.id
    }
  });
});
```

## Pattern 1: Request Validation

### Schema Validation

```typescript
// Validate with Zod
import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(2).max(100),
  age: z.number().int().min(13).max(120).optional()
});

app.post('/api/users', async (req, res) => {
  try {
    const data = createUserSchema.parse(req.body);
    const user = await createUser(data);
    res.status(201).json(user);
  } catch (error) {
    if (error instanceof z.ZodError) {
      res.status(400).json({
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Invalid input',
          details: error.errors
        }
      });
    }
  }
});
```

### Type Safety

Generate types from schema.

## Pattern 2: Pagination

### Cursor-Based

```typescript
// Efficient pagination
interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    nextCursor?: string;
    prevCursor?: string;
    hasMore: boolean;
  };
}

app.get('/api/users', async (req, res) => {
  const { cursor, limit = 20 } = req.query;

  const users = await db.users
    .where('id', '>', cursor || '')
    .limit(limit + 1)
    .orderBy('id');

  const hasMore = users.length > limit;
  const data = hasMore ? users.slice(0, -1) : users;

  res.json({
    data,
    pagination: {
      nextCursor: hasMore ? data[data.length - 1].id : undefined,
      hasMore
    }
  });
});
```

### Offset-Based

```typescript
// Simple but can be slow
app.get('/api/users', async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 20;
  const offset = (page - 1) * limit;

  const [users, total] = await Promise.all([
    db.users.limit(limit).offset(offset),
    db.users.count()
  ]);

  res.json({
    data: users,
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit)
    }
  });
});
```

## Pattern 3: Authentication

### JWT Implementation

```typescript
// Secure JWT handling
import { SignJWT, jwtVerify } from 'jose';

const secret = new TextEncoder().encode(process.env.JWT_SECRET);

export async function createToken(userId: string) {
  return await new SignJWT({ sub: userId })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('2h')
    .sign(secret);
}

// Middleware
async function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({
      error: { code: 'UNAUTHORIZED', message: 'No token' }
    });
  }

  try {
    const { payload } = await jwtVerify(token, secret);
    req.userId = payload.sub;
    next();
  } catch {
    res.status(401).json({
      error: { code: 'INVALID_TOKEN', message: 'Token invalid' }
    });
  }
}
```

### API Keys

```typescript
// Simple API key auth
async function apiKeyAuth(req, res, next) {
  const apiKey = req.headers['x-api-key'];

  const key = await db.apiKeys.findOne({
    key: apiKey,
    active: true
  });

  if (!key) {
    return res.status(401).json({
      error: { code: 'INVALID_KEY' }
    });
  }

  // Track usage
  await db.apiKeys.update(key.id, {
    lastUsed: new Date(),
    usageCount: key.usageCount + 1
  });

  req.apiKey = key;
  next();
}
```

## Pattern 4: Rate Limiting

### Implementation

```typescript
// Redis-based rate limiting
import { RateLimiterRedis } from 'rate-limiter-flexible';

const rateLimiter = new RateLimiterRedis({
  storeClient: redisClient,
  points: 100, // requests
  duration: 60  // per minute
});

async function rateLimitMiddleware(req, res, next) {
  const key = req.apiKey?.id || req.ip;

  try {
    await rateLimiter.consume(key);

    // Add rate limit headers
    const remaining = await rateLimiter.get(key);
    res.setHeader('X-RateLimit-Limit', 100);
    res.setHeader('X-RateLimit-Remaining', remaining?.remainingPoints || 100);

    next();
  } catch {
    res.status(429).json({
      error: {
        code: 'RATE_LIMIT_EXCEEDED',
        message: 'Too many requests'
      }
    });
  }
}
```

### Tiered Limits

Different limits per plan.

## Pattern 5: Versioning

### URL Versioning

```typescript
// Version in path
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

// v1Router
v1Router.get('/users', (req, res) => {
  // Old response format
  res.json({ users: [...] });
});

// v2Router
v2Router.get('/users', (req, res) => {
  // New response format with pagination
  res.json({
    data: [...],
    pagination: {...}
  });
});
```

### Header Versioning

```typescript
// Accept-Version header
app.use((req, res, next) => {
  const version = req.headers['accept-version'] || '1';
  req.apiVersion = version;
  next();
});
```

## Documentation Strategy

### Interactive Docs

```typescript
// Swagger UI setup
import swaggerUi from 'swagger-ui-express';
import { readFileSync } from 'fs';
import { load } from 'js-yaml';

const spec = load(readFileSync('./openapi.yaml', 'utf8'));

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(spec));
```

### Code Examples

```markdown
## Create User

POST /api/users

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

### Response
```json
{
  "id": "usr_123",
  "email": "user@example.com",
  "name": "John Doe",
  "createdAt": "2026-02-12T10:00:00Z"
}
```
```

## Testing Approach

### Integration Tests

```typescript
// Test API endpoints
import { describe, it, expect } from 'vitest';

describe('Users API', () => {
  it('creates user', async () => {
    const response = await fetch('http://localhost:3000/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: 'test@example.com',
        name: 'Test User'
      })
    });

    expect(response.status).toBe(201);
    const user = await response.json();
    expect(user.email).toBe('test@example.com');
  });
});
```

### Contract Testing

Ensure API matches spec.

## Performance

| Endpoint | Avg Response | p95 | p99 |
|----------|--------------|-----|-----|
| GET /users | 50ms | 80ms | 120ms |
| POST /users | 100ms | 150ms | 200ms |
| GET /users/:id | 20ms | 30ms | 50ms |

## FAQ

### Q1: REST vs GraphQL?

REST for public APIs, GraphQL for frontend-driven apps.

### Q2: How to version APIs?

URL versioning is clearest for clients.

### Q3: Rate limiting strategy?

Per-user or per-API-key with tiered limits.

### Q4: Documentation tool?

OpenAPI + Swagger UI or ReadMe.io.

### Q5: API key vs OAuth?

API keys for server-to-server, OAuth for user apps.

## Best Practices

### Checklist
- [ ] OpenAPI spec written
- [ ] Input validation
- [ ] Error handling
- [ ] Authentication
- [ ] Rate limiting
- [ ] Documentation
- [ ] Tests written
- [ ] Monitoring setup

## Conclusion

Great APIs drive adoption.

Key takeaways:
- Design first
- Document thoroughly
- Validate inputs
- Version carefully
- Monitor constantly

Build APIs developers love.

**Resources:**
- OpenAPI Specification
- API Design Patterns
- Testing Strategies
- Security Guidelines

**Next Steps:**
1. Write OpenAPI spec
2. Implement validation
3. Add authentication
4. Generate documentation
5. Deploy with monitoring

Build better APIs today.

