Learning Objectives

  • Make GET and POST requests with fetch
  • Handle responses and errors properly
  • Send JSON data and custom headers
  • Use async/await with fetch
  • Apply fetch best practices

Basic GET Request

// Simple GET request with Promises
fetch('https://api.example.com/users')
    .then(response => response.json())
    .then(data => {
        console.log('Users:', data);
    })
    .catch(error => {
        console.error('Error:', error);
    });

// With async/await (recommended)
async function getUsers() {
    try {
        const response = await fetch('https://api.example.com/users');
        const data = await response.json();
        console.log('Users:', data);
        return data;
    } catch (error) {
        console.error('Error:', error);
        throw error;
    }
}

// Usage
const users = await getUsers();

POST Request

async function createUser(userData) {
    const response = await fetch('https://api.example.com/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(userData)
    });
    
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
}

// Usage
const newUser = await createUser({
    name: 'John Doe',
    email: 'john@example.com',
    age: 30
});

console.log('Created user:', newUser);

Error Handling

Important: Fetch only rejects on network errors, not HTTP errors (404, 500, etc.)!
async function fetchWithErrorHandling(url) {
    try {
        const response = await fetch(url);
        
        // Check if response is OK (status 200-299)
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        const data = await response.json();
        return data;
    } catch (error) {
        // Network error
        if (error.name === 'TypeError') {
            console.error('Network error:', error.message);
        } 
        // HTTP error
        else {
            console.error('HTTP error:', error.message);
        }
        throw error;
    }
}

// Usage
try {
    const data = await fetchWithErrorHandling('https://api.example.com/data');
} catch (error) {
    // Handle error in UI
    showErrorMessage(error.message);
}

Request Options

const response = await fetch(url, {
    // HTTP method
    method: 'POST', // GET, POST, PUT, DELETE, PATCH, etc.
    
    // Request headers
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer your-token-here',
        'Accept': 'application/json'
    },
    
    // Request body (for POST, PUT, PATCH)
    body: JSON.stringify({
        key: 'value'
    }),
    
    // CORS mode
    mode: 'cors', // cors, no-cors, same-origin
    
    // Credentials (cookies)
    credentials: 'include', // include, same-origin, omit
    
    // Cache mode
    cache: 'no-cache', // default, no-cache, reload, force-cache, only-if-cached
    
    // Redirect handling
    redirect: 'follow', // follow, error, manual
    
    // Referrer policy
    referrerPolicy: 'no-referrer',
    
    // Abort signal (for cancellation)
    signal: abortController.signal
});

Response Methods

const response = await fetch(url);

// Parse response body
const json = await response.json();           // Parse as JSON
const text = await response.text();           // Get as plain text
const blob = await response.blob();           // Get as Blob (files)
const formData = await response.formData();   // Parse as FormData
const arrayBuffer = await response.arrayBuffer(); // Get as ArrayBuffer

// Response properties
console.log(response.ok);          // true if status 200-299
console.log(response.status);      // HTTP status code (200, 404, etc.)
console.log(response.statusText);  // Status message ("OK", "Not Found")
console.log(response.headers);     // Headers object
console.log(response.url);         // Final URL (after redirects)
console.log(response.redirected);  // Was redirected?
console.log(response.type);        // Response type (basic, cors, etc.)

// Get specific header
const contentType = response.headers.get('Content-Type');

Real-World Examples

API Client Class

class ApiClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
        this.token = null;
    }
    
    setToken(token) {
        this.token = token;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;
        
        const headers = {
            'Content-Type': 'application/json',
            ...options.headers
        };
        
        if (this.token) {
            headers['Authorization'] = `Bearer ${this.token}`;
        }
        
        const response = await fetch(url, {
            ...options,
            headers
        });
        
        if (!response.ok) {
            const error = await response.json().catch(() => ({}));
            throw new Error(error.message || `HTTP ${response.status}`);
        }
        
        return response.json();
    }
    
    get(endpoint) {
        return this.request(endpoint, { method: 'GET' });
    }
    
    post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
    
    put(endpoint, data) {
        return this.request(endpoint, {
            method: 'PUT',
            body: JSON.stringify(data)
        });
    }
    
    delete(endpoint) {
        return this.request(endpoint, { method: 'DELETE' });
    }
}

// Usage
const api = new ApiClient('https://api.example.com');
api.setToken('my-auth-token');

const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John' });
await api.delete('/users/123');

Request Timeout

async function fetchWithTimeout(url, options = {}, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
        const response = await fetch(url, {
            ...options,
            signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        return response;
    } catch (error) {
        clearTimeout(timeoutId);
        
        if (error.name === 'AbortError') {
            throw new Error('Request timeout');
        }
        throw error;
    }
}

// Usage
try {
    const response = await fetchWithTimeout('https://api.example.com/slow', {}, 3000);
    const data = await response.json();
} catch (error) {
    console.error('Request failed:', error.message);
}

Retry Logic

async function fetchWithRetry(url, options = {}, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url, options);
            
            if (response.ok) {
                return response;
            }
            
            // Don't retry client errors (4xx)
            if (response.status >= 400 && response.status < 500) {
                throw new Error(`HTTP ${response.status}`);
            }
            
            // Retry server errors (5xx)
            if (i < retries - 1) {
                await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
                continue;
            }
            
            throw new Error(`HTTP ${response.status}`);
        } catch (error) {
            if (i === retries - 1) throw error;
            await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
        }
    }
}

// Usage
const response = await fetchWithRetry('https://api.example.com/data');
const data = await response.json();

Best Practices

1. Always check response.ok
if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
}
2. Use async/await for cleaner code
// Good
const data = await fetch(url).then(r => r.json());

// Better
const response = await fetch(url);
const data = await response.json();
3. Handle errors properly
try {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const data = await response.json();
} catch (error) {
    console.error('Fetch failed:', error);
}
4. Set appropriate headers
headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
}

Key Takeaways