Minimal example
GET
const api = new Fetchtastic('https://jsonplaceholder.typicode.com');
const posts = await api.get('/posts').json();
POST
const api = new Fetchtastic('https://jsonplaceholder.typicode.com');
const body = {
title: 'foo',
body: 'bar',
userId: 1,
};
await api.post('/posts', body).appendHeader('Content-Type', 'application/json').resolve();
Composable configuration
You safely can reuse previous instances, since every method returns a new instance and does not modify the previous one.
Global config
const config = new Fetchtastic('https://jsonplaceholder.typicode.com')
.appendHeader('Accept', 'application/json')
.setOptions({ credentials: 'include', mode: 'cors' });
Endpoint level config
const postsAPI = config.searchParams({ page: 1, first: 12, sortBy: 'id' }).url('/api/v1');
const commentsAPI = config.appendHeader('Content-Type', 'application/json').url('/api/v2');
Send Requests
const blogPost = await postsAPI.get('/posts/1').json();
await commentsAPI
.post('/posts/1/comments', {
text: 'Lorem ipsum dolor sit amet',
userId: 1,
postId: 2,
})
.json();
Aborting a request
Fetch has the ability to abort requests using AbortController and signals under the hood. Compatible with browsers that support AbortController (opens in a new tab). Otherwise, you could use a polyfill (opens in a new tab).
const controller = new AbortController();
const api = new Fetchtastic('https://jsonplaceholder.typicode.com');
api
.controller(controller)
.get('/posts/1/comments')
.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
You can provide an assertion function to json
resolver in order to perform a runtime validation. Otherwise, the result is going to be unknown
.
For example, you can do that by using:
Type guards (opens in a new tab)
type Post = {
id: number;
title: string;
body: string;
};
function isPost(data: unknown): data is Post {
return (
data != null &&
typeof data === 'object' &&
'title' in data &&
typeof data.title === 'string' &&
'body' in data &&
typeof data.body === 'string'
);
}
export function assertPosts(data: unknown) {
if (data && Array.isArray(data) && data.every(isPost)) {
return data;
}
throw new Error('Invalid data format');
}
const api = new Fetchtastic('https://jsonplaceholder.typicode.com');
const posts = await api.get('/posts').json(assertPosts);
Zod (opens in a new tab)
import { z } from 'zod';
const PostSchema = z.object({
id: z.number(),
title: z.string(),
body: z.string(),
});
const PostListSchema = z.array(PostSchema);
const api = new Fetchtastic('https://jsonplaceholder.typicode.com');
const posts = await api.get('/posts').json(PostListSchema.parse);