API Best Practices
Guidelines for building robust and efficient integrations with the SoshiaConnect API.
Authentication
Secure Credential Storage
✅ Do:
// Store in environment variables
const apiKey = process.env.SOSHIA_API_KEY;
const apiSecret = process.env.SOSHIA_API_SECRET;
❌ Don't:
// Hard-code credentials
const apiKey = "sk_live_abc123..."; // Never do this!
Token Management
Implement Token Refresh:
async function makeAuthenticatedRequest(url, options) {
try {
return await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${accessToken}`
}
});
} catch (error) {
if (error.status === 401) {
// Token expired, refresh it
accessToken = await refreshToken();
return makeAuthenticatedRequest(url, options);
}
throw error;
}
}
Error Handling
Implement Retry Logic
async function apiCallWithRetry(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)
if (i < maxRetries - 1) {
await sleep(Math.pow(2, i) * 1000); // Exponential backoff
continue;
}
} catch (error) {
if (i === maxRetries - 1) throw error;
await sleep(Math.pow(2, i) * 1000);
}
}
}
Handle Rate Limits
async function handleRateLimiting(response) {
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await sleep(retryAfter * 1000);
return true; // Indicates should retry
}
return false;
}
Performance Optimization
1. Use Caching
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function getCachedData(key, fetchFunction) {
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const data = await fetchFunction();
cache.set(key, { data, timestamp: Date.now() });
return data;
}
// Usage
const routes = await getCachedData('routes', () =>
fetch('/api/route-access/all-routes')
);
2. Batch Requests
// Instead of multiple single requests
const routePromises = routeIds.map(id =>
fetch(`/api/route-access/routes-data?id=${id}`)
);
const routes = await Promise.all(routePromises);
3. Request Only What You Need
// ✅ Good - Specific parameters
GET /api/route-access/routes-data?id=123&fields=name,origin,destination
// ❌ Bad - Fetching unnecessary data
GET /api/route-access/all-routes // Returns all routes
Data Management
Handle Pagination
async function fetchAllPages(baseUrl) {
let allData = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${baseUrl}?page=${page}&limit=100`);
const data = await response.json();
allData = allData.concat(data.items);
hasMore = data.has_more;
page++;
// Add delay to respect rate limits
if (hasMore) await sleep(100);
}
return allData;
}
Data Validation
function validateRouteData(data) {
const required = ['id', 'name', 'origin', 'destination'];
for (const field of required) {
if (!data[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
return data;
}
Monitoring and Logging
Log API Calls
class APILogger {
logRequest(method, url, data) {
console.log(`[API] ${method} ${url}`, {
timestamp: new Date().toISOString(),
data: JSON.stringify(data)
});
}
logResponse(method, url, status, duration) {
console.log(`[API] ${method} ${url} - ${status}`, {
duration: `${duration}ms`,
timestamp: new Date().toISOString()
});
}
logError(method, url, error) {
console.error(`[API ERROR] ${method} ${url}`, {
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
}
}
Track Metrics
class APIMetrics {
constructor() {
this.calls = 0;
this.errors = 0;
this.totalDuration = 0;
}
recordCall(duration, success) {
this.calls++;
this.totalDuration += duration;
if (!success) this.errors++;
}
getStats() {
return {
totalCalls: this.calls,
errorRate: (this.errors / this.calls * 100).toFixed(2) + '%',
avgDuration: (this.totalDuration / this.calls).toFixed(2) + 'ms'
};
}
}
Security Best Practices
1. Use HTTPS Only
// Always use HTTPS
const API_BASE = 'https://api.soshiaconnect.com/api';
// Never use HTTP
// const API_BASE = 'http://api.soshiaconnect.com/api'; ❌
2. Validate Input
function sanitizeInput(input) {
// Remove potentially harmful characters
return input.replace(/[<>\"']/g, '');
}
// Validate email
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
3. Implement Timeouts
async function fetchWithTimeout(url, options, timeout = 30000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
return response;
} finally {
clearTimeout(timeoutId);
}
}
Testing
Unit Tests
describe('API Client', () => {
it('should handle authentication', async () => {
const token = await login('user@example.com', 'password');
expect(token).toBeDefined();
});
it('should retry on rate limit', async () => {
// Mock rate limit response
// Test retry logic
});
it('should cache responses', async () => {
const data1 = await getCachedData('test');
const data2 = await getCachedData('test');
// Expect only one API call
});
});
Integration Tests
describe('Route API', () => {
beforeAll(async () => {
token = await getTestToken();
});
it('should fetch routes', async () => {
const routes = await fetchRoutes(token);
expect(routes.length).toBeGreaterThan(0);
});
it('should handle invalid route ID', async () => {
await expect(fetchRoute(99999)).rejects.toThrow();
});
});
Code Organization
Create an API Client Class
class SoshiaConnectClient {
constructor(apiKey, baseURL) {
this.apiKey = apiKey;
this.baseURL = baseURL || 'https://api.soshiaconnect.com/api';
this.token = null;
}
async login(email, password) {
const response = await this.request('POST', '/auth/login', {
email,
password
});
this.token = response.access_token;
return this.token;
}
async request(method, endpoint, data, useApiKey = false) {
const headers = {
'Content-Type': 'application/json'
};
if (useApiKey) {
headers['X-API-Key'] = this.apiKey;
} else if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
const response = await fetch(`${this.baseURL}${endpoint}`, {
method,
headers,
body: data ? JSON.stringify(data) : undefined
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
// Convenience methods
async getRoutes() {
return this.request('GET', '/route-access/all-routes');
}
async getRoute(id) {
return this.request('GET', `/route-access/routes-data?id=${id}`);
}
}
// Usage
const client = new SoshiaConnectClient(process.env.API_KEY);
await client.login('user@example.com', 'password');
const routes = await client.getRoutes();
Documentation
Document Your Integration
/**
* Fetches shipping route data from SoshiaConnect API
* @param {number} routeId - The ID of the route to fetch
* @param {Object} options - Optional parameters
* @param {boolean} options.includeHistory - Include historical data
* @returns {Promise<Object>} Route data object
* @throws {Error} If route not found or API error occurs
*
* @example
* const route = await fetchRoute(123, { includeHistory: true });
* console.log(route.name);
*/
async function fetchRoute(routeId, options = {}) {
// Implementation
}
Checklist
Before deploying your integration:
- Credentials stored securely (environment variables)
- Error handling implemented
- Retry logic with exponential backoff
- Rate limit handling
- Request timeout configured
- Response caching implemented
- Logging and monitoring set up
- Unit and integration tests written
- API client documented
- Security review completed