api-layer-title
api-layer-subtitle
api-layer-intro-note
๐๏ธ api-layer-what-title
Think of the API layer as a bouncer for your app โ it decides who talks to the backend and how. No more spaghetti fetch calls scattered across your components! ๐ Let's build a clean, organized gateway that keeps your UI and data layers happily separated.
๐ต Impact: MEDIUM โ A dedicated API layer keeps your codebase organized and your components blissfully unaware of HTTP details
๐ In this section: API Layer Definition โข Separation of Concerns โข Benefits Overview
api-layer-what-benefit-title
api-layer-what-benefit-desc
๐จ api-layer-bad-title
We've all been there โ fetch calls living right inside your components like uninvited guests at a party. ๐ Let's see why mixing API logic with UI code is a recipe for spaghetti and how to fix it!
๐ด Impact: CRITICAL โ Mixing API calls with UI code makes everything harder to test, debug, and maintain โ don't do it!
๐ In this section: Anti-Pattern Examples โข Mixed Concerns โข Separation Solution
// โ BAD: Mixed API and UI codeimport { useEffect, useState } from "react";import { useParams } from "react-router";import { apiClient } from "@/api/client";export function UserProfile() {const { handle } = useParams<{ handle: string }>();const [user, setUser] = useState();const [userShouts, setUserShouts] = useState();const [hasError, setHasError] = useState(false);useEffect(() => {// API details mixed with UI logicapiClient.get(`/user/${handle}`) // Endpoint path in component.then((response) => setUser(response.data)).catch(() => setHasError(true));apiClient.get(`/user/${handle}/shouts`) // Another endpoint in component.then((response) => setUserShouts(response.data)).catch(() => setHasError(true));}, [handle]);if (hasError) return <div>An error occurred</div>;if (!user || !userShouts) return <LoadingSpinner />;return <div>{/* UI code */}</div>;}// Problems:// - Component knows about API endpoints (/user/:handle)// - Component knows about HTTP methods (GET)// - Component handles API response structure (response.data)// - Hard to reuse this API logic in other components// - If endpoint changes, must update component code
// โ GOOD: Separated API layer// src/api/user.tsimport { apiClient } from "./client";async function getUser(handle: string) {const response = await apiClient.get(`/user/${handle}`);return response.data;}async function getUserShouts(handle: string) {const response = await apiClient.get(`/user/${handle}/shouts`);return response.data;}export default { getUser, getUserShouts };// Component - clean and focused on UIimport UserApi from "@/api/user";export function UserProfile() {const { handle } = useParams<{ handle: string }>();const [user, setUser] = useState();const [userShouts, setUserShouts] = useState();const [hasError, setHasError] = useState(false);useEffect(() => {if (!handle) return;UserApi.getUser(handle).then((data) => setUser(data)).catch(() => setHasError(true));UserApi.getUserShouts(handle).then((data) => setUserShouts(data)).catch(() => setHasError(true));}, [handle]);// Component only cares about UIif (hasError) return <div>An error occurred</div>;if (!user || !userShouts) return <LoadingSpinner />;return <div>{/* UI code */}</div>;}// Benefits:// - Component doesn't know endpoint paths// - Component doesn't know HTTP methods// - API logic is reusable// - Easy to change API without touching UI
๐ก api-layer-why-title
Why should you care about an API layer? Because your future self will thank you! ๐ A proper API layer makes your app easier to maintain, test, and scale. Here are the key reasons it's a game-changer.
๐ Impact: HIGH โ Understanding WHY matters is the difference between a junior dev and a senior architect
๐ In this section: Separation of Concerns โข Maintainability โข Reusability โข Flexibility
1. api-layer-why-separation-title
api-layer-why-separation-desc
2. api-layer-why-maintainability-title
api-layer-why-maintainability-desc
3. api-layer-why-reusability-title
api-layer-why-reusability-desc
4. api-layer-why-flexibility-title
api-layer-why-flexibility-desc
๐ช api-layer-hooks-title
Custom hooks are like superpowers for your API layer! ๐ฆธ They encapsulate all the loading, error, and data management so your components stay clean and focused. Build once, use everywhere โ that's the dream!
๐ Impact: HIGH โ Custom hooks turn repetitive fetch-and-state patterns into elegant, reusable one-liners
๐ In this section: useFetch Hook โข Type-Safe Wrappers โข Reusable API Hooks
// โ BAD: Basic fetch functions without hooks// api/user.tsexport async function getUser(id: string) {const response = await fetch(`/api/users/${id}`);return response.json();}// Component - must manage state manuallyfunction UserProfile({ userId }) {const [user, setUser] = useState(null);const [loading, setLoading] = useState(false);const [error, setError] = useState(null);useEffect(() => {setLoading(true);getUser(userId).then(data => {setUser(data);setLoading(false);}).catch(err => {setError(err);setLoading(false);});}, [userId]);if (loading) return <div>Loading...</div>;if (error) return <div>Error occurred</div>;return <div>{user?.name}</div>;}// Problems:// - Repetitive state management in every component// - Loading and error states duplicated// - Not type-safe// - Can't easily cancel requests
// โ GOOD: Custom hooks for API layer// hooks/useFetch.tsimport { useState } from "react";type UseFetchProps = {url: string;method: "GET" | "POST" | "PUT" | "DELETE";};export function useFetch<T>({ url, method }: UseFetchProps) {const [isLoading, setIsLoading] = useState(false);const [data, setData] = useState<T | null>(null);const [error, setError] = useState<Error | null>(null);const commonFetch = async (input?: Record<string, any>) => {setIsLoading(true);setError(null);try {const response = await fetch(url, {method,headers: { "Content-Type": "application/json" },body: input ? JSON.stringify(input) : undefined,});if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}const jsonData = await response.json();setData(jsonData);return jsonData;} catch (err) {const error = err instanceof Error ? err : new Error("Unknown error");setError(error);throw error;} finally {setIsLoading(false);}};return { isLoading, data, error, commonFetch };}// api/user.tsimport { useFetch } from "@/hooks/useFetch";import { User } from "@/types";export const useGetUser = () => {const { commonFetch, isLoading, data, error } = useFetch<User>({url: "/api/users",method: "GET",});const getUser = (id: string) =>commonFetch({ id });return { getUser, isLoading, data, error };};// Component - clean and declarativefunction UserProfile({ userId }) {const { getUser, isLoading, data: user, error } = useGetUser();useEffect(() => {getUser(userId);}, [userId]);if (isLoading) return <div>Loading...</div>;if (error) return <div>Error: {error.message}</div>;return <div>{user?.name}</div>;}// Benefits:// - Reusable loading/error state// - Type-safe with TypeScript generics// - Consistent interface across all API calls// - Easy to extend with abort logic, caching, etc.
๐งฉ api-layer-complete-title
Time to put it all together! ๐ฏ This is the full blueprint for a production-grade API layer โ types, requests, and aggregated hooks all working in harmony. Copy this structure and never look back!
๐ด Impact: CRITICAL โ This is the full pattern you'll use in every production project โ master it and level up your architecture game
๐ In this section: Folder Structure โข TypeScript Types โข Request Hooks โข Hook Aggregation
api-layer-complete-structure-title
api/
supplier/
types.ts # TypeScript types
requests.ts # Individual API hooks
api.ts # Aggregated API hook
user/
types.ts
requests.ts
api.ts
hooks/
useFetch.ts # Base fetch hookapi-layer-complete-types-title
// โ Types scattered or missingfunction getUser(id) { // No type informationreturn fetch(`/api/users/${id}`);}// Component doesn't know what to expectconst user = await getUser("123");console.log(user.name); // TypeScript error: Property 'name' does not exist
// โ Types defined clearly// api/user/types.tsexport type User = {id: string;name: string;email: string;createdOn: string;};export type GetUserInput = { id: string };// api/user/requests.tsimport { useFetch } from "@/hooks/useFetch";import { User, GetUserInput } from "./types";export const useGetUser = () => {const { commonFetch, isLoading, data, error } = useFetch<User>({url: "/api/users/get",method: "GET",});const getUser = (input: GetUserInput) =>commonFetch({ input });return { getUser, isLoading, data, error };};// Component - fully type-safeconst { getUser, data } = useGetUser();await getUser({ id: "123" });// TypeScript knows data is User | nullconsole.log(data?.name); // โ Type-safe!
api-layer-complete-aggregation-title
// โ Multiple hooks imported separatelyimport { useGetUser } from "@/api/user/requests";import { useGetUserPosts } from "@/api/user/requests";import { useUpdateUser } from "@/api/user/requests";function UserProfile() {const { getUser, isLoading: getUserLoading } = useGetUser();const { getPosts, isLoading: getPostsLoading } = useGetUserPosts();const { updateUser, isLoading: updateLoading } = useUpdateUser();// Messy and verbose...
// โ Single aggregated hook// api/user/api.tsimport { useGetUser, useGetUserPosts, useUpdateUser } from "./requests";export const useUserApi = () => {const getUserHook = useGetUser();const getPostsHook = useGetUserPosts();const updateUserHook = useUpdateUser();return {getUser: {query: getUserHook.getUser,isLoading: getUserHook.isLoading,data: getUserHook.data,},getPosts: {query: getPostsHook.getUserPosts,isLoading: getPostsHook.isLoading,data: getPostsHook.data,},updateUser: {mutation: updateUserHook.updateUser,isLoading: updateUserHook.isLoading,data: updateUserHook.data,},};};// Component - clean and organizedimport { useUserApi } from "@/api/user/api";function UserProfile() {const {getUser: { query: getUser, data: user, isLoading: userLoading },getPosts: { query: getPosts, data: posts, isLoading: postsLoading },updateUser: { mutation: updateUser },} = useUserApi();// Clean, predictable interface!
๐ฏ api-layer-takeaways-title
๐ข Impact: LOW โ Quick recap to cement everything you've learned โ bookmark this for your next project!
๐ In this section: Core Principles โข Best Practices Summary โข Next Steps
โ api-layer-takeaway-1
โ api-layer-takeaway-2
โ api-layer-takeaway-3
โ api-layer-takeaway-4
โ api-layer-takeaway-5