Docs
Best Practices
Best Practices
Essential best practices for using the Twitter API effectively, including performance optimization, error handling, caching strategies, and code patterns.
Overview
This guide covers best practices for building reliable, efficient, and maintainable applications using the Twitter API. Following these practices will help you build better applications and avoid common pitfalls.
Performance Optimization
1. Implement Caching
Cache API responses to reduce requests and improve performance:
class APICache {
constructor(defaultTTL = 5 * 60 * 1000) {
this.cache = new Map();
this.defaultTTL = defaultTTL;
}
get(key) {
const cached = this.cache.get(key);
if (!cached) return null;
if (Date.now() - cached.timestamp > cached.ttl) {
this.cache.delete(key);
return null;
}
return cached.data;
}
set(key, data, ttl = this.defaultTTL) {
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl
});
}
clear() {
this.cache.clear();
}
}
// Usage
const cache = new APICache();
async function getCachedTweet(tweetId) {
const cacheKey = `tweet:${tweetId}`;
const cached = cache.get(cacheKey);
if (cached) return cached;
const response = await fetch(`/tweet/${tweetId}`, {
headers: { 'X-API-KEY': API_KEY }
});
const data = await response.json();
cache.set(cacheKey, data, 5 * 60 * 1000); // 5 minutes
return data;
}2. Use Bulk Endpoints
Prefer bulk endpoints over individual requests:
// ❌ Bad: 100 individual requests
async function getUsersBad(userIds) {
const users = [];
for (const id of userIds) {
const response = await fetch(`/user/${id}`, {
headers: { 'X-API-KEY': API_KEY }
});
users.push(await response.json());
}
return users;
}
// ✅ Good: 1 bulk request
async function getUsersGood(userIds) {
const response = await fetch(`/users/bulk?ids=${userIds.join(',')}`, {
headers: { 'X-API-KEY': API_KEY }
});
return await response.json();
}3. Implement Request Batching
Batch requests to stay within rate limits:
class RequestBatcher {
constructor(batchSize = 10, delay = 100) {
this.batchSize = batchSize;
this.delay = delay;
this.queue = [];
}
async add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.process();
});
}
async process() {
if (this.queue.length === 0) return;
const batch = this.queue.splice(0, this.batchSize);
const results = await Promise.all(
batch.map(({ requestFn, reject }) =>
requestFn().catch(reject)
)
);
batch.forEach(({ resolve }, i) => resolve(results[i]));
if (this.queue.length > 0) {
setTimeout(() => this.process(), this.delay);
}
}
}
// Usage
const batcher = new RequestBatcher(10, 100);
for (let i = 0; i < 100; i++) {
batcher.add(() => fetch(`/tweet/${i}`, {
headers: { 'X-API-KEY': API_KEY }
}));
}4. Optimize Pagination
Use efficient pagination patterns:
async function getAllWithPagination(endpoint, extractData) {
const allItems = [];
let cursor = null;
do {
const url = `${endpoint}?limit=100${cursor ? `&cursor=${cursor}` : ''}`;
const response = await fetch(url, {
headers: { 'X-API-KEY': API_KEY }
});
const data = await response.json();
allItems.push(...extractData(data));
cursor = data.cursor?.bottom || null;
// Respect rate limits
await new Promise(resolve => setTimeout(resolve, 100));
} while (cursor);
return allItems;
}Error Handling
1. Comprehensive Error Handling
class APIError extends Error {
constructor(code, message, details) {
super(message);
this.code = code;
this.details = details;
this.name = 'APIError';
}
}
async function handleRequest(url, options) {
try {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json().catch(() => ({
code: response.status,
message: response.statusText
}));
throw new APIError(
error.code || response.status,
error.message || response.statusText,
error.details
);
}
return await response.json();
} catch (error) {
if (error instanceof APIError) {
// Handle API errors
console.error(`API Error ${error.code}: ${error.message}`);
throw error;
}
// Handle network errors
console.error('Network error:', error);
throw new Error('Network request failed');
}
}2. Retry Logic with Exponential Backoff
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return await response.json();
}
// Don't retry client errors (4xx)
if (response.status >= 400 && response.status < 500) {
throw new Error(`Client error: ${response.status}`);
}
// Retry server errors (5xx) and rate limits (429)
if (response.status >= 500 || response.status === 429) {
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
}
}Code Organization
1. Create API Client Class
class TwitterAPIClient {
constructor(apiKey, baseUrl) {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.cache = new Map();
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
'X-API-KEY': this.apiKey,
...options.headers
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
}
async getTweet(tweetId) {
return this.request(`/tweet/${tweetId}`);
}
async getUser(userId) {
return this.request(`/user/${userId}`);
}
async searchTweets(query, filters = {}) {
const params = new URLSearchParams({
q: query,
...filters
});
return this.request(`/search/tweets?${params}`);
}
}
// Usage
const client = new TwitterAPIClient(API_KEY, 'https://<direct.gateway>');
const tweet = await client.getTweet('123456789');2. Use TypeScript for Type Safety
interface Tweet {
id: string;
text: string;
author: {
id: string;
username: string;
name: string;
};
created_at: string;
metrics: {
like_count: number;
retweet_count: number;
};
}
interface APIResponse<T> {
data: T;
meta?: {
result_count?: number;
next_cursor?: string;
};
}
class TwitterAPIClient {
async getTweet(tweetId: string): Promise<Tweet> {
const response = await this.request<APIResponse<Tweet>>(`/tweet/${tweetId}`);
return response.data;
}
}Security
1. Secure API Key Storage
// ✅ Good: Environment variables
const API_KEY = process.env.API_KEY;
// ❌ Bad: Hardcoded
const API_KEY = 'sk_live_1234567890';2. Validate Input
function validateTweetId(tweetId) {
if (!tweetId) {
throw new Error('Tweet ID is required');
}
if (!/^\d+$/.test(tweetId)) {
throw new Error('Invalid tweet ID format');
}
return true;
}
function validateSearchQuery(query) {
if (!query || query.trim().length === 0) {
throw new Error('Search query cannot be empty');
}
if (query.length > 500) {
throw new Error('Search query too long');
}
return true;
}3. Sanitize User Input
function sanitizeInput(input) {
return input
.trim()
.replace(/[<>]/g, '') // Remove potential HTML
.slice(0, 500); // Limit length
}Monitoring and Logging
1. Log API Requests
class LoggingClient {
constructor(client) {
this.client = client;
}
async request(endpoint, options) {
const startTime = Date.now();
try {
const result = await this.client.request(endpoint, options);
const duration = Date.now() - startTime;
console.log('API Request:', {
endpoint,
status: 'success',
duration: `${duration}ms`,
timestamp: new Date().toISOString()
});
return result;
} catch (error) {
const duration = Date.now() - startTime;
console.error('API Request Failed:', {
endpoint,
status: 'error',
error: error.message,
duration: `${duration}ms`,
timestamp: new Date().toISOString()
});
throw error;
}
}
}2. Monitor Rate Limits
function monitorRateLimits(response) {
const limit = parseInt(response.headers.get('X-RateLimit-Limit') || '0');
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
const percentage = ((limit - remaining) / limit) * 100;
if (percentage > 90) {
console.warn('⚠️ Rate limit critical:', { limit, remaining, percentage });
} else if (percentage > 75) {
console.warn('⚠️ Rate limit warning:', { limit, remaining, percentage });
}
return { limit, remaining, percentage };
}Testing
1. Mock API Responses
// Mock for testing
class MockAPIClient {
constructor() {
this.responses = new Map();
}
setResponse(endpoint, response) {
this.responses.set(endpoint, response);
}
async request(endpoint) {
const response = this.responses.get(endpoint);
if (!response) {
throw new Error(`No mock response for ${endpoint}`);
}
return response;
}
}
// Usage in tests
const mockClient = new MockAPIClient();
mockClient.setResponse('/tweet/123', {
id: '123',
text: 'Test tweet'
});
const tweet = await mockClient.request('/tweet/123');2. Test Error Scenarios
describe('API Client', () => {
it('handles 404 errors', async () => {
const response = { status: 404, json: async () => ({ error: 'Not found' }) };
fetch.mockResolvedValueOnce(response);
await expect(client.getTweet('invalid')).rejects.toThrow();
});
it('handles rate limits', async () => {
const response = {
status: 429,
headers: { get: () => '60' },
json: async () => ({ error: 'Rate limited' })
};
fetch.mockResolvedValueOnce(response);
// Should retry after delay
await expect(client.getTweet('123')).rejects.toThrow();
});
});Common Pitfalls to Avoid
1. Not Handling Errors
// ❌ Bad
const tweet = await fetch(`/tweet/${id}`).then(r => r.json());
// ✅ Good
try {
const response = await fetch(`/tweet/${id}`, {
headers: { 'X-API-KEY': API_KEY }
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const tweet = await response.json();
} catch (error) {
console.error('Failed to fetch tweet:', error);
}2. Ignoring Rate Limits
// ❌ Bad: No rate limit handling
for (let i = 0; i < 1000; i++) {
await fetch(`/tweet/${i}`, { headers: { 'X-API-KEY': API_KEY } });
}
// ✅ Good: Respect rate limits
const queue = new RequestQueue(5); // 5 per second
for (let i = 0; i < 1000; i++) {
queue.add(() => fetch(`/tweet/${i}`, { headers: { 'X-API-KEY': API_KEY } }));
}3. Not Caching Responses
// ❌ Bad: Request same data multiple times
function getTweet(id) {
return fetch(`/tweet/${id}`, { headers: { 'X-API-KEY': API_KEY } })
.then(r => r.json());
}
// ✅ Good: Cache responses
const cache = new Map();
function getTweet(id) {
if (cache.has(id)) return cache.get(id);
const tweet = await fetch(`/tweet/${id}`, { headers: { 'X-API-KEY': API_KEY } })
.then(r => r.json());
cache.set(id, tweet);
return tweet;
}Related Documentation
- Error Handling Guide - Comprehensive error handling
- Rate Limits Guide - Rate limit management
- Authentication Guide - Authentication best practices
