rest-envelop v1.0.1
Wrapper for working with REST API using well-known axios and node-fetch modules
Content
About
Config Initialization REST clients use Requests execution result More about request options
About The wrapper extends and facilitates the work with modules.
Inspired by the caching implementation in Apollo GraphQL Server, where the keys are urls with parameters, this was added here, but also left the option to specify the key yourself. Additionally, caching is performed only for responses with specific response statuses that you specify. You can use both Memcached and Redis for data storage.
It also has the functionality to repeat requests (retry) when receiving statuses that are not expected to be successful.
Added the ability to create a node-fetch instance like axios.create({})
Added timeout support to node-fetch using AbortController
Config
// config/default.js
const pkg = require('../package.json');
module.exports = {
app: {
name: pkg.name,
version: pkg.version,
env: process.env.NODE_ENV,
},
cacheServices: {
redis: {
port: 6379,
host: '127.0.0.1',
db: 5,
},
memcached: {
servers: ['127.0.0.1:11211'],
options: {
retries: 5,
retry: 5000,
remove: true,
failOverServers: ['127.0.0.1:11214', '127.0.0.1:11215'],
},
},
},
rest: {
jsonPlaceholder: {
schema: 'https',
hostname: 'jsonplaceholder.typicode.com'
}
}
}
Initialization
// helpers/init.js
const config = require('config');
const { Axios, Fetch } = require('rest-envelop');
const { app, cacheServices, rest: { jsonPlaceholder } } = config;
const requestUrl = `${jsonPlaceholder.schema}://${jsonPlaceholder.hostname}`;
const configs = {
// baseURL: requestUrl,
timeout: 1000,
headers: {
'X-Request-Source': `${app.name}:${app.version}`
},
optional: {
environment: app.env,
// requestLog: true,
// createInstance: true,
cacheService: {
cachedStatuses: [200],
redis: cacheServices.redis,
// memcached: cacheServices.memcached,
},
},
};
// who prefers axios
const axios = new Axios(configs);
// who prefers node-fetch
const fetch = new Fetch(configs);
module.exports = {
axios,
fetch,
};
REST clients use
// clients/jsonPlaceholder.js
const config = require('config');
const { axios, fetch } = require('../helpers/init.js')
const { rest: { jsonPlaceholder } } = config;
// if 'createInstance: true' is not specified in init.js, we need the url for the request here
const requestUrl = `${jsonPlaceholder.schema}://${jsonPlaceholder.hostname}`;
// API paths for requests
const paths = {
todos: 'todos',
comments: 'comments',
users: 'users',
posts: 'posts',
};
// cache ttl
const ttl = {
tenMin: 10 * 60,
thirtyMin: 30 * 60,
oneHour: 60 * 60
};
module.exports = {
// USING AXIOS WITHOUT CREATING AN INSTANCE
async todos(){
return axios.request(`${requestUrl}/${paths.todos}`, {
method: 'GET',
params: { completed: true, userId: 7 },
// requestLog: true,
retry: {
attempts: 3,
expectedStatuses: [200, 201],
},
cache: {
ttl: ttl.tenMin,
// cachedStatuses: [200],
// key: `${paths.todos}_${completed}_true_userId_7}`,
},
})
},
// USING AXIOS WITH CREATED AN INSTANCE
async comments(){
return axios.request(`/${paths.comments}`, {
method: 'GET',
params: { postId: 5 },
// requestLog: true,
cache: {
ttl: ttl.thirtyMin,
// cachedStatuses: [200],
// key: `${paths.comments}_postId_5}`,
},
})
},
// USING FETCH WITHOUT CREATING AN INSTANCE
async users(){
return fetch.request(`${requestUrl}/${paths.users}`, {
method: 'GET',
cache: {
ttl: ttl.thirtyMin,
},
})
},
// USING FETCH WITH CREATED AN INSTANCE
async createPost(){
return fetch.request(`/${paths.posts}`, {
method: 'POST',
body: JSON.stringify({
title: 'foo',
body: 'bar',
userId: 1,
}),
headers: {
'Content-Type': 'application/json',
},
})
}
}
Requests execution result The result of the request is a format object:
{
data,
status,
headers
}
- data — response body
- status — http response status code
- headers — response headers (if the data is returned from the cache, then this field is missing)
When you execute requests and logged them in the terminal, you will see the following (the display and format are borrowed from Apollo GraphQL Server):
[/rest-service]
> node index.js
GET 200: https://jsonplaceholder.typicode.com/todos?completed=true&userId=7 (142 ms.)
GET 200: https://jsonplaceholder.typicode.com/comments?postId=5 (43 ms.)
GET 200: https://jsonplaceholder.typicode.com/users? (36 ms.)
POST 201: https://jsonplaceholder.typicode.com/posts? (506 ms.)
If the requests have already been executed and caching is enabled, the logs will be slightly different:
[/rest-service]
> node index.js
GET: https://jsonplaceholder.typicode.com/todos?completed=true&userId=7 (cached)
GET: https://jsonplaceholder.typicode.com/comments?postId=5 (cached)
GET: https://jsonplaceholder.typicode.com/users? (cached)
POST 201: https://jsonplaceholder.typicode.com/posts? (544 ms.)
Usually POST requests are not cached, but if you need it, you can do it, so by passing the necessary parameters for caching
The info below demonstrates a request using the retry option:
[/rest-service]
> node index.js
Attempt 1/3 failed for GET https://jsonplaceholder.typicode.com/todos_?completed=true&userId=7: Request failed with status code 404
Attempt 2/3 failed for GET https://jsonplaceholder.typicode.com/todos_?completed=true&userId=7: Request failed with status code 404
Attempt 3/3 failed for GET https://jsonplaceholder.typicode.com/todos_?completed=true&userId=7: Request failed with status code 404
Failed to fetch after 3 attempts for https://jsonplaceholder.typicode.com/todos_?completed=true&userId=7
It is set for a specific request and looks like this:
{
retry: {
{
attempts: 3,
expectedStatuses: [200, 201],
}
}
How it works: if the API response status does not match the expectedStatuses, then we will make requests in the number of attempts
More about request options You may notice that some options are the same in the instance created in helpers/init.js and in individual requests.
If the option is checked when creating an instance, it means that it will be applied to all requests. If it is not set when creating an instance, but is set for a specific request, then it will work only for this request