Advanced30 min read

JSON & Fetch API

Work with JSON data and make HTTP requests using the Fetch API for communicating with web services.

JSON Basics

JSON (JavaScript Object Notation) is a lightweight text format for data exchange. It is the most common format for sending and receiving data from web APIs.

Converting between JS and JSON:

javascript
// JavaScript object to JSON string
const user = { name: 'Alice', age: 30 };
const jsonString = JSON.stringify(user);
console.log(jsonString); // '{"name":"Alice","age":30}'

// JSON string to JavaScript object
const parsed = JSON.parse(jsonString);
console.log(parsed.name); // 'Alice'

Pretty-printing JSON with indentation:

javascript
JSON.stringify(user, null, 2);
// {
//   "name": "Alice",
//   "age": 30
// }

Key differences from JavaScript objects:

  • Keys must be double-quoted strings ("name", not name)
  • No functions allowed
  • No undefined (use null instead)
  • No comments
  • No trailing commas
json
{
  "name": "Alice",
  "scores": [95, 87, 92],
  "active": true,
  "address": null
}

The Fetch API

The Fetch API provides a modern way to make HTTP requests from JavaScript. It replaces the older XMLHttpRequest.

javascript
// Basic GET request
const response = await fetch('https://api.example.com/users');
const data = await response.json();
console.log(data);

Key points about fetch:

  • fetch(url) returns a Promise that resolves to a Response object
  • You must await twice: once for the fetch itself, once for parsing the body
  • The Response object has useful properties:
    • response.oktrue if status is 200-299
    • response.status — the HTTP status code (200, 404, 500, etc.)
    • response.headers — the response headers

Parsing the response body:

javascript
// Parse as JSON
const json = await response.json();

// Parse as plain text
const text = await response.text();

// Parse as binary (Blob)
const blob = await response.blob();

Important: Each body parsing method (json(), text(), etc.) can only be called once per response. The body stream is consumed after the first call.

Making Requests

By default, fetch() makes a GET request. You can make other types of requests by passing an options object.

POST request (sending data):

javascript
const response = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Alice',
    email: 'alice@example.com',
  }),
});

const newUser = await response.json();

Other HTTP methods:

javascript
// PUT — replace entire resource
await fetch('/api/users/1', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice', age: 31 }),
});

// PATCH — partial update
await fetch('/api/users/1', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ age: 31 }),
});

// DELETE
await fetch('/api/users/1', { method: 'DELETE' });

Custom headers:

javascript
const response = await fetch('/api/data', {
  headers: {
    'Authorization': 'Bearer my-token',
    'Accept': 'application/json',
  },
});

Error Handling with Fetch

A common mistake is assuming fetch() throws on HTTP errors. It does not.

fetch() only rejects the Promise on network failures (no internet, DNS error, etc.). A 404 or 500 response is still a successful fetch — you must check manually.

javascript
try {
  const response = await fetch('/api/data');

  // Check if the HTTP status indicates success
  if (!response.ok) {
    throw new Error(`HTTP error! Status: ${response.status}`);
  }

  const data = await response.json();
  console.log(data);
} catch (error) {
  // Catches both network errors AND our thrown error
  console.error('Request failed:', error.message);
}

Creating a reusable wrapper:

javascript
async function fetchJSON(url, options = {}) {
  const response = await fetch(url, {
    headers: { 'Content-Type': 'application/json' },
    ...options,
  });

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

  return response.json();
}

// Usage
try {
  const users = await fetchJSON('/api/users');
  console.log(users);
} catch (err) {
  console.error(err.message);
}

This pattern ensures you handle both network errors and HTTP error responses in one place.

JSON & Fetch in Practice

html
<div id="output"></div>

<script>
  // JSON.stringify and JSON.parse
  const product = {
    name: 'Laptop',
    price: 999.99,
    specs: { ram: '16GB', storage: '512GB' }
  };

  // Convert to JSON string
  const json = JSON.stringify(product, null, 2);

  // Parse back to object
  const parsed = JSON.parse(json);

  // Simulate a fetch-like flow with JSON data
  const mockApiResponse = JSON.stringify({
    status: 'success',
    data: { users: ['Alice', 'Bob', 'Charlie'] }
  });

  const apiData = JSON.parse(mockApiResponse);

  const output = document.getElementById('output');
  output.innerHTML = `
    <p><strong>Original:</strong> ${product.name} - $${product.price}</p>
    <p><strong>JSON:</strong></p>
    <pre>${json}</pre>
    <p><strong>Parsed specs:</strong> ${parsed.specs.ram}, ${parsed.specs.storage}</p>
    <p><strong>API users:</strong> ${apiData.data.users.join(', ')}</p>
  `;
</script>

What does response.json() return?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.