rest-api-helper is a tiny lightweight package that abstracts the process of making HTTP requests.
Install the package using npm:
npm install rest-api-helperor yarn:
yarn add rest-api-helperTo perform any request it is required to:
- Define transport aka the way you're going to communicate
- Configure client to glue everything together (base url, headers, transport etc)
- Create request object
The Transport interface obliges you to implement one single method handle. It can do whatever you want whether it's fetch or XHR or setTimeout mock. In most cases, you're going to use the fetch API:
import { Transport } from "rest-api-helper";
const transport: Transport<Response> = {
handle(request) {
return fetch(request.url.href, request);
}
};The Interceptor interface binds you to implement onResponse method. Instead of being resolved immediately, original promise will fall through interceptor pipeline.
Each onResponse call comes along with three arguments:
request: Request– original request objectresponse: T– received responsepromise: OriginalPromise<T>- original Promise handles (resolveandrejectfunctions)
It allows you to intercept, analyze and modify responses before they are returned. This might be useful for scenarios like handling unauthorized responses or refreshing tokens:
import { Interceptor } from "rest-api-helper";
const interceptor: Interceptor<Response> = {
onResponse: async (request, response, promise) => {
if (response.ok) {
promise.resolve(response); // bypass
return;
}
if (response.status === 401) {
// - refresh access token
// - reattempt original request with new headers
// - return new response
}
promise.reject(new Error("Unknown error"));
}
};Create a new Client instance, configure it with a base URL, transport, interceptor (if needed) and deafault headers (if needed):
import { Client } from "rest-api-helper";
const client = new Client<Response>("https://api.frankfurter.app")
.setTransport(transport)
.setInterceptor(interceptor)
.setDefaultHeaders({ "content-type": "application/json" });Scaffold request and perform it (you can use predefined classes like Get, Post etc. or create it from scratch using Request):
import { Get, Request } from "rest-api-helper";
const get = new Get("/latest")
.setSearchParam("amount", 10)
.setSearchParam("from", "GBP")
.setSearchParam("to", "USD");
const request = new Request("/latest", "get")
.setSearchParam("amount", 10)
.setSearchParam("from", "GBP")
.setSearchParam("to", "USD");
const response = await client.perform(request);
const parsed = await response.json();As you might have noticed Transport, Interceptor and Client have generic type arguments:
Transport<T>
Interceptor<T>
Client<T>
T defines the shape of each response. Since transport object responsible for performing requests, it dictates the response type. In order to be compatible, Transport, Interceptor and Client should share the same type.
In example described above, we used fetch API that is directly returned from handle method. Thus, generic type is native Response. However, we could easily move response parsing into the transport and replace native Response with something like this:
import { Transport, Request } from "rest-api-helper";
type CustomResponse = {
data: unknown;
status: number;
};
const transport: Transport<CustomResponse> = {
async handle(request: Request) {
const response = await fetch(request.url, request);
const parsed = await reponse.json();
return { data: parsed, status: response.status } as CustomResponse;
}
};
// ...
// const interceptor: Interceptor<CustomResponse> = {...}
// const client = new Client<CustomResponse>(...)readonly url:URLreadonly method:stringreadonly headers:Record<string, string>readonly isInterceptionAllowed:booleanreadonly body:BodyInit | null
constructor(path: string, method: string)Creates a new request with a path and a method (GET, POST, PUT, DELETE etc.).
path: a string that follows the base URL -/users. Can contain URL parameters, e.g./users/:idmethod: a string that represents an HTTP method, e.g. GET, POST, PUT, DELETE. Case-insensitive.
Throws Error if path contains duplicate URL parameters. For example: /users/:id/devices/:id
Appends or overrides an existing header by key
key: a header name, case-insensitivevalue: a header value
Merges passed record with existing one.
headers: an object with key-value pairs, where key is a header name. Keys are case-insensitive
Removes a header by key, if it exists.
key: a header name, case-insensitive
Sets the body of the request.
data: the request body data
A shorthand for setting the body as JSON string, so you don't have to call JSON.stringify yourself.
data: an object or an array of objects
Sets interception flag setting for request. True by default
allowed: a boolean value indicating whether interception is allowed or not
Sets the AbortController for the request so you can manually abort it.
abortController: anAbortControllerinstance
Sets a URL parameter. It will replace the occurrence of :key in the URL path.
key: parameter keyvalue: parameter value
/users/:id -> setUrlParam("id", 2) -> /users/2
setSearchParam(key: string, value: string | number | boolean | Array<string | number | boolean>): Request
Sets a query parameter. It will append the key-value pair to the URL.
key: query parameter keyvalue: query parameter value
setSearchParam("name", "John") -> /users?name=John
setSearchParam("names", ["John", "Alice"]) -> /users?names[]=John&names[]=Alice
setSearchParams(params: Record<string, string | number | boolean | Array<string | number | boolean>>): Request
Sets multiple query parameters. It will append the key-value pairs to the URL.
params: an object with key-value pairs representing the query parameters
setSearchParams({ name: "John", age: 30 }) -> /users?name=John&age=30
setSearchParams({ names: ["John", "Alice"] }) -> /users?names[]=John&names[]=Alice
GetPostPutDeletePatchHead
These subclasses are convenience classes that extend Request and set the method property accordingly.
baseURL:stringdefaultHeaders:Record<string, string>
constructor(baseURL: string)Creates a new Client instance with a base URL.
baseUrl: the base URL for the client
Sets the transport for the client.
transport: aTransportobject implementation
Sets the default headers for the client.
headers: an object with key-value pairs representing the default headers
Sets the interceptor for the client.
interceptor: anInterceptorobject implementation
Performs the given request and returns a response Promise.
request: anyRequestinstance includingPost,Getetc.
interface Transport<T> {
handle(request: Request): Promise<T>;
}The Transport interface defines a single method handle that takes a Request instance and returns a Promise that resolves with the response of T type.
interface Interceptor<T> {
onResponse(request: Request, response: T, promise: OriginalPromise<T>): Promise<void>;
}The Interceptor<T> interface defines a single method onResponse that is called with the request, response, and the original Promise. It can be used to modify the response or handle errors.
request: Request– original request objectresponse: T– received responsepromise: OriginalPromise<T>- original Promise handles (resolveandrejectfunctions)
type OriginalPromise<T> = {
resolve: (value: T) => void;
reject: (reason?: unknown) => void;
};