Skip to content

💡 API Best Practices

🎯 Overview

This guide provides comprehensive best practices for working with the Continuum API Gateway, covering design, security, performance, and operational excellence. Following these guidelines ensures reliable, scalable, and maintainable API integrations.

🏗️ API Design

📝 Collection Design

✅ DO

Use Clear, Descriptive Names

// Good
{
  "name": "blog_posts",
  "title": "Blog Posts"
}

// Bad
{
  "name": "bp",
  "title": "BP"
}

Define Required Fields

{
  "fields": [
    {
      "name": "email",
      "type": "email",
      "required": true,  // Enforce at database level
      "unique": true
    }
  ]
}

Set Appropriate Defaults

{
  "name": "status",
  "type": "string",
  "defaultValue": "draft",  // Safe default
  "description": "Publication status"
}

Enable Timestamps

{
  "name": "articles",
  "options": {
    "timestamps": true,  // Always enable for audit trails
    "paranoid": true     // Consider soft deletes
  }
}

❌ DON'T

  • ❌ Use reserved keywords as names
  • ❌ Create overly complex nested structures
  • ❌ Skip field validation
  • ❌ Use inconsistent naming conventions (mix snake_case and camelCase)
  • ❌ Forget to document field purposes

🔗 Relationship Design

✅ DO

Choose Correct Relationship Types

// One-to-Many: Post has many comments
{
  "name": "comments",
  "type": "hasMany",
  "target": "comments"
}

// Many-to-Many: Post has many tags
{
  "name": "tags",
  "type": "belongsToMany",
  "target": "tags",
  "through": "post_tags"
}

Name Relationships Intuitively

// Good
{"name": "author", "type": "belongsTo", "target": "users"}

// Bad
{"name": "user_rel", "type": "belongsTo", "target": "users"}

Set Up Bidirectional Relationships

// In posts collection
{"name": "comments", "type": "hasMany", "target": "comments"}

// In comments collection
{"name": "post", "type": "belongsTo", "target": "posts"}

❌ DON'T

  • ❌ Create unnecessary relationships
  • ❌ Use many-to-many for one-to-many scenarios
  • ❌ Forget to index foreign keys
  • ❌ Create circular dependencies
  • ❌ Create deeply nested relationship chains

🔐 Security

🔑 API Key Management

✅ DO

Set APP_KEY Before Production

# .env
APP_KEY=randomly-generated-64-character-minimum-secret-key-here

Use Appropriate Expiration Times

# Production keys - shorter expiration
POST /api/apiKeys:create
{"role": {"name": "admin"}, "expiresIn": "30d"}

# Development keys - can be longer
POST /api/apiKeys:create
{"role": {"name": "developer"}, "expiresIn": "90d"}

Rotate Keys Regularly

# Create new key
# Update all services
# Test thoroughly
# Delete old key after grace period

Use Environment Variables

// Good
const API_KEY = process.env.CONTINUUM_API_KEY;

// Bad
const API_KEY = "eyJhbGciOiJIUzI1NiIs..."; // Hardcoded!

❌ DON'T

  • ❌ Commit API keys to version control
  • ❌ Share keys between environments
  • ❌ Create keys without expiration in production
  • ❌ Log API keys in application logs
  • ❌ Expose keys in client-side code
  • ❌ Use the same key for all applications

🛡️ Permissions

✅ DO

Apply Least Privilege Principle

// Good: Editor can only edit content
{
  "role": "editor",
  "resource": "articles",
  "action": ["list", "get", "create", "update"]
}

// Bad: Too permissive
{
  "role": "editor",
  "resource": "*",
  "action": "*"
}

Use Filter-Based Restrictions

{
  "role": "author",
  "resource": "articles",
  "action": "update",
  "filter": {
    "authorId": "{{ ctx.state.currentUser.id }}"
  }
}

Restrict Field Access

{
  "role": "public",
  "resource": "users",
  "action": "list",
  "fields": ["id", "name", "bio"],  // No email, password, etc.
}

❌ DON'T

  • ❌ Give admin access by default
  • ❌ Allow unrestricted schema modifications
  • ❌ Expose sensitive fields publicly
  • ❌ Skip permission testing
  • ❌ Use client-provided filter values

🔒 HTTPS & Transport Security

✅ DO

  • Always use HTTPS in production
  • ✅ Enforce TLS 1.2+ minimum
  • ✅ Implement certificate pinning for critical apps
  • ✅ Use secure headers (HSTS, CSP, etc.)

❌ DON'T

  • ❌ Use HTTP for API calls
  • ❌ Accept self-signed certificates in production
  • ❌ Disable certificate validation
  • ❌ Transmit API keys in URLs

⚡ Performance

🚀 Query Optimization

✅ DO

Use Pagination

# Good: Paginated
GET /api/articles:list?page=1&pageSize=20

# Bad: Fetching all records
GET /api/articles:list?paginate=false

Select Only Needed Fields

# Good: Specific fields
GET /api/articles:list?fields=id,title,createdAt

# Bad: All fields
GET /api/articles:list

Filter at Database Level

# Good: Database filter
GET /api/articles:list?filter={"status":"published"}

# Bad: Fetch all, filter in app
GET /api/articles:list
# Then filter in application code

Use Efficient Sorting

# Good: Sort by indexed field
GET /api/articles:list?sort=-createdAt

# Bad: Sort by unindexed computed field
GET /api/articles:list?sort=-complexCalculation

❌ DON'T

  • ❌ Fetch all records without pagination
  • ❌ Use SELECT * when few fields needed
  • ❌ Over-use appends (eager loading)
  • ❌ Create N+1 query problems
  • ❌ Sort by unindexed fields

💾 Caching

✅ DO

Cache Frequently Accessed Data

// Good: Cache static/rarely changing data
const cache = require('node-cache');
const myCache = new cache({ stdTTL: 600 }); // 10 minutes

async function getTags() {
  let tags = myCache.get('tags');
  if (!tags) {
    const response = await api.resource('tags').list();
    tags = response.data;
    myCache.set('tags', tags);
  }
  return tags;
}

Use ETags

# First request
GET /api/articles/1
# Response includes: ETag: "abc123"

# Subsequent request
GET /api/articles/1
If-None-Match: "abc123"
# Returns 304 Not Modified if unchanged

❌ DON'T

  • ❌ Cache sensitive user data without encryption
  • ❌ Use overly long cache TTLs
  • ❌ Forget to invalidate cache on updates
  • ❌ Cache paginated results incorrectly

📊 Indexing

✅ DO

Index Frequently Queried Fields

{
  "name": "email",
  "type": "string",
  "unique": true,  // Creates index automatically
}

{
  "name": "createdAt",
  "type": "datetime",
  // Add database index manually if frequently sorted
}

Index Foreign Keys

{
  "name": "author",
  "type": "belongsTo",
  "target": "users",
  "foreignKey": "authorId"  // Automatically indexed
}

❌ DON'T

  • ❌ Over-index (slows down writes)
  • ❌ Index low-cardinality fields (status with 2-3 values)
  • ❌ Forget to index frequently filtered fields
  • ❌ Index text fields (use full-text search instead)

🔄 API Usage

📡 Request Patterns

✅ DO

Use Bulk Operations

# Good: Single request for multiple creates
POST /api/articles:create
Content-Type: application/json

[
  {"title": "Article 1", "content": "..."},
  {"title": "Article 2", "content": "..."},
  {"title": "Article 3", "content": "..."}
]

# Bad: Multiple requests
POST /api/articles:create {"title": "Article 1"}
POST /api/articles:create {"title": "Article 2"}
POST /api/articles:create {"title": "Article 3"}

Handle Rate Limiting

// Good: Exponential backoff
async function apiCallWithRetry(apiCall, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await apiCall();
    } catch (error) {
      if (error.status === 429) {  // Rate limited
        const delay = Math.pow(2, i) * 1000;  // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
}

Implement Proper Error Handling

// Good: Comprehensive error handling
try {
  const response = await api.resource('articles').create({...});
  return response.data;
} catch (error) {
  if (error.status === 401) {
    // Re-authenticate
  } else if (error.status === 403) {
    // Permission denied - log and notify
  } else if (error.status === 422) {
    // Validation error - show to user
  } else if (error.status >= 500) {
    // Server error - retry with backoff
  }
  throw error;
}

❌ DON'T

  • ❌ Make sequential API calls when batch is possible
  • ❌ Ignore rate limit responses
  • ❌ Retry failed requests without delay
  • ❌ Swallow errors silently
  • ❌ Make unnecessary duplicate requests

🔍 Filtering & Querying

✅ DO

Use Specific Filters

# Good: Precise filter
GET /api/articles:list?filter={"status":"published","publishedAt":{"$gte":"2025-01-01"}}

# Bad: Fetch all, filter client-side
GET /api/articles:list

Combine Filters Efficiently

# Good: Single request with complex filter
GET /api/articles:list?filter={"$and":[{"status":"published"},{"$or":[{"featured":true},{"viewCount":{"$gt":1000}}]}]}

❌ DON'T

  • ❌ Use overly complex nested filters
  • ❌ Filter on non-indexed fields for large datasets
  • ❌ Use regex filters unnecessarily ($like with %)
  • ❌ Fetch all data to filter client-side

🏢 Multi-Application

🌐 Application Management

✅ DO

Use Descriptive Application Names

# Good
POST /api/applications:create
{
  "name": "marketing-campaigns",
  "displayName": "Marketing Campaigns Platform"
}

# Bad
POST /api/applications:create
{
  "name": "app1",
  "displayName": "App 1"
}

Plan Application Boundaries - Separate by business domain - Isolate by customer/tenant - Separate dev/staging/prod environments

Document Application Purpose

{
  "name": "client-portal",
  "displayName": "Client Portal",
  "description": "Self-service portal for clients to manage campaigns and view reports",
  "options": {
    "metadata": {
      "owner": "product-team",
      "environment": "production"
    }
  }
}

❌ DON'T

  • ❌ Create applications for every minor feature
  • ❌ Mix unrelated business domains in one app
  • ❌ Share API keys across applications
  • ❌ Delete applications without backups

📊 Monitoring & Logging

📈 Observability

✅ DO

Log API Usage

// Good: Structured logging
logger.info('API call', {
  endpoint: '/api/articles:create',
  app: 'main',
  user: userId,
  duration: 150,
  status: 201
});

Monitor Key Metrics - Request rate (requests/second) - Response time (p50, p95, p99) - Error rate (%) - API key usage per key - Most accessed endpoints

Set Up Alerts - High error rate (>5%) - Slow response times (>500ms p95) - Rate limit hits - Authentication failures spike - API key expiration warnings

❌ DON'T

  • ❌ Log API keys or sensitive data
  • ❌ Ignore error logs
  • ❌ Skip performance monitoring
  • ❌ Wait for users to report issues

🔧 Development Workflow

💻 Development Best Practices

✅ DO

Use Environment-Specific Apps

# Development
X-App: dev-environment

# Staging
X-App: staging-environment

# Production
X-App: main

Version Your Schema

# Document schema changes
# Use migrations for breaking changes
# Keep backwards compatibility when possible

Test Thoroughly

// Good: Comprehensive tests
describe('Articles API', () => {
  test('creates article with valid data', async () => {
    const article = await api.resource('articles').create({
      title: 'Test',
      content: 'Content'
    });
    expect(article.data.id).toBeDefined();
  });

  test('rejects article without required fields', async () => {
    await expect(
      api.resource('articles').create({})
    ).rejects.toThrow();
  });

  test('enforces permissions', async () => {
    // Test with viewer key
    await expect(
      viewerApi.resource('articles').create({...})
    ).rejects.toThrow('No permissions');
  });
});

❌ DON'T

  • ❌ Test directly in production
  • ❌ Skip schema validation
  • ❌ Deploy without testing permissions
  • ❌ Ignore breaking changes

📚 Documentation

📝 Documentation Best Practices

✅ DO

  • ✅ Add descriptions to all collections
  • ✅ Document field purposes
  • ✅ Provide example values
  • ✅ Keep API documentation in sync
  • ✅ Use consistent terminology
  • ✅ Include error handling examples

❌ DON'T

  • ❌ Leave fields undocumented
  • ❌ Use technical jargon without explanation
  • ❌ Skip relationship documentation
  • ❌ Forget to update docs after changes

🎯 Checklist

🚀 Production Readiness

Before going to production:

  • Security
  • APP_KEY set and persisted
  • HTTPS enforced
  • API keys rotated
  • Permissions tested
  • Sensitive fields hidden

  • Performance

  • Indexes on foreign keys
  • Pagination implemented
  • Caching strategy defined
  • Rate limiting configured

  • Monitoring

  • Logging configured
  • Metrics collection enabled
  • Alerts set up
  • Error tracking integrated

  • Documentation

  • API documentation complete
  • Examples provided
  • Error codes documented
  • Changelog maintained

  • Backup & Recovery

  • Backup strategy defined
  • Recovery procedures tested
  • Data retention policy set
  • API Gateway: Core API architecture
  • Authentication: Security implementation
  • Permissions: Access control
  • Troubleshooting: Common issues and solutions

Remember: Following these best practices ensures reliable, secure, and performant API integrations that scale with your needs.