relax-client
A super-simple REST client creation tool to encourage custom API clients for every business.
Intention
Every time I stepped into a project, the frontend code was littered with bespoke, individual
fetch
(or axios
, flavor-of-the-week, etc.) requests being made every time the developer
needed to hit the API. When I see that, my eyes cross and I get tired. So I started always
creating custom API clients for every project.
This library aims to make that task a little easier.
relax-client
is a wrapper around fetch that uses middlewares to apply helpful additional
features. The library ships with several middlewares, but writing new ones is trivial.
Usage
npm install --save relax-client
import create, {bearerAuth, json} from 'relax-client'
const client = create(`https://api.test.com/v1`)
.use(bearerAuth(`storage-key`))
.use(json())
export const login = (username, password) => client.post(`login`, {username, password})
export const logout = () => client.get(`logout`)
export const getAccount = () => client.get(`account`)
export const updateAccount = (username) => client.put(`account`, {username})
export const deleteAccount = () => client.destroy(`account`)
import * as client from './api-client'
loginForm.on(`submit`, async function(e) {
e.preventDefault()
const username = loginForm.querySelector(`[name="username"]`).value
const password = loginForm.querySelector(`[name="password"]`).value
const {success} = await client.login(username, password)
if (success) {
const account = await client.getAccount()
}
})
Middleware
Middlewares are added via the use
function. Each middleware function is called with two arguments -
the current request
details and a callback that accepts an update object for those request details.
The callback will return a Promise that ultimately resolves with the response object, and your
middlewares can further process the response.
Depending on what actions your middlewares take, the order can matter. They take effect in the order they
are added for the creation of the response (FILO), and in reverse order for updating the request (LIFO).
import create, {Client, form} from 'relax-client'
function custom() {
return (request, next) => {
console.log(`requesting ${request.url}`)
const response = await next({url: `${request.url}?some-extra-data`})
const text = await response.text()
console.log(`response: ${text}`)
return text
}
}
const {get, post, put, destroy} = create(`https://api.test.com/v1`)
.use(form())
.use(custom())
An individual request can also be configured to use a "one-off" middleware in the event it differs
from the conventions of the API at large (eg. a particularly large request body needs compression).
import create, {json} from 'relax-client'
import gzip from 'relax-client/dist/middlewares/gzip'
const client = create(`https://api.test.com/v1`).use(json())
export const createAccount = (username, criminalRecord) =>
client.post(`account`, {username, criminalRecord}, { middleware: gzip() })
export const getAccount = () => client.get(`account`)
Bearer Auth
The bearer auth middleware can be used to automatically configure all requests to the API with
authentication headers. By default, it uses the Authorization
header and stores auth tokens
in localStorage
, but can be configured according to your use case.
import create, {bearerAuth} from 'relax-client'
const authMiddleware = bearerAuth(`storage-key`)
const authMiddleware = bearerAuth(`storage-key`, {store: bearerAuth.THIS_SUBDOMAIN})
const authMiddleware = bearerAuth(`storage-key`, {store: 'localstorage'})
const authMiddleware = bearerAuth(`storage-key`, {store: bearerAuth.ALL_SUBDOMAINS})
const authMiddleware = bearerAuth(`storage-key`, {store: 'cookie'})
const authMiddleware = bearerAuth(`storage-key`, {store: bearerAuth.THIS_SESSION})
const authMiddleware = bearerAuth(`storage-key`, {store: 'memory'})
const authMiddleware = bearerAuth(`storage-key`, {header: `X-Auth-Token`})
const client = create(`https://api.test.com/v1`).use(authMiddleware)
await client.post('/login', {username, password})
const res = await client.get('/account')
The store config value can also be an object with functions read
and write
. In that case, read
will take the storage key as an argument, and write
will take both the storage key and token value.
Either or both functions can return a simple value or a promise. In the example below, some tokens
service is responsible both for generating the key that's used and for reading / writing to some
other custom storage location.
import create, {bearerAuth} from 'relax-client'
import tokens from './services/tokens'
const authMiddleware = bearerAuth(tokens.getKey(), {
store: {
read: (k) => tokens.get(k),
write: (k, v) => tokens.put(k, v),
}
})
Gzip
A gzip middleware for compressing request bodies is available, but it uses Pako, which is too large
a library to include by default. Given that, it's not exposed from the index file, so it will need
to be explicitly loaded from the dist directory.
import create, {Client, json} from 'relax-client'
import gzip from 'relax-client/dist/middlewares/gzip'
const {get, post, put, destroy} = create(`https://api.test.com/v1`)
.use(json())
.use(gzip())