| name | cloudflare-api-orchestration |
| description | Activates automatically when designing or implementing Cloudflare Workers APIs, orchestrating multi-provider API calls, implementing D1 database operations, or managing KV-based caching strategies. Ensures proper error handling, provider tagging, fallback chains, and orchestration patterns for BooksTrack backend infrastructure. Auto-activates on: - File patterns: *.worker.js, *-api.ts, *-service.ts in backend code - Keywords: "api orchestration", "multi-provider", "fallback chain", "D1 query" - Context: Cloudflare Workers development, API design discussions |
| allowed-tools | Read, Grep, Edit, Write, WebFetch |
Cloudflare API Orchestration Skill
This skill enforces BooksTrack-specific API orchestration patterns for Cloudflare Workers.
Core Principles
1. Provider Orchestration (MANDATORY)
NEVER make direct API calls without orchestration layer.
Correct pattern:
// orchestrator.js
export class APIOrchestrator {
async searchBooks(query, options = {}) {
const providers = [
{ name: 'google', fn: () => this.googleBooks.search(query) },
{ name: 'openlibrary', fn: () => this.openLibrary.search(query) }
];
for (const provider of providers) {
try {
const result = await provider.fn();
return {
...result,
provider: `orchestrated:${providers.map(p => p.name).join('+')}`,
cached: false
};
} catch (error) {
console.warn(`Provider ${provider.name} failed:`, error);
continue;
}
}
// Final fallback to cache
const cached = await this.kv.get(`book:${query}`);
if (cached) {
return {
...JSON.parse(cached),
provider: 'cache:kv',
cached: true
};
}
throw new Error('All providers failed');
}
}
Wrong pattern (FORBIDDEN):
// ❌ Direct API call without orchestration
async function searchBooks(query) {
const response = await fetch(`https://www.googleapis.com/books/v1/volumes?q=${query}`);
return response.json(); // Missing provider tag, no fallback, no caching
}
2. Provider Tagging (REQUIRED)
All responses MUST include provider metadata:
{
data: { /* actual response */ },
provider: "orchestrated:google+openlibrary", // Multi-provider
cached: false,
timestamp: Date.now()
}
Tag formats:
"orchestrated:google+openlibrary"- Successful multi-provider aggregation"google"- Single provider (fallback failed or only one available)"cache:kv"- Retrieved from KV cache"cache:d1"- Retrieved from D1 database
3. Error Handling Patterns
Implement graceful degradation:
try {
// Primary provider
const result = await primaryProvider.fetch();
return { data: result, provider: 'primary', cached: false };
} catch (primaryError) {
console.warn('Primary provider failed:', primaryError);
try {
// Secondary provider
const result = await secondaryProvider.fetch();
return { data: result, provider: 'secondary', cached: false };
} catch (secondaryError) {
console.warn('Secondary provider failed:', secondaryError);
// Cache fallback
const cached = await cache.get(key);
if (cached) {
return { data: cached, provider: 'cache:kv', cached: true };
}
// Final error with context
throw new APIError('All providers exhausted', {
primaryError,
secondaryError,
key
});
}
}
4. D1 Query Patterns
Always use prepared statements:
// ✅ Correct - prepared statement
const result = await env.DB.prepare(
'SELECT * FROM books WHERE isbn = ?'
).bind(isbn).first();
// ❌ Wrong - SQL injection vulnerability
const result = await env.DB.prepare(
`SELECT * FROM books WHERE isbn = '${isbn}'`
).first();
Batch operations for efficiency:
// ✅ Correct - batch insert
const stmt = env.DB.prepare('INSERT INTO books (isbn, title) VALUES (?, ?)');
const batch = books.map(book => stmt.bind(book.isbn, book.title));
await env.DB.batch(batch);
// ❌ Wrong - individual inserts (slow)
for (const book of books) {
await env.DB.prepare('INSERT INTO books (isbn, title) VALUES (?, ?)')
.bind(book.isbn, book.title)
.run();
}
5. KV Caching Strategy
Key naming convention:
namespace:entity:id
Examples:
book:isbn:9780134685991search:query:swift+programminguser:profile:user123
Caching pattern:
async function getCachedOrFetch(key, fetchFn, ttl = 3600) {
// Try cache first
const cached = await env.KV.get(key, { type: 'json' });
if (cached) {
return { ...cached, cached: true };
}
// Fetch from provider
const fresh = await fetchFn();
// Cache for TTL seconds
await env.KV.put(key, JSON.stringify(fresh), {
expirationTtl: ttl,
metadata: { cached_at: Date.now() }
});
return { ...fresh, cached: false };
}
6. Rate Limiting
Implement per-endpoint rate limits:
import { RateLimiter } from './rate-limiter';
const limiter = new RateLimiter({
search: { requests: 100, window: 60 }, // 100 req/min
details: { requests: 200, window: 60 } // 200 req/min
});
async function handleSearch(request, env) {
const clientId = request.headers.get('CF-Connecting-IP');
if (!await limiter.check('search', clientId)) {
return new Response('Rate limit exceeded', {
status: 429,
headers: {
'Retry-After': '60',
'X-RateLimit-Limit': '100',
'X-RateLimit-Remaining': '0'
}
});
}
// Process request...
}
7. Circuit Breaker Pattern
Prevent cascading failures:
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async call(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
}
}
BooksTrack-Specific Patterns
Book Search API
// Route: GET /api/v2/books/search?q=...
export async function handleBookSearch(request, env) {
const url = new URL(request.url);
const query = url.searchParams.get('q');
if (!query) {
return new Response(JSON.stringify({ error: 'Missing query parameter' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
const orchestrator = new APIOrchestrator(env);
try {
const result = await orchestrator.searchBooks(query);
return new Response(JSON.stringify(result), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=3600',
'X-Provider': result.provider
}
});
} catch (error) {
console.error('Search failed:', error);
return new Response(JSON.stringify({
error: 'Search failed',
message: error.message
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
Book Details API
// Route: GET /api/v2/books/:isbn
export async function handleBookDetails(isbn, env) {
// Try D1 first (authoritative source)
const cached = await env.DB.prepare(
'SELECT * FROM books WHERE isbn = ?'
).bind(isbn).first();
if (cached) {
return {
...cached,
provider: 'cache:d1',
cached: true
};
}
// Fetch from providers
const orchestrator = new APIOrchestrator(env);
const result = await orchestrator.getBookByISBN(isbn);
// Store in D1 for future requests
await env.DB.prepare(
'INSERT OR REPLACE INTO books (isbn, title, author, data) VALUES (?, ?, ?, ?)'
).bind(isbn, result.title, result.author, JSON.stringify(result)).run();
return result;
}
Anti-Patterns to Avoid
❌ Direct API Calls
// DON'T DO THIS
const response = await fetch('https://api.example.com/books');
❌ Missing Provider Tags
// DON'T DO THIS
return { data: books }; // Missing provider metadata
❌ Unhandled Errors
// DON'T DO THIS
const result = await provider.fetch(); // No try-catch
❌ SQL Injection Vulnerabilities
// DON'T DO THIS
await env.DB.prepare(`SELECT * FROM books WHERE title = '${title}'`).first();
❌ No Caching Strategy
// DON'T DO THIS - always hitting live APIs
const result = await provider.fetch(); // No cache check
Validation Checklist
When implementing or reviewing Cloudflare Workers APIs, verify:
- All API calls go through orchestration layer
- Responses include provider tags
- Fallback chains implemented (primary → secondary → cache)
- D1 queries use prepared statements
- KV keys follow naming convention
- Rate limiting configured for public endpoints
- Circuit breakers protect external services
- Error handling provides useful context
- Caching strategy reduces API calls
- Logging captures provider failures
Integration with Code Review
This skill complements the code-review-grok subagent. When code review identifies orchestration violations, this skill provides the correct patterns to follow.
Last Updated: November 23, 2025 Maintained by: Claude Code PM System Related Agents: cloudflare-specialist, code-review-grok