Using Fetch API with TypeScript: Tutorial & Examples

Updated: January 8, 2024 By: Guest Contributor Post a comment

Introduction

In the compact tapestry of modern JavaScript, TypeScript emerges as the refined thread, giving strength and clarity to code. And there, alongside it, the Fetch API weaves in, allowing us to reach out into the vast internet expanse and pull in the very threads of data we desire. Together, they dance—a duet of precision and potential. Let this here guide illuminate the path to harnessing the Fetch API through the keen eye of TypeScript.

Setting the Stage

Firstly, ensure ye have TypeScript installed in ye environment—an npm install -g typescript should serve ye well. And lo, create a TypeScript project if ye haven’t yet been blessed with one. We’ll be assuming that ye are versed with the basics of TypeScript, its types, and its compilation. Ye will also want to navigate the tsconfig.json and be sure that “lib”: [“dom”, “es6”] is present, seeing as the Fetch API is browser-born and we’ll be promenading with Promises.

Basic Fetch Request

Now, let’s make our first call with the Fetch API using TypeScript. Look closely at this simple invocation:

async function fetchText(url: string): Promise<string> {
  const response = await fetch(url);
  if (!response.ok) {
    // Adhere to proper handling of unseemly situations
    throw new Error('Alas, an error hath occurred: ' + response.statusText);
  }
  return await response.text();
}

We define a function that makes a gallant promise to yield a string—or throw despair in the face of failure. Awaiting the completion of fetch, we assess the virtue of our response before proceeding to cal extit{lously} convert it to text.

Fetching JSON Data

More often than not, we aim to wrangle with JSON. Observe:

interface JovialData {
  id: number;
  jest: string;
}

async function fetchJson(url: string): Promise<JovialData> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error('Forsooth, a scourge upon our fetch quest: ' + response.statusText);
  }
  const jsonData: JovialData = await response.json();
  return jsonData;
}

Our interface, JovialData, stands as a testament to the shape of JSON we expect. Should the morrow’s response not shine favorably upon us, we once more throw our error. Otherwise, we proceed to parse our JSON into the structured form we’ve so desired.

Adding Request Headers

To converse with some servers you may need to don a proper cap—in the form of request headers. Thus:

async function fetchWithHeaders(url: string): Promise<Response> {
  const headers = new Headers();
  headers.set('Content-Type', 'application/json');
  const requestOptions = {
    method: 'GET',
    headers: headers
  };

  const response = await fetch(url, requestOptions);
  if (!response.ok) {
    throw new Error('The server resoundingly rebuked our headers: ' + response.statusText);
  }
  return response;
}

With our Headers in tow and requestOptions well-defined, we approach the server’s portcullis assuredly, requesting passage with our decorative cap.

Error Handling with Fetch API

Navigating the treacherous waters of error-riddled responses requires a steadfast resolve. Our thoughtfully crafted approach resembles this:

async function resilientFetch(url: string): Promise<any> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`Harbinger of misfortune, our requests are thwarted: ${response.statusText}`);
    }
    return await response.json();
  } catch (error) {
    console.error(`Zounds! Our valiant attempt was met with defeat: `, error);
    throw error; // Ensure the calling function knows of our trials and tribulations
  }
}

We sally forth into the try block, hopeful. An unfavorable repose leads us to cast an Error, else joyous JSON retrieval is ours. In the vile grasp of an exception, we catch and release it back into the console’s depths with a dramatic flair.

POST Request with JSON Payload

Progressing to sending forth our own JSON valiantly across the network:

interface NoblePayload {
  title: string;
  body: string;
  userId: number;
}

async function postJson(url: string, data: NoblePayload): Promise<Response> {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  });

  if (!response.ok) {
    throw new Error(`Blast! Our letter was not received favorably: ${response.statusText}`);
  }
  return await response.json();
}

Our dispatch, housed in NoblePayload, sets sail within the body of our request, JSON-stringified and itinerary set to POST. Oncemore, we prepare for unkind retorts with an Error at the ready.

Putting TypeScript Enums and Interfaces to Use

Employing TypeScript’s Enums and Interfaces can further empower our Fetch API interactions:

enum HttpMethod {
  GET = 'GET',
  POST = 'POST'
}

interface RegalResponse {
  id: number;
}

async function fetchWithEnum(url: string, method: HttpMethod): Promise<RegalResponse> {
  const requestOptions = {
    method: method
  };

  const response = await fetch(url, requestOptions);
  if (!response.ok) {
    throw new Error(`Affront! The request method ${method} did not curry favor with the server: ${response.statusText}`);
  }
  return await response.json();
}

Our HttpMethod Enum provides a distinguished selection of request manners, combined with our anticipated RegalResponse interface. This coalescence bestows style and expectation upon our communications.

Conclusion

In the denouement of our adventure, deploy these guidelines and examples as companions in your journey with TypeScript and the Fetch API. Embrace the eloquence of interfaces, the vigilance of error handling, and the sturdiness of TypeScript’s typing alongside the grace of the Fetch API. Stride forth, fellow coders, into a new realm where data is but a proper request away.