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-helper
or yarn:
yarn add rest-api-helper
To 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 (resolve
andreject
functions)
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
:URL
readonly method
:string
readonly headers
:Record<string, string>
readonly isInterceptionAllowed
:boolean
readonly 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/:id
method
: 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
: anAbortController
instance
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
Get
Post
Put
Delete
Patch
Head
These subclasses are convenience classes that extend Request
and set the method
property accordingly.
baseURL
:string
defaultHeaders
: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
: aTransport
object 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
: anInterceptor
object implementation
Performs the given request and returns a response Promise.
request
: anyRequest
instance includingPost
,Get
etc.
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 (resolve
andreject
functions)
type OriginalPromise<T> = {
resolve: (value: T) => void;
reject: (reason?: unknown) => void;
};