An Instagram client is fundamentally a multimedia networking application. It continuously coordinates network traffic: downloading paginated photo feeds, authorizing requests with security credentials, uploading high-resolution user photos, and resolving responsive image streams.
Rather than installing heavy HTTP libraries like Axios (which adds unnecessary bundle weight), we engineered a lean, high-performance networking layer using:
-
The Native
fetch()API (Zero-dependency, browser-standard HTTP communication) - Automatic Bearer Token Injection & Centralized Error Handling
- Client-Side Canvas-Optimized Base64 File Conversion (Resizing & compressing user uploads directly in the browser)
- Unsplash Dynamic URL Resolution & Responsive
srcsetGeneration
In this tutorial, we will examine how these networking primitives fit together in our Instagram client.
1. Why Native fetch() over Axios?
In the early days of JavaScript, fetch() was missing features like automatic JSON parsing and interceptors, prompting developers to rely on libraries like Axios. Today, native fetch() is supported in 100% of modern browsers.
Why Native fetch() Wins for Our Architecture:
- Zero Bundle Weight: Saves 15–30 KB of compressed JavaScript, keeping initial page load fast.
- Native Streaming & AbortController: Direct integration with browser cancellation primitives.
-
Standards-Compliant: Uses native browser
Request,Response, andHeadersobjects.
2. Automatic Auth Headers Injection & Base Client Architecture
In src/utils/api.js, all outbound HTTP requests flow through a unified network layer that automatically detects active user sessions.
Step 1: Environment-Aware Base URL
// Automatically swaps between local development and production cloud backends
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
Step 2: Injecting Bearer Authentication Headers
Whenever an endpoint requires authentication (e.g., liking a post, following a creator, or publishing a new photo), our helper dynamically injects the token stored in localStorage:
import { getAuthToken } from './api';
export function getAuthHeaders() {
const token = getAuthToken();
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
// If the user has an active session, inject the Bearer token
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return headers;
}
Step 3: Resilient API Call Pattern
Here is how our client calls the backend's /auth/login endpoint with clean error checking:
export async function loginApi({ handle, password }) {
const response = await fetch(`${API_BASE_URL}/auth/login`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
handle: handle.trim(),
password
})
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
// Surface user-friendly error message from backend
const errorMessage = data?.detail || 'Login failed. Please check your credentials.';
throw new Error(errorMessage);
}
return data; // Returns { token, user }
}
3. Client-Side Image Upload: Base64 File Conversion with Canvas Optimization
When a user selects a 12-Megapixel (15 MB) photo from their smartphone or PC to upload to Instagram, uploading that raw uncompressed image causes three major problems:
- Massive bandwidth usage and slow upload progress on mobile data.
- Backend memory exhaustion while processing gigabytes of raw image bytes.
- High database storage costs.
The Solution: In-Browser Resizing & Compression via HTML5 Canvas
Instead of sending the raw file directly, our client converts, resizes, and compresses the image into a clean JPEG Base64 Data URL right inside the browser before the upload request is even dispatched!
Here is our implementation from CreatePostModal.jsx:
/**
* Reads and optimizes an image file from local PC disk into a clean JPEG data URL.
* Scales down large images to max 1200x1200px and compresses to 85% JPEG quality.
*/
export function readOptimizedImageFile(file, maxWidth = 1200, maxHeight = 1200, quality = 0.85) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onload = (event) => {
const img = new Image();
img.onerror = () => reject(new Error('Failed to load image file'));
img.onload = () => {
let width = img.width;
let height = img.height;
// 1. Maintain aspect ratio while capping maximum dimensions
if (width > maxWidth || height > maxHeight) {
if (width / height > maxWidth / maxHeight) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
} else {
width = Math.round((width * maxHeight) / height);
height = maxHeight;
}
}
// 2. Render resized photo onto off-screen HTML5 Canvas
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// 3. Export as optimized Base64 data URL string
const dataUrl = canvas.toDataURL('image/jpeg', quality);
resolve({ url: dataUrl, width, height });
};
// Feed FileReader array buffer to Image element
img.src = event.target.result;
};
// Read the File object as a data URL
reader.readAsDataURL(file);
});
}
How This Improves Performance:
- A raw 12 MB camera shot is instantly resized to 1200px and compressed to ~180 KB in memory in under 50 milliseconds!
- The resulting Base64 string can be previewed instantly in the UI with zero server round-trips.
- The payload sent over the network is minuscule, ensuring instant post creation even on slow 3G connections.
4. Unsplash URL Resolver & Responsive srcset Generation
In our Instagram clone, users can also publish posts using URLs from Unsplash, or pick from curated photography presets.
However, raw Unsplash photos can be up to 4000x3000 pixels in resolution. To save mobile data and ensure rapid rendering, we implemented an Unsplash URL Resolver that leverages Unsplash's dynamic Image CDN parameters.
1. Curated Preset URLs with Uniform CDN Parameters
const UNSPLASH_PRESETS = [
{ label: '🍝 Pasta', url: 'https://images.unsplash.com/photo-1551183053-bf91a1d81141?auto=format&fit=crop&w=1080&q=80' },
{ label: '🌸 Flora', url: 'https://images.unsplash.com/photo-1520763185298-1b434c919102?auto=format&fit=crop&w=1080&q=80' },
{ label: '📚 Books', url: 'https://images.unsplash.com/photo-1512820790803-83ca734da794?auto=format&fit=crop&w=1080&q=80' },
{ label: '🎧 Music', url: 'https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?auto=format&fit=crop&w=1080&q=80' },
];
-
auto=format: Automatically serves next-generation WebP or AVIF formats if the user's browser supports it. -
fit=crop: Crops the image to fit the container without distortion. -
w=1080: Bounds the maximum width to standard Instagram full-HD resolution. -
q=80: Compresses to 80% perceptual quality, reducing file size by 60% with zero noticeable visual loss.
2. Responsive srcset Generation (src/utils/images.js)
When displaying images on different screen resolutions (mobile phone vs. Retina 4K display), serving one fixed image size wastes bandwidth or looks blurry.
In src/utils/images.js, we built a responsive generator that constructs browser-native srcset attributes dynamically:
/**
* Generates responsive srcset strings for CDN-backed image URLs.
*/
export function getResponsiveSrcSet(url) {
if (!url) return '';
if (url.includes('unsplash.com')) {
// Strip existing query parameters to obtain clean base CDN path
const baseUrl = url.split('&w=')[0].split('?')[0];
// Return responsive candidate set across 400px, 800px, and 1200px screen sizes
return `${baseUrl}?auto=format&fit=crop&w=400&q=80 400w, ` +
`${baseUrl}?auto=format&fit=crop&w=800&q=80 800w, ` +
`${baseUrl}?auto=format&fit=crop&w=1200&q=80 1200w`;
}
// Fallback for standard local or non-CDN URLs
return `${url} 1x`;
}
How the Browser Uses This in HTML:
<img
src={post.image_url}
srcSet={getResponsiveSrcSet(post.image_url)}
sizes="(max-width: 600px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt={post.caption}
loading="lazy"
/>
- A small mobile screen automatically downloads the lightweight 400px version (~40 KB).
- A high-density desktop screen downloads the sharp 1200px version.
- The browser decides the best resolution automatically without any custom JavaScript logic!
Networking Architecture Summary
| Networking Layer | Implementation Pattern | Key Benefit |
|---|---|---|
| HTTP Client | Native browser fetch() API |
Zero-dependency, lightweight, native async/await. |
| Authentication | Automatic Bearer Header Injection | Seamless session passing across protected endpoints. |
| Local File Uploads | Canvas-based Base64 conversion | 90%+ image file size reduction before network transmission. |
| CDN Integration | Unsplash query parameter binding | Dynamic WebP delivery, auto-cropping, and quality tuning. |
| Responsive Images |
getResponsiveSrcSet generator |
Serves optimal image dimensions matching exact device widths. |
In the next frontend post, we will explore UX, Routing & Deep Linking: In-Memory SPA Routing, History API, and Popstate Navigation!
Top comments (0)