top of page

Learn, Explore & Get Support from Freelance Experts

Welcome to Colabcodes, where innovation drives technology forward. Explore the latest trends, practical programming tutorials, and in-depth insights across software development, AI, ML, NLP and more. Connect with our experienced freelancers and mentors for personalised guidance and support tailored to your needs.

Coding expert help blog - colabcodes

JavaScript fetch() Essentials: Writing Reliable and Efficient API Calls

  • Writer: Samul Black
    Samul Black
  • Jul 27
  • 6 min read

When working with modern web applications, interacting with APIs is almost unavoidable—and at the heart of that interaction lies JavaScript’s fetch() method. Simple, promise-based, and powerful, fetch() is the go-to way to request and send data to servers in today’s JavaScript world. But while it's easy to get started with, writing reliable and efficientfetch calls requires a deeper understanding of how it works under the hood.

In this blog, we’ll break down everything you need to know to use fetch() effectively in real-world scenarios. Whether you're fetching JSON from a REST API, handling errors gracefully, or chaining multiple requests, this guide will help you write clean, robust, and scalable code.

Fetch JavaScript - Colabcodes

Introduction to fetch() in JavaScript

The fetch() API is a modern, built-in JavaScript method used to make HTTP requests to servers — whether you're retrieving data, submitting a form, or connecting to an external API. Introduced as a cleaner alternative to the older XMLHttpRequest, fetch() provides a more powerful and flexible interface for working with asynchronous operations using Promises.

Unlike XMLHttpRequest, which requires verbose syntax and callback hell, fetch() allows you to write cleaner, more readable code using .then() chains or the more modern async/await syntax.


Here’s a basic example:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Key advantages of fetch():


  • Promise-based: Makes handling asynchronous code smoother

  • Built into modern browsers: No need for external libraries

  • Readable and flexible: Works well with async/await

  • Supports custom headers, methods, and request bodies


Understanding the Basic Syntax of fetch()

At its core, the fetch() function takes in a URL (and optionally, a configuration object) and returns a Promise that resolves to a Response object. This response needs to be processed — usually converted to JSON, text, or another format.

Here’s the basic syntax:

fetch(url, options)
  .then(response => {
    // Handle the response
  })
  .catch(error => {
    // Handle any errors
  });

Parameters:

  • url (string): The endpoint you're making the request to.

  • options (optional object): Includes HTTP method, headers, body, etc.


Minimal Example (GET Request):

fetch('https://api.example.com/users')
  .then(response => response.json()) // Convert response to JSON
  .then(data => console.log(data))   // Work with the data
  .catch(error => console.error('Error:', error)); // Handle errors

Common options fields:

{
  method: 'POST', // or GET, PUT, DELETE
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'Alice' }) // For POST/PUT
}

fetch() does not reject on HTTP error statuses like 404 or 500. You need to check response.ok manually. The response body can only be read once, so make sure you handle it properly (e.g., as .json() or .text()).

This simple yet flexible syntax makes fetch() a powerful tool for any web developer looking to interact with APIs cleanly and efficiently.


Making GET and POST Requests with fetch()

The fetch() API makes it straightforward to perform both GET and POST requests — the two most common types of HTTP methods used in web development.


GET Request with fetch()

A GET request is used to retrieve data from a server. It's the default method used by fetch().

fetch('https://api.example.com/users')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

Key points:

  • No need to specify the method (GET is default).

  • Always check response.ok to catch HTTP errors.

  • Use .json() to parse JSON data from the response.


POST Request with fetch()

A POST request is used to send data to a server — commonly for form submissions, creating new records, etc.

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Alice',
    email: 'alice@example.com'
  })
})
  .then(response => {
    if (!response.ok) {
      throw new Error('Failed to submit data');
    }
    return response.json();
  })
  .then(data => console.log('Success:', data))
  .catch(error => console.error('Error:', error));

Key points:

  • Set method to 'POST'.

  • Always specify appropriate headers, especially Content-Type.

  • Convert your request body to a JSON string using JSON.stringify().


Using async/await for Cleaner fetch() Calls

While .then() and .catch() work perfectly with fetch(), using async/await can make your code cleaner, easier to read, and more like synchronous code — especially when you have multiple asynchronous operations in a row.


Why use async/await?


  • Improves readability by eliminating nested .then() chains.

  • Makes it easier to handle errors with try...catch.

  • Better suited for modern JavaScript development and real-world app logic.


Basic Example with async/await

async function getUserData() {
  try {
    const response = await fetch('https://api.example.com/users');

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

    const data = await response.json();
    console.log('User data:', data);

  } catch (error) {
    console.error('Fetch failed:', error);
  }
}

POST Request Example with async/await

async function createUser() {
  try {
    const response = await fetch('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: 'Bob',
        email: 'bob@example.com'
      })
    });

    if (!response.ok) {
      throw new Error('Failed to create user');
    }

    const result = await response.json();
    console.log('User created:', result);

  } catch (error) {
    console.error('Error:', error);
  }
}

Switching to async/await makes your fetch logic more robust, modern, and production-ready — especially as your codebase grows.


  • Use await fetch(...) to pause until the request completes.

  • Always wrap await calls in try...catch to handle errors gracefully.

  • Check response.ok to detect and handle HTTP errors.


How to Handle Errors in fetch() Requests

Handling errors properly in fetch() is critical for building reliable web applications. Although fetch() is promise-based, it only rejects on network errors (like no internet connection or DNS failure). It does not reject on HTTP error statuses like 404 or 500.


Common Types of Errors in fetch()


  1. Network Errors – e.g., user is offline, DNS fails.

  2. HTTP Errors – e.g., API returns 404 Not Found or 500 Internal Server Error.

  3. Parsing Errors – e.g., malformed JSON or calling .json() on empty response.

  4. Timeouts – fetch() has no built-in timeout, so long-hanging requests may need custom handling.

fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      // Manually throw an error for HTTP response codes outside 200–299
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('Data:', data);
  })
  .catch(error => {
    console.error('Fetch error:', error.message);
  });

With async/await and try...catch

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');

    if (!response.ok) {
      throw new Error(`Server responded with status ${response.status}`);
    }

    const data = await response.json();
    console.log('Data received:', data);

  } catch (error) {
    console.error('Fetch failed:', error.message);
  }
}

Adding Timeout

function fetchWithTimeout(url, options = {}, timeout = 7000) {
  return Promise.race([
    fetch(url, options),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Request timed out')), timeout)
    )
  ]);
}

By handling errors effectively, your application becomes more resilient, user-friendly, and easier to maintain in production.


Working with JSON and Other Response Types

The fetch() API returns a Response object, and it’s up to you to decide how to read its contents. The most common formats are:


1. JSON (.json())

Most APIs return JSON. You need to parse it:

const response = await fetch('https://api.example.com/data');
const data = await response.json(); // Parse JSON body

.json() returns a promise. You must await it or use .then().


2. Text (.text())

Use this when you expect plain text or HTML:

const response = await fetch('/readme.txt'); 
const content = await response.text();

3. Blob (.blob())

Useful for binary data like images, PDFs, or videos:

const response = await fetch('/image.jpg'); 
const blob = await response.blob(); 
const imageUrl = URL.createObjectURL(blob);

4. ArrayBuffer (.arrayBuffer())

For low-level binary operations:

const response = await fetch('/video.mp4'); 
const buffer = await response.arrayBuffer();

Pro Tip: Always check Content-Type in the response headers to know how to parse it:

const contentType = response.headers.get('Content-Type');

Setting Custom Headers and Request Options

To customize a fetch() request, you can pass a second argument — an options object — where you define the method, headers, body, and more.


Basic POST with Custom Headers

fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_TOKEN_HERE'
  },
  body: JSON.stringify({ name: 'Alice' })
});

Commonly Used Headers

Header

Purpose

Content-Type

Format of request body (e.g., application/json)

Authorization

API keys or tokens

Accept

Desired response format

X-Custom-Header

Any custom application header

With precise control over response types and headers, your fetch() requests become more powerful, secure, and adaptable to any API or data format.


fetch() vs Axios: Which One Should You Use?

Both fetch() and Axios are powerful tools for making HTTP requests in JavaScript — but they come with different trade-offs. Choosing between them depends on your project’s needs, preferences, and complexity.


Quick Comparison Table

Feature

fetch() (Native)

Axios (Library)

Built-in?

✅ Yes (in browsers)

❌ No (requires installation)

Promise-based?

✅ Yes

✅ Yes

JSON auto-parsing

❌ No

✅ Yes

Request/Response Interceptors

❌ No

✅ Yes

Error on HTTP errors

❌ No (manual check)

✅ Yes (throws on 4xx/5xx)

Timeout support

❌ Manual implementation

✅ Built-in

Browser support

Modern browsers only

Works in older ones too

Node.js support

❌ (needs node-fetch)

✅ Yes

Download size

0 KB

~15 KB minified

When to Use fetch() ?


  1. When you want zero dependencies.

  2. When you only need basic GET/POST requests.

  3. When you're working in a modern browser environment.

  4. When you want full control over parsing and error handling.


Conclusion: Best Practices for Efficient API Calls with fetch()

The JavaScript fetch() API is a powerful and flexible tool for making asynchronous HTTP requests — and when used correctly, it enables fast, reliable communication between your frontend and backend or external APIs.

In this blog, we explored:


  1. The basics of fetch() syntax

  2. How to handle GET and POST requests

  3. Cleaner patterns using async/await

  4. Crucial steps for error handling

  5. Working with JSON, text, blob, and more

  6. Setting custom headers and options

  7. When to choose fetch() over Axios


While fetch() is native and lightweight, it requires manual handling of things like HTTP errors and JSON parsing. But with the right structure and practices — like checking response.ok, using try...catch, and structuring your requests properly — you can build robust, modern web applications without needing external libraries.

Whether you're building a simple frontend or a scalable production app, mastering fetch() equips you with the fundamentals of web communication in JavaScript.

Get in touch for customized mentorship, research and freelance solutions tailored to your needs.

bottom of page