Skip to main content

What is the Fetch API?

Fetch is a built-in browser function for making HTTP requests (GET, POST, PUT, DELETE) to remote servers (APIs).
const response = await fetch(url);
const data = await response.json();

Basic GET Request

main.ts
const API_URL = "https://api.escuelajs.co/api/v1/products";

async function fetchProducts(limit: number = 20): Promise<Product[]> {
  // Build URL with query parameter
  const url = `${API_URL}?limit=${limit}`;
  console.log(`Fetching: ${url}`);
  
  // Send GET request and wait for response
  const response = await fetch(url);
  
  // Check if response was successful (status 200-299)
  if (!response.ok) {
    throw new Error(`HTTP Error: ${response.status}`);
  }
  
  // Parse JSON and type it as Product[]
  const data = await response.json() as Product[];
  console.log(`Products received: ${data.length}`);
  
  return data;
}

The Fetch Process

Step 1: Send Request

const response = await fetch(url);
Returns a Response object with:
  • response.status - HTTP status code (200, 404, 500, etc.)
  • response.ok - true if status is 200-299
  • response.headers - Response headers
  • Methods to get body: .json(), .text(), .blob()

Step 2: Check Status

main.ts
if (!response.ok) {
  throw new Error(`HTTP Error: ${response.status}`);
}

Step 3: Parse Response Body

// For JSON
const data = await response.json();

// For plain text
const text = await response.text();

// For files/images
const blob = await response.blob();

URL Building with Template Literals

main.ts
const API_URL = "https://api.escuelajs.co/api/v1/products";
const limit = 20;

// Template literal: embed variables in string
const url = `${API_URL}?limit=${limit}`;
// Result: "https://api.escuelajs.co/api/v1/products?limit=20"

Multiple Query Parameters

const baseUrl = "https://api.example.com/products";
const limit = 20;
const category = "electronics";
const sort = "price";

const url = `${baseUrl}?limit=${limit}&category=${category}&sort=${sort}`;
// Result: "https://api.example.com/products?limit=20&category=electronics&sort=price"

HTTP Status Codes

Success Codes (2xx)

CodeMeaning
200OK - Request succeeded
201Created - Resource created successfully
204No Content - Success but no data to return

Client Error Codes (4xx)

CodeMeaning
400Bad Request - Invalid data sent
401Unauthorized - Need to authenticate
403Forbidden - Not allowed to access
404Not Found - Resource doesn’t exist

Server Error Codes (5xx)

CodeMeaning
500Internal Server Error - Server problem
503Service Unavailable - Server down/overloaded

Checking Status

const response = await fetch(url);

// Check if successful (200-299)
if (response.ok) {
  const data = await response.json();
}

// Check specific status
if (response.status === 404) {
  console.log("Resource not found");
}

// Get status text
console.log(response.statusText); // "OK", "Not Found", etc.

Error Handling

main.ts
async function loadProducts(): Promise<void> {
  appState.status = LoadingState.Loading;
  appState.error = null;
  updateUI();
  
  try {
    const products = await fetchProducts(20);
    
    appState.status = LoadingState.Success;
    appState.products = products;
    
  } catch (error) {
    // Handle any errors (network, server, parsing, etc.)
    appState.error = error instanceof Error ? error.message : "Unknown error";
    appState.status = LoadingState.Error;
    
    console.error("Error loading products:", error);
  }
  
  updateUI();
}

Types of Errors

  1. Network Error - No internet connection
  2. HTTP Error - Server returned error status (404, 500)
  3. Parse Error - Response isn’t valid JSON
  4. Timeout - Request took too long
try {
  const response = await fetch(url);
  
  if (!response.ok) {
    // HTTP error (4xx, 5xx)
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  
  const data = await response.json();
  // Might throw if response isn't valid JSON
  
} catch (error) {
  if (error instanceof TypeError) {
    // Network error (no connection)
    console.error("Network error:", error);
  } else if (error instanceof SyntaxError) {
    // JSON parse error
    console.error("Invalid JSON:", error);
  } else {
    // Other errors
    console.error("Error:", error);
  }
}

POST Requests

Sending data to the server:
async function createProduct(product: Partial<Product>): Promise<Product> {
  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(product),
  });
  
  if (!response.ok) {
    throw new Error(`HTTP Error: ${response.status}`);
  }
  
  return await response.json() as Product;
}

// Usage
const newProduct = await createProduct({
  title: "New Product",
  price: 99.99,
  description: "A great product",
  categoryId: 1,
  images: ["https://example.com/image.jpg"]
});

PUT Request (Update)

async function updateProduct(id: number, updates: Partial<Product>): Promise<Product> {
  const response = await fetch(`${API_URL}/${id}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(updates),
  });
  
  if (!response.ok) {
    throw new Error(`HTTP Error: ${response.status}`);
  }
  
  return await response.json() as Product;
}

DELETE Request

async function deleteProduct(id: number): Promise<void> {
  const response = await fetch(`${API_URL}/${id}`, {
    method: 'DELETE',
  });
  
  if (!response.ok) {
    throw new Error(`HTTP Error: ${response.status}`);
  }
}

Request Headers

Send additional information with requests:
const response = await fetch(url, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123',
    'Accept': 'application/json',
  },
});

Common Headers

HeaderPurpose
Content-TypeFormat of data being sent
AuthorizationAuthentication credentials
AcceptFormat of data you want back
User-AgentInfo about your app

JSON Parsing

response.json()

Parses JSON and returns JavaScript object/array:
main.ts
const data = await response.json() as Product[];

Type Assertion with ‘as’

// Tell TypeScript what type the data is
const products = await response.json() as Product[];
const user = await response.json() as User;
const config = await response.json() as AppConfig;

Manual JSON Parsing

const text = await response.text();
const data = JSON.parse(text);

Timeout Handling

Fetch doesn’t have built-in timeout. Use AbortController:
async function fetchWithTimeout(url: string, timeout: number = 5000): Promise<Response> {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    return response;
  } catch (error) {
    clearTimeout(timeoutId);
    if (error instanceof Error && error.name === 'AbortError') {
      throw new Error('Request timeout');
    }
    throw error;
  }
}

Response Data Validation

Always validate data from external sources:
function isProduct(obj: any): obj is Product {
  return (
    typeof obj === 'object' &&
    typeof obj.id === 'number' &&
    typeof obj.title === 'string' &&
    typeof obj.price === 'number'
  );
}

async function fetchProducts(): Promise<Product[]> {
  const response = await fetch(API_URL);
  const data = await response.json();
  
  if (!Array.isArray(data)) {
    throw new Error('Invalid response: expected array');
  }
  
  // Validate each product
  data.forEach((item, index) => {
    if (!isProduct(item)) {
      throw new Error(`Invalid product at index ${index}`);
    }
  });
  
  return data as Product[];
}

Loading States with Fetch

Combine fetch with state management:
main.ts
enum LoadingState {
  Idle = "IDLE",
  Loading = "LOADING",
  Success = "SUCCESS",
  Error = "ERROR"
}

let appState: AppState = {
  status: LoadingState.Idle,
  products: [],
  error: null
};

async function loadProducts(): Promise<void> {
  // Start loading
  appState.status = LoadingState.Loading;
  appState.error = null;
  updateUI();
  
  try {
    // Fetch data
    const products = await fetchProducts(20);
    
    // Success
    appState.status = LoadingState.Success;
    appState.products = products;
    
  } catch (error) {
    // Error
    appState.error = error instanceof Error ? error.message : "Unknown error";
    appState.status = LoadingState.Error;
  }
  
  // Update UI regardless of outcome
  updateUI();
}

CORS (Cross-Origin Resource Sharing)

If fetching from a different domain, the server must allow CORS:
// This might fail with CORS error if server doesn't allow it
const response = await fetch('https://different-domain.com/api');
Error message: Access to fetch at '...' has been blocked by CORS policy Solution: The API server must include CORS headers. You can’t fix this from the client.

Best Practices

1. Always Check response.ok

if (!response.ok) {
  throw new Error(`HTTP Error: ${response.status}`);
}

2. Use Try/Catch

try {
  const data = await fetch(url);
} catch (error) {
  console.error('Failed:', error);
}

3. Type Your Responses

const data = await response.json() as Product[];

4. Provide User Feedback

// Show loading state
updateUI(); 

// Fetch data
await fetchData();

// Show success/error
updateUI();

5. Log for Debugging

main.ts
console.log(`Fetching: ${url}`);
console.log(`Products received: ${data.length}`);
console.error("Error loading products:", error);

Next Steps

Build docs developers (and LLMs) love