@ns3/fetch-client v5.2.0
@ns3/fetch-client
fetch utilities for web and node (min v18).
Helper Functions
import { assertOk, toJson } from '@ns3/fetch-client';
// Ensures status ok and returns Promise<void>
fetch('https://google.com').then(assertOk);
// Ensures status ok and returns parsed body Promise<any>
fetch('https://example-api.com').then(toJson());In case response.ok !== true those helpers will throw FetchError that you can type check against:
import { assertOk, FetchError } from '@ns3/fetch-client';
fetch('https://google.com')
.then(assertOk)
.catch((error) => {
if (error instanceof FetchError) {
console.log(error.response.status);
}
});Interceptors
RequestInterceptor is a simple function that receives:
req- instance of fetchRequestnext- method to call a next interceptor or a final fetch.
and returns a fetch Response.
You can modify Request before a call to a next function.
You can modify Response after a call to a next function.
You can use interceptors by creating a new instance of fetch function using interceptFetch function.
It takes an array of interceptors as a first argument and optionally base fetch function as a second.
It returns modified, interceptable fetch function.
import { interceptFetch, RequestInterceptor } from '@ns3/fetch-client';
const firstInterceptor: RequestInterceptor = async (req, next) => {
console.log('first req');
const result = await next(req);
console.log('first res');
return result;
};
const interceptableFetch = interceptFetch([firstInterceptor]);Example
Here is a practical example of RequestInterceptor used for providing authorization header:
import { interceptFetch, RequestInterceptor } from '@ns3/fetch-client';
const authInterceptor: RequestInterceptor = async (req, next) => {
// modify request by adding auth header
const token = 'auth token from somewhere';
req.headers.set('authorization', `Bearer ${token}`);
const res = await next(req);
// check response and decide what to do
if (res.status === 401) {
// maybe I should refresh token and try again
} else {
return res;
}
};
const interceptableFetch = interceptFetch([authInterceptor]);
interceptableFetch('https://my-site-that-requires-auth.com').then(console.log);Call Order
Having interceptors:
import { interceptFetch } from '@ns3/fetch-client';
import { firstInterceptor, secondInterceptor } from './somewhere';
const interceptableFetch = interceptFetch([firstInterceptor, secondInterceptor]);The call order would be:
first reqsecond reqactual fetchsecond resfirst res
Extend
It is possible to extend existing interceptable fetch:
import { interceptFetch } from '@ns3/fetch-client';
import { extendInterceptor, firstInterceptor, secondInterceptor } from './somewhere';
const interceptableFetch = interceptFetch([firstInterceptor, secondInterceptor]);
const extendedFetch = interceptFetch([extendInterceptor], interceptableFetch);The call order of extendedFetch would be:
extendInterceptor reqfirst reqsecond reqactual fetchsecond resfirst resextendInterceptor res
Built-in interceptors
Library offers some built-in interceptors to address common use cases.
Timeout
To add timeout to a fetch call you can use timeoutInterceptor:
import { interceptFetch, timeoutInterceptor } from '@ns3/fetch-client';
const interceptableFetch1 = interceptFetch([timeoutInterceptor()]); // default 5000ms
const interceptableFetch2 = interceptFetch([timeoutInterceptor({ timeout: 1000 })]);It will return response with status 408 if timeout is reached.
Retry
To add retry to a fetch call you can use retryInterceptor:
import { interceptFetch, retryInterceptor } from '@ns3/fetch-client';
// default 2 retries (3 calls in total) with scalling duration 1500ms, exclude 4xx responses
const interceptableFetch1 = interceptFetch([retryInterceptor()]);
const interceptableFetch2 = interceptFetch([
retryInterceptor({
maxRetryAttempts: 3, // 4 calls in total
scalingDuration: 1000, // retries will be delayed respectively by [1000, 2000, 3000]
excludePredicate: (res) => response.status < 500, // wont retry 4xx responses
}),
]);FetchClient
A light weight FetchClient. It was inspired by Angular's HttpClient and axios.
It features:
- Fetch API and all its features.
- An object-oriented, Promise based API.
- Request transformation (
JSON.stringify+'Content-Type': 'application/json'header). - Response transformation via
toJsonutility function. baseUrlsupport.headerssupport.- Interceptors via
interceptFetch.
Base usage
import { FetchClient, toJson } from '@ns3/fetch-client';
const client = new FetchClient({
baseUrl: 'https://example-api.com',
headers: { 'x-api-key': '123' },
});
client.get('/retrieve').then(console.log);
// Body is `JSON.stringify`
// 'Content-Type': 'application/json' header is added
client.post('/upsert', { foo: 'bar' }).then(toJson()).then(console.log);
// Response is transformed using toJson functionInterceptors
FetchClient is made to work with RequestInterceptors.
Constructor receives a fetch function as an optional first argument.
You can use interceptFetch to provide it a fetch instance interceptable with RequestInterceptors.
import { FetchClient, interceptFetch } from '@ns3/fetch-client';
import { firstInterceptor, secondInterceptor } from './somewhere';
const client = new FetchClient({ fetch: interceptFetch([firstInterceptor, secondInterceptor]) });With @ns3/di
For those using @ns3/di this is an example of simple integration with dependency injection system:
import { Container, Injectable } from '@ns3/di';
import {
Fetch,
FetchClient,
interceptFetch,
RequestHandler,
retryInterceptor,
} from '@ns3/fetch-client';
@Injectable({ useValue: fetch })
export abstract class IFetch {}
export interface IFetch extends Fetch {}
@Injectable()
export class AppFetchClient extends FetchClient {
constructor(_fetch: IFetch) {
super(interceptFetch([retryInterceptor()], _fetch));
}
}
const container = Container.make();
const client = container.get(AppFetchClient);This way you can easily mock IFetch in your tests while still maintain convenient default Fetch value through IFetch symbol.