💡 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
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
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
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
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_KEYset 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
🔗 Related Documentation¶
- 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.