Skip to main content

Usage Guide

This guide is designed to help you get started with Fetchtastic, walking you through the process of setting up and using the library for predictable and type-safe network requests. You'll find step-by-step instructions, practical examples, and best practices

Basic example

const api = fetchtastic('https://jsonplaceholder.typicode.com')
.setOptions({ cache: 'default', mode: 'cors' })
.headers({
Accept: 'application/json',
'Content-Type': 'application/json',
});

// Send GET request with URL query params, resolve as JSON object
const json = await api
.get('/albums')
.setSearchParams({ page: 1, per_page: 12 })
.json();

// Send POST request with JSON body, resolve as raw `Response`
const response = await api
.url('/posts/1/comments')
.post({ title: 'foo', content: 'bar' })
.resolve();

// Send PUT request with JSON body, resolve as plain text
const text = await api
.url('/albums/20')
.put({ title: 'foo', body: 'bar', userId: 3 })
.text();

Composable configuration

You can safely reuse previous instances.

All methods return a new instance and does not modify the previous one.

1. Global config

const api = fetchtastic('/api/v2')
.setHeaders({
Accept: 'application/json',
'Content-Type': 'application/json',
})
.unauthorized(async error => {
console.log(error.message, await error.response.json());
redirec('/login');
});

2. Endpoint level config

const postsApi = api
.url('/posts')
.setSearchParams({ sortBy: 'date', order: 'desc' });

const albumsApi = api
.url('/albums')
.setOptions({ cache: 'default', mode: 'cors' });

3. Send Requests

// URL: /api/v2/posts?sortBy=date&order=desc
const posts = await postsApi.get().json();

// URL: /api/v2/albums/10
const album = await albumsApi.get('/10').json();

Aborting a request

Fetch has the ability to abort requests using AbortController and signals under the hood. Compatible with browsers that support AbortController. Otherwise, you could use a polyfill.

const controller = new AbortController();
const api = fetchtastic('https://jsonplaceholder.typicode.com');

api
.get('/posts/1/comments')
.controller(controller)
.json()
.then(data => storeData(data)) // should never be called
.catch((e: Error) => {
console.log(e.name); // AbortError
console.log(e.message); // The user aborted a request.
});
// Abort it!
controller.abort();

With this pattern you can also associate the same controller with multiple requests:

const controller = new AbortController();

api.controller(controller).get('/posts').json();
api.controller(controller).get('/comments').json();
// Abort both requests
controller.abort();

Type-safety

By default, the json() method returns an unknown type, however there are two ways to infer type-safety for the resulting data types.

Generic types

By providing a generic type argument to .json() the resulting type will inferred:

type Post = {
id: number;
title: string;
body: string;
};

const post = await fetchtastic('https://jsonplaceholder.typicode.com')
.get('/posts/3')
.json<Post>();

Runtime schema validation

You can provide a function to the json() resolver that performs runtime validations in order to assert the returned type. Next are two examples to achieve that:

Type guards

type Album = {
id: number;
title: string;
};

function isAlbum(data: unknown): data is Album {
return (
data != null &&
typeof data === 'object' &&
'id' in data &&
typeof data.id === 'number' &&
'title' in data &&
typeof data.title === 'string'
);
}

export function assertIsAlbum(data: unknown) {
if (isAlbum(data)) return data;
else throw new Error('Invalid data');
}

const album = await fetchtastic('https://jsonplaceholder.typicode.com')
.get('/albums/3')
.json(assertIsAlbum);

Zod

import { z } from 'zod';

const PostSchema = z.object({
id: z.number(),
title: z.string(),
body: z.string(),
});

const post = await fetchtastic('https://jsonplaceholder.typicode.com')
.get('/posts/5')
.json(PostSchema.parse);