API Guides
Best Practices
Follow these best practices to build production-ready applications that are reliable, performant, and scalable when using API Codex APIs.
Architecture Patterns
1. API Gateway Pattern
Implement an API gateway to centralize API calls and add cross-cutting concerns:
Code
class APIGateway { constructor(config) { this.baseURL = config.baseURL; this.apiKey = config.apiKey; this.timeout = config.timeout || 30000; this.retryConfig = config.retry || { maxRetries: 3, delay: 1000 }; // Initialize components this.rateLimiter = new RateLimiter(config.rateLimit); this.cache = new CacheManager(config.cache); this.metrics = new MetricsCollector(); this.circuitBreaker = new CircuitBreaker(); } async request(service, endpoint, options = {}) { const startTime = Date.now(); const cacheKey = this.getCacheKey(service, endpoint, options); try { // Check cache first const cached = await this.cache.get(cacheKey); if (cached) { this.metrics.recordCacheHit(service); return cached; } // Check circuit breaker if (!this.circuitBreaker.isAvailable(service)) { throw new Error(`Service ${service} is unavailable`); } // Apply rate limiting await this.rateLimiter.acquire(service); // Make request with retry logic const response = await this.executeWithRetry( () => this.makeRequest(service, endpoint, options) ); // Cache successful response await this.cache.set(cacheKey, response, options.cacheTTL); // Record metrics this.metrics.recordSuccess(service, Date.now() - startTime); return response; } catch (error) { this.metrics.recordError(service, error); this.circuitBreaker.recordFailure(service); throw error; } } async makeRequest(service, endpoint, options) { const url = `${this.baseURL[service]}${endpoint}`; const response = await fetch(url, { method: options.method || 'GET', headers: { 'x-rapidapi-key': this.apiKey, 'x-rapidapi-host': this.getHost(service), ...options.headers }, body: options.body, signal: AbortSignal.timeout(this.timeout) }); if (!response.ok) { throw new APIError(response.status, await response.text()); } return response.json(); } getHost(service) { const hosts = { dns: 'advanced-dns-lookup-api.p.rapidapi.com', email: 'email-intelligence-api.p.rapidapi.com', text: 'text-analysis-api.p.rapidapi.com' }; return hosts[service]; } getCacheKey(service, endpoint, options) { return `${service}:${endpoint}:${JSON.stringify(options.params || {})}`; } } // Usage const gateway = new APIGateway({ apiKey: process.env.RAPIDAPI_KEY, baseURL: { dns: 'https://advanced-dns-lookup-api.p.rapidapi.com', email: 'https://email-intelligence-api.p.rapidapi.com' }, rateLimit: { requestsPerSecond: 10 }, cache: { ttl: 3600000, maxSize: 1000 } }); const dnsResult = await gateway.request('dns', '/v1/check', { params: { name: 'example.com' }, cacheTTL: 86400000 // Cache DNS for 24 hours });
2. Repository Pattern
Abstract API calls behind a repository interface:
Code
class DNSRepository { constructor(apiClient) { this.apiClient = apiClient; this.cache = new Map(); } async lookupDomain(domain, type = 'A') { const cacheKey = `${domain}:${type}`; // Check memory cache if (this.cache.has(cacheKey)) { const cached = this.cache.get(cacheKey); if (Date.now() - cached.timestamp < cached.ttl * 1000) { return cached.data; } } // Fetch from API const response = await this.apiClient.get('/v1/check', { params: { name: domain, type } }); // Cache with TTL from response const minTTL = this.extractMinTTL(response.records); this.cache.set(cacheKey, { data: response, timestamp: Date.now(), ttl: minTTL }); return response; } async reverseLookup(ip) { return this.apiClient.get('/v1/check', { params: { ip } }); } extractMinTTL(records) { let minTTL = Infinity; Object.values(records || {}).forEach(recordArray => { recordArray.forEach(record => { if (record.TTL < minTTL) { minTTL = record.TTL; } }); }); return minTTL === Infinity ? 3600 : minTTL; } } // Usage with dependency injection const apiClient = new APIClient(config); const dnsRepo = new DNSRepository(apiClient); const records = await dnsRepo.lookupDomain('example.com');
Performance Optimization
1. Connection Pooling
Reuse connections for better performance:
Code
const https = require('https'); const http = require('http'); class ConnectionPool { constructor(options = {}) { this.maxSockets = options.maxSockets || 50; this.maxFreeSockets = options.maxFreeSockets || 10; this.timeout = options.timeout || 60000; this.keepAlive = options.keepAlive !== false; // Create custom agents this.httpsAgent = new https.Agent({ maxSockets: this.maxSockets, maxFreeSockets: this.maxFreeSockets, timeout: this.timeout, keepAlive: this.keepAlive, keepAliveMsecs: 1000 }); this.httpAgent = new http.Agent({ maxSockets: this.maxSockets, maxFreeSockets: this.maxFreeSockets, timeout: this.timeout, keepAlive: this.keepAlive, keepAliveMsecs: 1000 }); } getAgent(url) { return url.startsWith('https') ? this.httpsAgent : this.httpAgent; } async request(url, options = {}) { const agent = this.getAgent(url); return fetch(url, { ...options, agent }); } destroy() { this.httpsAgent.destroy(); this.httpAgent.destroy(); } getStats() { return { https: { sockets: Object.keys(this.httpsAgent.sockets).length, freeSockets: Object.keys(this.httpsAgent.freeSockets).length, requests: Object.keys(this.httpsAgent.requests).length }, http: { sockets: Object.keys(this.httpAgent.sockets).length, freeSockets: Object.keys(this.httpAgent.freeSockets).length, requests: Object.keys(this.httpAgent.requests).length } }; } } // Global connection pool const connectionPool = new ConnectionPool({ maxSockets: 100, maxFreeSockets: 20 }); // Use for all API requests async function makeAPIRequest(url, options) { return connectionPool.request(url, { ...options, headers: { 'x-rapidapi-key': process.env.RAPIDAPI_KEY, ...options.headers } }); }
2. Smart Caching Strategy
Implement intelligent caching with different strategies:
Code
class SmartCache { constructor() { this.stores = { memory: new MemoryCache(), redis: new RedisCache(), disk: new DiskCache() }; this.strategies = { dns: { store: 'redis', ttl: 86400000 }, // 24 hours email: { store: 'memory', ttl: 3600000 }, // 1 hour text: { store: 'memory', ttl: 1800000 } // 30 minutes }; } async get(category, key) { const strategy = this.strategies[category]; const store = this.stores[strategy.store]; const cached = await store.get(key); if (cached) { // Check if still valid if (Date.now() - cached.timestamp < strategy.ttl) { // Update access time for LRU await store.touch(key); return cached.data; } // Expired - delete it await store.delete(key); } return null; } async set(category, key, data) { const strategy = this.strategies[category]; const store = this.stores[strategy.store]; await store.set(key, { data, timestamp: Date.now(), category }, strategy.ttl); // Implement cache warming for critical data if (strategy.warm) { this.warmCache(category, key, data); } } async warmCache(category, key, data) { // Pre-populate other cache levels if (category === 'dns') { // Also store in memory for fast access await this.stores.memory.set(key, { data, timestamp: Date.now() }, 300000); // 5 minutes in memory } } async invalidate(pattern) { // Invalidate across all stores await Promise.all( Object.values(this.stores).map(store => store.invalidate(pattern) ) ); } } class MemoryCache { constructor(maxSize = 1000) { this.cache = new Map(); this.maxSize = maxSize; this.accessOrder = []; } async get(key) { const value = this.cache.get(key); if (value) { // Move to end (most recently used) this.updateAccessOrder(key); } return value; } async set(key, value, ttl) { // Implement LRU eviction if (this.cache.size >= this.maxSize) { const lru = this.accessOrder.shift(); this.cache.delete(lru); } this.cache.set(key, value); this.accessOrder.push(key); } updateAccessOrder(key) { const index = this.accessOrder.indexOf(key); if (index > -1) { this.accessOrder.splice(index, 1); } this.accessOrder.push(key); } }
3. Batch Processing
Optimize multiple operations:
Code
class BatchProcessor { constructor(options = {}) { this.batchSize = options.batchSize || 10; this.batchTimeout = options.batchTimeout || 100; this.queue = []; this.processing = false; this.timer = null; } async add(item) { return new Promise((resolve, reject) => { this.queue.push({ item, resolve, reject, timestamp: Date.now() }); this.scheduleBatch(); }); } scheduleBatch() { // Clear existing timer if (this.timer) { clearTimeout(this.timer); } // Process immediately if batch is full if (this.queue.length >= this.batchSize) { this.processBatch(); return; } // Schedule batch processing this.timer = setTimeout(() => { this.processBatch(); }, this.batchTimeout); } async processBatch() { if (this.processing || this.queue.length === 0) { return; } this.processing = true; // Take items for this batch const batch = this.queue.splice(0, this.batchSize); try { // Process batch const results = await this.executeBatch(batch.map(b => b.item)); // Resolve promises batch.forEach((item, index) => { item.resolve(results[index]); }); } catch (error) { // Reject all promises in batch batch.forEach(item => { item.reject(error); }); } finally { this.processing = false; // Process next batch if queue has items if (this.queue.length > 0) { this.scheduleBatch(); } } } async executeBatch(items) { // Implement your batch processing logic // This example shows parallel processing return Promise.all( items.map(item => this.processItem(item)) ); } async processItem(item) { // Individual item processing return fetch(item.url, item.options); } } // Usage const batchProcessor = new BatchProcessor({ batchSize: 10, batchTimeout: 100 }); // Add items to batch const results = await Promise.all([ batchProcessor.add({ url: '/api/1', options: {} }), batchProcessor.add({ url: '/api/2', options: {} }), batchProcessor.add({ url: '/api/3', options: {} }) ]);
Security Best Practices
1. Secure Configuration Management
Code
class SecureConfig { constructor() { this.config = this.loadConfig(); this.validateConfig(); } loadConfig() { // Priority: Environment > Secrets Manager > Config File return { apiKey: this.getSecret('RAPIDAPI_KEY'), apiHost: process.env.API_HOST, environment: process.env.NODE_ENV || 'development', security: { enableHTTPS: process.env.ENABLE_HTTPS !== 'false', validateSSL: process.env.VALIDATE_SSL !== 'false', allowedOrigins: this.parseList(process.env.ALLOWED_ORIGINS), rateLimitBypass: this.getSecret('RATE_LIMIT_BYPASS_TOKEN') } }; } getSecret(key) { // Try environment variable first if (process.env[key]) { return process.env[key]; } // Try secrets manager (AWS example) if (process.env.USE_AWS_SECRETS === 'true') { return this.getAWSSecret(key); } // Try key vault (Azure example) if (process.env.USE_AZURE_KEYVAULT === 'true') { return this.getAzureSecret(key); } throw new Error(`Required secret ${key} not found`); } validateConfig() { const required = ['apiKey', 'apiHost']; for (const field of required) { if (!this.config[field]) { throw new Error(`Missing required configuration: ${field}`); } } // Validate API key format if (!/^[a-zA-Z0-9]{20,}$/.test(this.config.apiKey)) { throw new Error('Invalid API key format'); } } parseList(value) { if (!value) return []; return value.split(',').map(item => item.trim()); } get(path) { const keys = path.split('.'); let value = this.config; for (const key of keys) { value = value[key]; if (value === undefined) { return undefined; } } return value; } } // Singleton instance const config = new SecureConfig(); export default config;
2. Request Validation & Sanitization
Code
class RequestValidator { constructor() { this.validators = { email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, domain: /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i, ip: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, url: /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/ }; } validate(type, value) { const validator = this.validators[type]; if (!validator) { throw new Error(`Unknown validation type: ${type}`); } if (!validator.test(value)) { throw new ValidationError(`Invalid ${type}: ${value}`); } return true; } sanitize(input) { if (typeof input === 'string') { // Remove potential XSS vectors return input .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '') .replace(/javascript:/gi, '') .replace(/on\w+\s*=/gi, '') .trim(); } if (typeof input === 'object' && input !== null) { const sanitized = {}; for (const [key, value] of Object.entries(input)) { sanitized[this.sanitize(key)] = this.sanitize(value); } return sanitized; } return input; } validateRequest(schema, data) { const errors = []; // Check required fields for (const field of schema.required || []) { if (!data[field]) { errors.push(`Missing required field: ${field}`); } } // Validate field types and formats for (const [field, rules] of Object.entries(schema.fields || {})) { const value = data[field]; if (value === undefined && !rules.required) { continue; } // Type validation if (rules.type && typeof value !== rules.type) { errors.push(`Invalid type for ${field}: expected ${rules.type}`); } // Format validation if (rules.format && !this.validators[rules.format].test(value)) { errors.push(`Invalid format for ${field}`); } // Length validation if (rules.minLength && value.length < rules.minLength) { errors.push(`${field} must be at least ${rules.minLength} characters`); } if (rules.maxLength && value.length > rules.maxLength) { errors.push(`${field} must be at most ${rules.maxLength} characters`); } } if (errors.length > 0) { throw new ValidationError(errors.join(', ')); } return true; } } class ValidationError extends Error { constructor(message) { super(message); this.name = 'ValidationError'; } }
Monitoring & Observability
1. Comprehensive Metrics Collection
Code
class MetricsCollector { constructor() { this.metrics = { requests: { total: 0, success: 0, error: 0, byEndpoint: {}, byStatus: {} }, performance: { responseTimes: [], averageResponseTime: 0, p95ResponseTime: 0, p99ResponseTime: 0 }, cache: { hits: 0, misses: 0, hitRate: 0 }, errors: { byType: {}, recent: [] } }; this.startTime = Date.now(); } recordRequest(endpoint, status, responseTime) { this.metrics.requests.total++; if (status >= 200 && status < 300) { this.metrics.requests.success++; } else { this.metrics.requests.error++; } // By endpoint if (!this.metrics.requests.byEndpoint[endpoint]) { this.metrics.requests.byEndpoint[endpoint] = { total: 0, success: 0, error: 0 }; } this.metrics.requests.byEndpoint[endpoint].total++; // By status this.metrics.requests.byStatus[status] = (this.metrics.requests.byStatus[status] || 0) + 1; // Performance this.metrics.performance.responseTimes.push(responseTime); this.calculatePercentiles(); } calculatePercentiles() { const times = this.metrics.performance.responseTimes.slice().sort((a, b) => a - b); const len = times.length; if (len === 0) return; this.metrics.performance.averageResponseTime = times.reduce((a, b) => a + b, 0) / len; this.metrics.performance.p95ResponseTime = times[Math.floor(len * 0.95)]; this.metrics.performance.p99ResponseTime = times[Math.floor(len * 0.99)]; // Keep only last 1000 measurements if (this.metrics.performance.responseTimes.length > 1000) { this.metrics.performance.responseTimes = this.metrics.performance.responseTimes.slice(-1000); } } recordCacheHit() { this.metrics.cache.hits++; this.updateCacheHitRate(); } recordCacheMiss() { this.metrics.cache.misses++; this.updateCacheHitRate(); } updateCacheHitRate() { const total = this.metrics.cache.hits + this.metrics.cache.misses; this.metrics.cache.hitRate = total > 0 ? (this.metrics.cache.hits / total) * 100 : 0; } recordError(error) { const errorType = error.name || 'Unknown'; this.metrics.errors.byType[errorType] = (this.metrics.errors.byType[errorType] || 0) + 1; this.metrics.errors.recent.push({ type: errorType, message: error.message, timestamp: Date.now() }); // Keep only last 100 errors if (this.metrics.errors.recent.length > 100) { this.metrics.errors.recent.shift(); } } getReport() { const uptime = Date.now() - this.startTime; const requestsPerSecond = this.metrics.requests.total / (uptime / 1000); return { uptime: Math.floor(uptime / 1000), requestsPerSecond: requestsPerSecond.toFixed(2), ...this.metrics, health: this.calculateHealth() }; } calculateHealth() { const errorRate = this.metrics.requests.total > 0 ? (this.metrics.requests.error / this.metrics.requests.total) * 100 : 0; if (errorRate > 10) return 'critical'; if (errorRate > 5) return 'warning'; return 'healthy'; } export() { // Export metrics in Prometheus format return ` # HELP api_requests_total Total number of API requests # TYPE api_requests_total counter api_requests_total ${this.metrics.requests.total} # HELP api_requests_success_total Total number of successful API requests # TYPE api_requests_success_total counter api_requests_success_total ${this.metrics.requests.success} # HELP api_requests_error_total Total number of failed API requests # TYPE api_requests_error_total counter api_requests_error_total ${this.metrics.requests.error} # HELP api_response_time_seconds API response time in seconds # TYPE api_response_time_seconds summary api_response_time_seconds{quantile="0.5"} ${this.metrics.performance.averageResponseTime / 1000} api_response_time_seconds{quantile="0.95"} ${this.metrics.performance.p95ResponseTime / 1000} api_response_time_seconds{quantile="0.99"} ${this.metrics.performance.p99ResponseTime / 1000} # HELP cache_hit_rate Cache hit rate percentage # TYPE cache_hit_rate gauge cache_hit_rate ${this.metrics.cache.hitRate} `.trim(); } } // Global metrics instance const metrics = new MetricsCollector(); // Expose metrics endpoint app.get('/metrics', (req, res) => { res.type('text/plain'); res.send(metrics.export()); });
2. Structured Logging
Code
class Logger { constructor(options = {}) { this.level = options.level || 'info'; this.service = options.service || 'api-codex'; this.version = options.version || '1.0.0'; } log(level, message, data = {}) { if (!this.shouldLog(level)) return; const entry = { timestamp: new Date().toISOString(), level, service: this.service, version: this.version, message, ...this.enrichData(data) }; // Output as JSON for log aggregation console.log(JSON.stringify(entry)); // Send to logging service if (process.env.LOG_SERVICE_URL) { this.sendToLoggingService(entry); } } enrichData(data) { return { ...data, environment: process.env.NODE_ENV, hostname: require('os').hostname(), pid: process.pid, requestId: data.requestId || this.generateRequestId() }; } generateRequestId() { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } shouldLog(level) { const levels = ['debug', 'info', 'warn', 'error']; return levels.indexOf(level) >= levels.indexOf(this.level); } debug(message, data) { this.log('debug', message, data); } info(message, data) { this.log('info', message, data); } warn(message, data) { this.log('warn', message, data); } error(message, data) { this.log('error', message, data); } async sendToLoggingService(entry) { try { await fetch(process.env.LOG_SERVICE_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(entry) }); } catch (error) { console.error('Failed to send log to service:', error); } } } // Usage with request context const logger = new Logger({ level: 'info' }); async function handleAPIRequest(req, res) { const requestId = req.headers['x-request-id'] || logger.generateRequestId(); const startTime = Date.now(); logger.info('API request started', { requestId, method: req.method, path: req.path, ip: req.ip }); try { const result = await processRequest(req); logger.info('API request completed', { requestId, status: 200, duration: Date.now() - startTime }); res.json(result); } catch (error) { logger.error('API request failed', { requestId, error: error.message, stack: error.stack, duration: Date.now() - startTime }); res.status(500).json({ error: 'Internal server error' }); } }
Production Checklist
Pre-Deployment ✅
- Environment variables configured securely
- API keys stored in secret management service
- Error handling implemented comprehensively
- Rate limiting configured appropriately
- Caching strategy implemented
- Connection pooling configured
- Monitoring and alerting set up
- Logging configured with appropriate levels
- Health checks endpoint implemented
- Graceful shutdown handling added
- Security headers configured
- Input validation implemented
- Circuit breakers configured
- Retry logic with exponential backoff
- Timeout values set appropriately
Deployment
Code
// Graceful shutdown handler class GracefulShutdown { constructor(server) { this.server = server; this.isShuttingDown = false; this.connections = new Set(); // Track connections server.on('connection', (connection) => { this.connections.add(connection); connection.on('close', () => { this.connections.delete(connection); }); }); // Handle shutdown signals process.on('SIGTERM', () => this.shutdown()); process.on('SIGINT', () => this.shutdown()); } async shutdown() { if (this.isShuttingDown) return; this.isShuttingDown = true; console.log('Graceful shutdown initiated...'); // Stop accepting new requests this.server.close(() => { console.log('Server closed'); }); // Close existing connections for (const connection of this.connections) { connection.end(); } // Wait for connections to close await this.waitForConnections(); // Cleanup resources await this.cleanup(); console.log('Graceful shutdown complete'); process.exit(0); } async waitForConnections() { const maxWait = 30000; // 30 seconds const startTime = Date.now(); while (this.connections.size > 0) { if (Date.now() - startTime > maxWait) { console.warn('Forcing connections to close'); for (const connection of this.connections) { connection.destroy(); } break; } await new Promise(resolve => setTimeout(resolve, 100)); } } async cleanup() { // Close database connections // Flush caches // Save state // Send final metrics } } // Health check endpoint app.get('/health', (req, res) => { const health = { status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), memory: process.memoryUsage(), checks: { api: checkAPIHealth(), cache: checkCacheHealth(), database: checkDatabaseHealth() } }; const isHealthy = Object.values(health.checks).every(check => check.status === 'ok'); res.status(isHealthy ? 200 : 503).json(health); });
Next Steps
- Review Authentication for secure API access
- Understand Rate Limiting for optimal usage
- Master Error Handling for reliability
- Explore our API Catalog to start building
Conclusion
Following these best practices will help you build robust, scalable, and maintainable applications with API Codex APIs. Remember to:
- Plan your architecture before implementation
- Implement proper error handling from the start
- Monitor and measure everything
- Optimize gradually based on metrics
- Keep security as a top priority
For additional support and updates, follow our documentation and join the API Codex community.
Last modified on