An Axios Based Package and Promise based HTTP client for the browser and node.js.
Axios is an awesome library, and provides you with great features, however, there are some missing features that is needed in most of our real world projects.
- Everything that axios provides.
- Easily convert
PUT
requests toPOST
requests with_method=PUT
appended to the request body. - Easily set
Authorization
header for all requests. - Easily create Restful API endpoints with a single class.
- Properly guides you to create your endpoints functions.
- Empowers Cache Management.
- Easily abort requests.
- Easily listen to endpoint events, before sending, on success, on error, and on complete.
yarn add @mongez/http
Or
npm i @mongez/http
Let's create a new Endpoint instance to handle our requests:
For demonstration purpose only, we may use React syntax for illustration when dealing with forms.
// src/endpoints.ts
import Endpoint from "@mongez/http";
export const endpoint = new Endpoint({
baseURL: "https://jsonplaceholder.typicode.com",
});
endpoint
.post("/login", {
email: "hassanzohdy@gmail.com",
password: "0000000",
})
.then((response) => {
//
});
Now we can use this endpoint to make requests from any service files or even components.
The Endpoint
class provides you with a set of methods to handle your requests, also it accepts Axios Configurations besides the configurations below:
import { AxiosRequestConfig } from "axios";
export type EndpointConfigurations = AxiosRequestConfig & {
/**
* If set to true, all PUT requests will be transformed to POST requests with ${putMethodKey} = PUT value will be appended.
*
* @default false
*/
putToPost?: boolean;
/**
* Defines the put key that will be added to post requests.
* Works only if `putToPost` is set to true and you send a `put` request
* The send value is `PUT`
*
* @default _method
*/
putMethodKey?: string;
/**
* Set authorization header
*
* Useful when using Key and Bearer Tokens
*/
setAuthorizationHeader?: string | (() => string);
};
In the above configurations, there are some interesting configurations that you may need to use such as putToPost
and setAuthorizationHeader
.
Why? because PUT requests won't allow sending files whereas post requests do it, so in some backend frameworks like Laravel has a nice workaround that allows you to send a post request and it handles it as put request.
If you're using Laravel or any app that does not allow uploading files using PUT
request method, so you need to send a POST request with _method=PUT
appended to the request body, and this is what putToPost
does.
You can also change the key of the appended data by changing the putMethodKey
value, which defaults to _method
.
If your backend api requires Authorization
header in every request, You may set Authorization header from configurations either as a string or as a callback,
import Endpoint from "@mongez/http";
export const endpoint = new Endpoint({
baseURL: "https://api.sitename.com/v1",
setAuthorizationHeader: () => {
if (user.isLoggedIn()) {
return `Bearer ${user.getAccessToken()}`;
}
return "key some-api-key";
},
});
Or you may set it directly as string, for example if the api only accept key
authorization header:
import Endpoint from "@mongez/http";
export const endpoint = new Endpoint({
baseURL: "https://api.sitename.com/v1",
setAuthorizationHeader: "key some-api-key",
});
The setAuthorizationHeader
configuration will be called before each request so it should have the proper value to be sent as it won't cache the initial value unless it is string.
In some situations, such as Admin Dashboard, there would be pages that implements CRUD Operations, thankfully, this can be done easily with RestfulEndpoint
class, which you provide the route of the CRUD requests, and it handles all Restful API.
// src/services/users-service.ts
import { endpoint } from "./endpoints";
import { RestfulEndpoint } from "@mongez/http";
class UsersService extends RestfulEndpoint {
/**
* {@inheritDoc}
*/
public route: string = "/users";
/**
* Endpoint handler
*/
public endpoint = endpoint;
}
const usersService: UsersService = new UsersService();
export default usersService;
From this point we can now use our usersService
object to list
get
create
update
delete
patch
or publish
If you're application uses only one endpoint, you can set the current endpoint instance, this will allow all RestfulEndpoint
instances to use the same endpoint instance unless you explicitly sets the endpoint
property.
// endpoints.ts
import Endpoint, { setCurrentEndpoint } from "@mongez/http";
export const endpoint = new Endpoint({
baseURL: "https://jsonplaceholder.typicode.com",
});
setCurrentEndpoint(endpoint);
Now we can use the RestfulEndpoint
class without setting the endpoint
property.
// src/services/users-service.ts
import { RestfulEndpoint } from "@mongez/http";
class UsersService extends RestfulEndpoint {
/**
* {@inheritDoc}
*/
public route: string = "/users";
}
const usersService: UsersService = new UsersService();
export default usersService;
Of course you can get the current endpoint instance using getCurrentEndpoint
method.
import { getCurrentEndpoint } from "@mongez/http";
const endpoint = getCurrentEndpoint();
// endpoint is the same instance that we set in the above example
To get a list of records, we can use list
method which is defined by default in RestfulEndpoint
class.
// src/index.ts
import usersService from "./services/users-service";
// list users without any params sent
// request: GET /users
usersService.list().then((response) => {
//
});
We may also send params as a query string to the request as well
// src/index.ts
import usersService from "./services/users-service";
// request: GET /users?paginate=true&itemsPerPage=15
const params: object = {
paginate: true,
itemsPerPage: 15,
};
usersService.list(paramsList).then((response) => {
//
});
To get a single record, use get
method.
// src/index.ts
import usersService from "./services/users-service";
// get user information
const userId: number = 1;
// request: GET /users/1
usersService.get(userId).then((response) => {
//
});
// get user with additional params sent with the request
// request: GET /users/1?active=true
usersService
.get(userId, {
active: true,
})
.then((response) => {
//
});
We may also send additional params with the single record as a query string.
// src/index.ts
import usersService from "./services/users-service";
// get user information
const userId: number = 1;
const params: object = {
active: true,
};
// get user with additional params sent with the request
// request: GET /users/1?active=true
usersService.get(userId, params).then((response) => {
//
});
Creating a new record can be done from the endpoint service using create
method.
Check acceptable types of data at Acceptable Http Data Section.
// src/index.ts
import usersService from "./services/users-service";
const data: object = {
email: "hassanzohdy@gmail.com",
password: "123456789",
confirmPassword: "123456789",
};
// POST /users
usersService.create(data).then((response) => {
// user request is created successfully.
});
Updating an existing record is also can be done using update
method.
Check acceptable types of data at Acceptable Http Data Section.
// src/index.ts
import usersService from "./services/users-service";
const data: object = {
email: "hassanzohdy@gmail.com",
password: "123456789",
confirmPassword: "123456789",
};
const id: number = 1;
// PUT /users/1
usersService.update(id, data).then((response) => {
// user resource is updated successfully.
});
Creating a small updates on records can be done use patch
method.
// src/index.ts
import usersService from "./services/users-service";
const data: object = {
published: true,
};
const id: number = 1;
// PATCH /users/1
usersService.patch(id, data).then((response) => {
// user resource is patched successfully.
});
A smaller method that allow you to publish/un-publish records using publish
method.
// src/index.ts
import usersService from "./services/users-service";
const isPublished: boolean = true;
const id: number = 1;
// PATCH /users/1
// request payload: { published: true }
usersService.publish(id, isPublished).then((response) => {
// user resource is patched successfully.
});
You may change the published
key to another key by passing your desired key in the third argument.
// src/index.ts
import usersService from "./services/users-service";
const isActivated: boolean = true;
const id: number = 1;
// PATCH /users/1
// request payload: { activated: true }
usersService.publish(id, isActivated, "activated").then((response) => {
// user resource is patched successfully.
});
Or you can set it globally when creating the endpoint instance.
// src/endpoints.ts
import Endpoint from "@mongez/http";
export const endpoint = new Endpoint({
baseURL: "https://jsonplaceholder.typicode.com",
publishKey: "isActive",
});
Our final method in the Restful API concept is to delete a resource/record.
// src/index.ts
import usersService from "./services/users-service";
const id: number = 1;
// DELETE /users/1
usersService.delete(id).then((response) => {
// deleted successfully.
});
This feature allows you to perform a bulk delete on a list of records, it will call the resource path with DELETE
method and send the ids as an array of ids.
// src/index.ts
import usersService from "./services/users-service";
const ids: number[] = [1, 2, 3];
// DELETE /users
usersService
.bulkDelete({
id: ids,
})
.then((response) => {
// deleted successfully.
});
In some cases we may need to create custom methods that can be used later in our project.
// src/services/users-service.ts
import { RestfulEndpoint } from "@mongez/http";
class UsersService extends RestfulEndpoint {
/**
* {@inheritDoc}
*/
public route: string = "/users";
/**
* Get active members only
*/
public listActive() {
return this.endpoint.get(this.path("/active"));
}
}
const usersService: UsersService = new UsersService();
export default usersService;
In the previous example, we created a new method listActive
which calls endpoint instance this.endpoint
and pass to it a path
method, this method concatenate the given argument with the basic route to generate another route, in the previous example the final route will be /users/active
.
For POST
PUT
requests, there are four acceptable formats of data:
-
object
: which will send the request as json. -
HTMLFormElement
which accepts an instance of HTMLFormElement and request data will be sent as form data. -
FormData
which accepts an instance of FormData and request data will be sent as form data. -
string
which will be sent as a string.
Let's see an example for each format.
In the next example, we'll see how to use an example of sending post request using plain object.
If data is sent as plain object, then a request header
"Content-Type": "Application/json"
will be added to headers by default.
import endpoint from "./endpoints";
const data: object = {
email: "hassanzohdy@gmail.com",
password: "123456789",
};
endpoint.post("/login", data).then((response) => {
//
});
In the next example, we'll see how to use an example of sending post request using form element.
// Form.tsx
import React from "react";
import endpoint from "./endpoints";
export default function MyForm() {
const submitForm = (e) => {
e.preventDefault();
const formElement: HTMLFormElement = e.target;
endpoint.post("/login", formElement).then((response) => {
//
});
};
return (
<form onSubmit={submitForm}>
<input name="email" type="email" />
<input name="password" type="password" />
</form>
);
}
In the next example, we'll see how to use an example of sending post request using form data.
// Form.tsx
import React from "react";
import endpoint from "./endpoints";
export default function MyForm() {
const submitForm = (e) => {
e.preventDefault();
const formElement: HTMLFormElement = e.target;
const formData = new FormData(formElement);
endpoint.post("/login", formData).then((response) => {
//
});
};
return (
<form onSubmit={submitForm}>
<input name="email" type="email" />
<input name="password" type="password" />
</form>
);
}
You can listen to events on the endpoint instance, the events are:
-
beforeSending
: will be fired before sending the request. -
onSuccess
: will be fired when the request is successful. -
onError
: will be fired when the request is failed. -
onComplete
: will be fired when the request is finished wether success or failed requests.
import endpoint from "./endpoints";
import { AxiosRequestConfig, AxiosResponse } from "axios";
import { EventSubscription } from "@mongez/events";
// This is triggered before sending any request
endpoint.events.beforeSending(
(requestConfig: AxiosRequestConfig): EventSubscription => {
// do something
}
);
// This is triggered when the request is successful
endpoint.events.onSuccess((response: AxiosResponse): EventSubscription => {
// do something
});
// This is triggered when the request is failed
endpoint.events.onError((response: AxiosResponse): EventSubscription => {
// do something
});
// This is triggered when the request is finished wether success or failed requests
endpoint.events.onComplete((response: AxiosResponse): EventSubscription => {
// do something
});
The onComplete
event will be triggered before onSuccess
and onError
events.
You can abort a request by getting the last request instance using getLastRequest
method which is an instance of AbortController.
import endpoint from "./endpoints";
endpoint.get("/users").then((response) => {
// do something
});
const lastRequest = endpoint.getLastRequest();
lastRequest.abort();
Of course you can use the original Axios signal
property to abort the request.
import endpoint from "./endpoints";
const abortController = new AbortController();
endpoint
.get("/users", {
signal: abortController.signal,
})
.then((response) => {
// do something
});
// Anywhere in your code
abortController.abort();
Added in 2.1.0
You can now easily cache your get
requests, to do so, you need to pass the cache
option to the request method.
import endpoint from "./endpoints";
endpoint
.get("/users", {
cache: true,
})
.then((response) => {
// do something
});
By default request will be cached for 5 minutes, you can change this by passing the cacheTime
option.
import endpoint from "./endpoints";
endpoint
.get("/users", {
cache: true,
cacheOptions: {
ttl: 10 * 60, // 10 minutes
},
})
.then((response) => {
// do something
});
Here we defined the cache time to be 10 minutes.
However, we need to define the cache driver that will contain the cached data, to do so, you need to define the driver
property in cache options as well.
You can easily use any cache driver here or the cache manger directly.
import endpoint from "./endpoints";
import cache from "@mongez/cache";
endpoint
.get("/users", {
cache: true,
cacheOptions: {
driver: cache,
ttl: 10 * 60, // 10 minutes
},
})
.then((response) => {
// do something
});
Using Run Time Driver will cache the data until the user refreshes the page regardless of the cache time, so you you may use it directly if you want to save it in the run time.
import endpoint from "./endpoints";
import { RunTimeDriver } from "@mongez/cache";
endpoint
.get("/users", {
cache: true,
cacheOptions: {
driver: new RunTimeDriver(),
ttl: 10 * 60, // 10 minutes
},
})
.then((response) => {
// do something
});
You can also set the default cache options for all requests by passing the cacheOptions
property to the endpoint instance and cache
flag.
// src/endpoints.ts
import Endpoint from '@mongez/http';
import cache from '@mongez/cache';
export const endpoint = new Endpoint({
baseURL: 'https://jsonplaceholder.typicode.com',
cache: true, // enable cache for all get requests
cacheOptions: {
ttl: 10 * 60 // 10 minutes
driver: cache,
}
});
The cache driver MUST implement the CacheDriverInterface.
If you're still using Version 1, you can see its documentation in Version 1 Documentation Section.
- 2.2.4 (16 Apr 2023)
- Updated Dependencies.
- Now
setAuthorizationHeader
if didn't return a value theAuthorization
header will not be added.
- 2.2.0 (28 Feb 2023)
- Updated dependencies.
- Fixed cache options
- 2.1.0 (07 Nov 2022)
- Added caching support.
- 2.0.0 (19 Sept 2022)
- Released Version 2.
- 1.0.22 (1 Feb 2022)
- Fixed Incorrect base url concatenation with request config url.
- 1.0.21 (31 Jan 2022)
- Fixed lastRequest incorrect Cancel Token Clone.
- Added
LastRequest
as return type tolastRequest()
function.