Overview
While the Juadah API does not currently implement rate limiting, following best practices will ensure optimal performance and prepare your application for future rate limit implementations.
Rate limiting is not currently enforced on the Juadah API. However, excessive or abusive usage may be subject to throttling or blocking without notice.
Best Practices
Request Optimization
Optimize your API requests to reduce unnecessary calls and improve performance.
Cache Responses Cache API responses locally to minimize redundant requests
Use Pagination Leverage pagination for large datasets instead of fetching all data
Batch Operations Combine multiple operations when possible
Conditional Requests Only fetch data when necessary
Caching Strategy
Implement intelligent caching to reduce API calls:
Products
User Data
Authentication Tokens
Recommendation: Cache product listings for 5-10 minutesProducts don’t change frequently, making them ideal for caching. const CACHE_DURATION = 5 * 60 * 1000 ; // 5 minutes
let productsCache = null ;
let cacheTimestamp = 0 ;
async function getProducts () {
const now = Date . now ();
if ( productsCache && ( now - cacheTimestamp ) < CACHE_DURATION ) {
return productsCache ;
}
const response = await fetch ( 'https://juadah-backend.vercel.app/api/products' , {
credentials: 'include'
});
productsCache = await response . json ();
cacheTimestamp = now ;
return productsCache ;
}
Recommendation: Cache user data for the session durationUser information rarely changes during an active session. // Store in session storage
function cacheUserData ( userData ) {
sessionStorage . setItem ( 'user' , JSON . stringify ( userData ));
}
function getCachedUserData () {
const cached = sessionStorage . getItem ( 'user' );
return cached ? JSON . parse ( cached ) : null ;
}
async function getCurrentUser () {
// Check cache first
const cached = getCachedUserData ();
if ( cached ) return cached ;
// Fetch from API
const response = await fetch ( 'https://juadah-backend.vercel.app/api/user' , {
credentials: 'include'
});
const data = await response . json ();
cacheUserData ( data . data . user );
return data . data . user ;
}
Recommendation: Tokens are automatically cached in cookiesThe API uses HTTP-only cookies which are automatically managed by the browser.
Access Token: 10 minutes (automatically refreshed)
Refresh Token: 10 days
See token configuration in src/lib/token.ts:5-12
The Products API supports infinite scroll pagination to efficiently handle large datasets.
Initial Request
Fetch the first batch of products (50 items): GET https://juadah-backend.vercel.app/api/products
Response: {
"status" : "success" ,
"data" : {
"meta" : {
"lastProductId" : 50
},
"products" : [
// ... 50 products
]
}
}
Load More
Use the lastProductId to fetch the next batch: GET https://juadah-backend.vercel.app/api/products?last_id= 50
This returns products 51-100.
Continue Pagination
Repeat with the new lastProductId until no more products are returned.
Implementation Example:
class ProductLoader {
constructor () {
this . lastProductId = null ;
this . hasMore = true ;
}
async loadMore () {
if ( ! this . hasMore ) return [];
const url = this . lastProductId
? `https://juadah-backend.vercel.app/api/products?last_id= ${ this . lastProductId } `
: 'https://juadah-backend.vercel.app/api/products' ;
const response = await fetch ( url , {
credentials: 'include'
});
const data = await response . json ();
if ( data . status === 'success' ) {
const { products , meta } = data . data ;
this . lastProductId = meta . lastProductId ;
this . hasMore = products . length === 50 ; // Assuming 50 per page
return products ;
}
return [];
}
}
// Usage
const loader = new ProductLoader ();
const firstBatch = await loader . loadMore ();
const secondBatch = await loader . loadMore ();
Connection Management
Manage HTTP connections efficiently:
Use persistent HTTP connections to reduce overhead. // Good: Use a single session
const session = new Session ();
async function makeRequest ( endpoint ) {
return session . get ( endpoint );
}
// Avoid: Creating new connections for each request
async function makeRequest ( endpoint ) {
return fetch ( endpoint ); // New connection each time
}
Ensure cookies are included in all requests: // Always include credentials for authenticated requests
fetch ( 'https://juadah-backend.vercel.app/api/products' , {
credentials: 'include' // Important!
});
The API uses cookie-based authentication, so this is critical for maintaining your session.
Set reasonable timeouts to avoid hanging requests: async function fetchWithTimeout ( url , options = {}, timeout = 10000 ) {
const controller = new AbortController ();
const id = setTimeout (() => controller . abort (), timeout );
try {
const response = await fetch ( url , {
... options ,
signal: controller . signal
});
clearTimeout ( id );
return response ;
} catch ( error ) {
clearTimeout ( id );
if ( error . name === 'AbortError' ) {
throw new Error ( 'Request timeout' );
}
throw error ;
}
}
Error Handling & Retries
Implement smart retry logic for transient failures:
async function fetchWithRetry ( url , options = {}, maxRetries = 3 ) {
let lastError ;
for ( let i = 0 ; i < maxRetries ; i ++ ) {
try {
const response = await fetch ( url , options );
const data = await response . json ();
// Only retry on server errors (500+)
if ( data . status === 'fail' && data . errors . code >= 500 ) {
throw new Error ( data . errors . message );
}
return data ;
} catch ( error ) {
lastError = error ;
// Don't retry on client errors (400-499)
if ( error . code && error . code < 500 ) {
throw error ;
}
// Wait before retrying (exponential backoff)
if ( i < maxRetries - 1 ) {
const delay = Math . pow ( 2 , i ) * 1000 ; // 1s, 2s, 4s
await new Promise ( resolve => setTimeout ( resolve , delay ));
}
}
}
throw lastError ;
}
// Usage
try {
const data = await fetchWithRetry ( 'https://juadah-backend.vercel.app/api/products' , {
credentials: 'include'
});
console . log ( data );
} catch ( error ) {
console . error ( 'Request failed after retries:' , error );
}
Token Refresh Strategy
The API uses short-lived access tokens (10 minutes) with automatic refresh capability.
Proactive Refresh
Refresh the access token before it expires: const TOKEN_LIFETIME = 10 * 60 * 1000 ; // 10 minutes
const REFRESH_BEFORE = 2 * 60 * 1000 ; // Refresh 2 minutes before expiry
let tokenTimestamp = Date . now ();
async function ensureValidToken () {
const now = Date . now ();
const age = now - tokenTimestamp ;
if ( age >= TOKEN_LIFETIME - REFRESH_BEFORE ) {
await refreshToken ();
tokenTimestamp = Date . now ();
}
}
async function refreshToken () {
const response = await fetch ( 'https://juadah-backend.vercel.app/api/refresh' , {
credentials: 'include'
});
return response . json ();
}
Handle 401 Responses
If you receive a 401, refresh and retry: async function makeAuthenticatedRequest ( url , options = {}) {
let response = await fetch ( url , {
... options ,
credentials: 'include'
});
// If unauthorized, try refreshing token
if ( response . status === 401 ) {
await refreshToken ();
// Retry original request
response = await fetch ( url , {
... options ,
credentials: 'include'
});
}
return response . json ();
}
Reduce Payload Size
Minimize the amount of data transferred:
For File Uploads:
Compress images before uploading
Respect the 5-image limit per product
Use supported formats only (PNG, JPG)
// Compress image before upload
async function compressImage ( file , maxWidth = 1200 ) {
return new Promise (( resolve ) => {
const reader = new FileReader ();
reader . onload = ( e ) => {
const img = new Image ();
img . onload = () => {
const canvas = document . createElement ( 'canvas' );
const ratio = Math . min ( maxWidth / img . width , 1 );
canvas . width = img . width * ratio ;
canvas . height = img . height * ratio ;
const ctx = canvas . getContext ( '2d' );
ctx . drawImage ( img , 0 , 0 , canvas . width , canvas . height );
canvas . toBlob ( resolve , 'image/jpeg' , 0.8 );
};
img . src = e . target . result ;
};
reader . readAsDataURL ( file );
});
}
Monitoring & Logging
Track your API usage to identify optimization opportunities:
class ApiMonitor {
constructor () {
this . requests = [];
}
logRequest ( endpoint , method , duration , status ) {
this . requests . push ({
endpoint ,
method ,
duration ,
status ,
timestamp: Date . now ()
});
}
getStats ( timeWindow = 60000 ) { // Last minute
const now = Date . now ();
const recent = this . requests . filter (
req => now - req . timestamp < timeWindow
);
return {
totalRequests: recent . length ,
averageDuration: recent . reduce (( sum , r ) => sum + r . duration , 0 ) / recent . length ,
errorRate: recent . filter ( r => r . status >= 400 ). length / recent . length ,
byEndpoint: this . groupByEndpoint ( recent )
};
}
groupByEndpoint ( requests ) {
return requests . reduce (( acc , req ) => {
if ( ! acc [ req . endpoint ]) {
acc [ req . endpoint ] = 0 ;
}
acc [ req . endpoint ] ++ ;
return acc ;
}, {});
}
}
// Usage
const monitor = new ApiMonitor ();
async function monitoredFetch ( url , options ) {
const start = Date . now ();
const response = await fetch ( url , options );
const duration = Date . now () - start ;
monitor . logRequest ( url , options . method || 'GET' , duration , response . status );
return response ;
}
// Check stats periodically
setInterval (() => {
const stats = monitor . getStats ();
console . log ( 'API Stats:' , stats );
}, 60000 );
Future Rate Limits
While not currently implemented, the API may introduce rate limits in the future:
Prepare for Future Limits When rate limits are implemented, you can expect:
Per-user limits based on authentication
Response headers indicating limit status
429 status code when limits are exceeded
Retry-After header for cooldown period
When rate limiting is implemented, responses will likely include:
X-RateLimit-Limit : 1000
X-RateLimit-Remaining : 999
X-RateLimit-Reset : 1234567890
Handling Rate Limits
Prepare your application to handle rate limits:
async function fetchWithRateLimitHandling ( url , options ) {
const response = await fetch ( url , options );
// Check for rate limit
if ( response . status === 429 ) {
const retryAfter = response . headers . get ( 'Retry-After' );
const waitTime = retryAfter ? parseInt ( retryAfter ) * 1000 : 60000 ;
console . log ( `Rate limited. Waiting ${ waitTime } ms` );
await new Promise ( resolve => setTimeout ( resolve , waitTime ));
// Retry request
return fetchWithRateLimitHandling ( url , options );
}
// Log remaining quota
const remaining = response . headers . get ( 'X-RateLimit-Remaining' );
if ( remaining ) {
console . log ( `API calls remaining: ${ remaining } ` );
}
return response ;
}
Recommendations Summary
Cache Aggressively Cache responses for 5-10 minutes to reduce redundant requests
Use Pagination Load products in batches of 50 using the pagination API
Retry Server Errors Implement exponential backoff for 500+ errors
Refresh Tokens Proactively Refresh access tokens before they expire (10-minute lifetime)
Monitor Usage Track request patterns to identify optimization opportunities
Handle Errors Gracefully Don’t retry client errors (400-499), only server errors
Next Steps
Authentication Learn about JWT authentication and token management
Error Handling Handle API errors effectively
Products API Explore the Products API with pagination
API Reference Browse all available endpoints