Custom Apis
Master the art of designing clean, reusable API layers using fetch, async/await, and production-grade patterns used by professional developers.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
🎯 What You'll Learn
💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:
Understanding the Role of an API Layer
An API layer acts as the "communication brain" of your application. Instead of writing dozens of repetitive fetch calls scattered throughout your codebase, you centralize everything into a reusable system. This architecture is used in frameworks like Axios, React Query, tRPC, and enterprise-grade SDKs.
Creating a Base Fetch Wrapper
Let's start with a base utility that handles automatic JSON conversion, standardized error messages, status code interpretation, and default headers. This eliminates 90% of the boilerplate beginners write.
Adding Timeout Support (AbortController)
Sometimes APIs freeze or respond too slowly, causing the UI to hang. The AbortController API lets you cancel requests that take too long. This is essential for production apps where network conditions are unpredictable.
Adding Automatic Retries with Exponential Backoff
Networks fail—especially on mobile. Retries with exponential backoff dramatically improve reliability. This technique is used in Stripe, Shopify, Google APIs, and most major SDKs. The delay doubles after each failed attempt (300ms → 600ms → 1200ms).
Creating an API Client with Token Support
Most real APIs require authentication (JWT, OAuth, API keys). Here's an authentication-aware wrapper that automatically injects the stored token into every request.
Caching Responses for Faster Performance
Caching results locally can reduce API load and make your UI feel instant. This memory cache prevents repeated network calls for unchanged data.
Data Normalization & Transformers
Sometimes an API returns data in a structure that's not ideal for your UI. Instead of refactoring your components every time, you can normalize data inside the API client. This pattern is used by Redux Toolkit, Prisma, and GraphQL clients.
Batching Multiple Requests in Parallel
Often you need to load several resources at once—user data, analytics, settings, notifications. Using Promise.all() instead of sequential awaits dramatically reduces waiting time and is essential for dashboards and homepages.
Implementing a Global API Error Handler
Instead of manually catching errors everywhere, create a single centralized handler that maps error types to user-friendly messages. This keeps your UI clean and consistent.
Creating Reusable API Classes
For even more structure, advanced developers wrap everything inside classes. This pattern makes every API call clean and semantic, following the same approach used in large-scale frontends.
Handling Paginated APIs
Real APIs often return paginated data. Here's a reusable paginator that fetches multiple pages and combines the results—perfect for tables, infinite scroll, and analytic dashboards.
Creating an API Cache With Expiry (TTL)
To prevent stale data, add expiry times (Time-To-Live) to your cache entries. This ensures data stays fresh while still reducing unnecessary network requests.
Smart Request Deduplication
Imagine multiple components requesting the same data at the same time. Instead of sending the same HTTP request repeatedly, deduplication ensures only one network call happens. This prevents duplicate traffic and speeds load times dramatically.
Building a Universal API Wrapper
Here's a complete, production-grade API wrapper that includes retries, timeouts, aborting, caching, JSON parsing, error mapping, and data transformation. This is the same architecture used by professional engineering teams.
Security Considerations
⚠️ Security Warning: Browser storage is not secure for sensitive data.
What You Learned
Core Patterns
Advanced Techniques
Practice Challenges
1. Build a Complete API Client
Create an API client that combines timeout, retry, caching, and token injection into a single reusable module.
2. Implement Optimistic UI Updates
Build a "like" button that updates instantly, then syncs with the server in the background with rollback on failure.
3. Create a Request Queue
Build a system that ensures only one API request runs at a time to prevent rate limiting and server overload.
4. Design a Stale-While-Revalidate Cache
Serve cached data instantly while fetching fresh data in the background, then update the UI when new data arrives.
🎉 Lesson Complete!
You now know how to build enterprise-grade API clients with retries, caching, and token injection. Next up: Error Handling Patterns.
Practice quiz
What is the main purpose of building an API layer, per the lesson?
- To centralize fetch logic into a reusable system instead of scattering repetitive calls
- To make the UI render faster
- To replace the need for a backend
- To avoid using async/await
Answer: To centralize fetch logic into a reusable system instead of scattering repetitive calls. An API layer centralizes everything into a reusable system, reducing duplicated code and managing headers, errors, and caching in one place.
Which browser API is used to add a timeout that cancels a slow request?
- setTimeout alone
- AbortController
- Promise.race only
- XMLHttpRequest
Answer: AbortController. AbortController lets you cancel requests; you abort the controller after a timeout and pass its signal to fetch.
In the timeout example, what error name indicates the request was aborted?
- TimeoutError
- NetworkError
- AbortError
- FetchError
Answer: AbortError. When a request is aborted, the error has name 'AbortError', which the lesson checks to log 'Request timed out!'.
How does the retry pattern's exponential backoff change the delay between attempts?
- The delay stays constant
- The delay doubles after each failed attempt
- The delay is halved each time
- The delay is random with no pattern
Answer: The delay doubles after each failed attempt. The retry helper passes delay * 2 on each recursive retry, so the delay doubles (300ms then 600ms then 1200ms).
How does the token-aware API client attach authentication to requests?
- By adding a query string ?token=
- By setting an Authorization: Bearer <token> header
- By putting the token in the URL path
- By using a cookie automatically
Answer: By setting an Authorization: Bearer <token> header. The client reads the token from localStorage and sets the Authorization header to on every request.
Which method runs multiple fetches in parallel rather than sequentially?
- Promise.all
- await in a for loop
- Promise.resolve
- Array.forEach with await
Answer: Promise.all. Promise.all([...]) runs all requests at once (parallel), which is much faster than awaiting each sequentially.
What problem does the request deduplication pattern solve?
- Slow JSON parsing
- Multiple components requesting the same URL sending duplicate network calls
- Expired auth tokens
- CORS errors
Answer: Multiple components requesting the same URL sending duplicate network calls. Deduplication stores the pending promise in a Map keyed by URL so concurrent callers share ONE network request.
In the TTL cache, what determines whether a cached entry is still valid?
- Whether the Map has any entries
- Whether Date.now() is past the entry's expiry time
- Whether the user is logged in
- The size of the cached value
Answer: Whether Date.now() is past the entry's expiry time. cacheGet deletes and returns null when Date.now() > entry.expiry; otherwise it returns the cached value (a cache hit).
Why does data normalization happen inside the API client?
- To avoid refactoring components every time the API shape changes
- To make requests faster
- To bypass authentication
- To reduce the number of requests
Answer: To avoid refactoring components every time the API shape changes. Normalizing data in the client (e.g. normalizeUser) reshapes responses so UI components don't need to change when the API structure differs.
Per the Security Considerations, what should you store in localStorage?
- User passwords
- Only tokens, never sensitive user data
- Full credit card numbers
- Hashed passwords
Answer: Only tokens, never sensitive user data. The lesson warns browser storage isn't secure: limit localStorage to tokens only and never store passwords or sensitive user data.