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);