Mongez Http, A Axios Based package to manage http requests better than you do in your projects
Hasan Zohdy
Posted on September 2, 2022
Introduction
As mentioned in the title, the [Mongez http package)(https://github.com/hassanzohdy/mongez-http) is Axios based library to manage your ajax requests (and node js as well) but in a professional manner.
This package is built on Axios so if you're good with Axios you will be familiar with the rest of the article.
Installation
Using npm
npm i @mongez/http
Using Yarn
yarn add @mongez/http
Once the installation is done, create a http-config.ts
file in your project (or .js, typescript is recommended though) so we can set our configurations.
import the file in an early stage of your project, at the start of the
src/index.ts
for example or in your main project file entry.
Let's define our config file.
// http-config.ts
import { setHttpConfigurations } from '@mongez/http';
setHttpConfigurations({
baseUrl: 'https://jsonplaceholder.typicode.com',
});
For the demonstration purpose, we will use JSON Placeholder API so you can see actual results.
We're good for the time being, now let's try to make some nice requests.
You can follow up with what's going on in Sandbox
The endpoint handler
Once we set our configurations, now we can use our endpoint
handler which can be used anywhere in your application.
What is endpoint handler? basically an axios instance but with some modifications.
To make our project clean and more structured let's create a services
directory in your src project, inside it we'll add todo-service.ts
file.
// todo-service.ts
import endpoint from "@mongez/http";
export function getTodoList() {
return endpoint.get("/todo");
}
So what we've done is we imported the endpoint handler and exported a function to get the Todo list.
As you can see, we just added in the get method only the relative path to the base api url /todo
so you don't have to put your base url in every request.
Usage
Now we've set our configurations, handled our services, let's use it.
in your App.ts
or if you're using react in the useEffect
hook call the endpoint service.
// App.ts
import { getTodoList } from 'src/services/todo-service';
// let's make an ajax call
getTodoList().then((response) => {
console.log(response.data);
});
Posting data
We've seen how to work with GET requests, now let's see how to deal with POST requests.
For demo only i'll use React to make a simple create account pages.
// RegisterPage.tsx
export default function RegisterPage() {
return (
<>
<form>
<input name="name" type="text" placeholder="Your Name" />
<br />
<input name="email" type="email" placeholder="Email Address" />
<br />
<input name="password" type="password" placeholder="Password" />
<br />
<button>Create Account</button>
</form>
</>
);
}
We just made a simple basic ui to preview the create account form, now to submit the form, we need to get the data that will be sent, but before that let's make the auth-service.ts
file in our services directory.
// src/services/auth-service.ts
import endpoint from "@mongez/http";
export function register(data: any) {
return endpoint.post('/users', data);
}
We create the register handler now let's head back again to our ui.
// RegisterPage.tsx
import { useState } from "react";
import { register } from "./services/auth-service";
export default function RegisterPage() {
const [data, setData] = useState({
email: "",
name: "",
password: ""
});
const createAccount = (e: any) => {
e.preventDefault();
register(data).then((response) => {
console.log(response.data);
});
};
return (
<>
<form onSubmit={createAccount}>
<input
name="name"
onChange={(e) => {
setData({
...data,
name: e.target.value
});
}}
type="text"
placeholder="Your Name"
/>
<br />
<input
onChange={(e) => {
setData({
...data,
email: e.target.value
});
}}
name="email"
type="email"
placeholder="Email Address"
/>
<br />
<input
onChange={(e) => {
setData({
...data,
password: e.target.value
});
}}
name="password"
type="password"
placeholder="Password"
/>
<br />
<button>Create Account</button>
</form>
</>
);
}
We made a simple state to store our form data inside it so we can send the object to the server, after submission you'll see console logging the same data that we filled as the api server returns the same data with an id, usually 11
.
Form Data And Uploads
That was okay and good, but what if we want to upload files or images? in that case we will have to use FormData, however, this is not necessary as the package handles this for you, le'ts enhance our code a little bit.
Please note this will not work as JSON Placeholder accepts only json but not form-data requests.
// RegisterPage.tsx
import { register } from "./services/auth-service";
export default function RegisterPage() {
const createAccount = (e: any) => {
e.preventDefault();
// as you can see we skipped the state to store the data as we'll fetch it from the form inputs directly
const formData = new FormData(e.target);
register(formData).then((response) => {
console.log(response.data);
});
};
return (
<>
<form onSubmit={createAccount}>
<input
name="name"
type="text"
placeholder="Your Name"
/>
<br />
<input
name="email"
type="email"
placeholder="Email Address"
/>
<br />
<input
name="password"
type="password"
placeholder="Password"
/>
<br />
<button>Create Account</button>
</form>
</>
);
}
Now we've sent a form data request, as mentioned earlier it can be done easier by just passing the form element The e.target element
directly to the endpoint.
// RegisterPage.tsx
import { register } from "./services/auth-service";
export default function RegisterPage() {
const createAccount = (e: any) => {
e.preventDefault();
register(e.target).then((response) => {
console.log(response.data);
});
};
return (
<>
<form onSubmit={createAccount}>
<input
name="name"
type="text"
placeholder="Your Name"
/>
<br />
<input
name="email"
type="email"
placeholder="Email Address"
/>
<br />
<input
name="password"
type="password"
placeholder="Password"
/>
<br />
<button>Create Account</button>
</form>
</>
);
}
Now we handled posting data to serer using three methods:
- Passing
Plain
objects. - Passing
FormDta
objects. - Passing
FormElement
objects.
The Restful Endpoint
Another powerful feature of Mongez Http is the Restful class
that manages multiple requests within a class.
Let's head back to our todo-service.ts again and add a Restful endpoint class to mange it.
// todo-service.ts
import endpoint, { RestfulEndpoint } from "@mongez/http";
class TodoService extends RestfulEndpoint {
/**
* {@inheritDoc}
*/
public route = "/todos";
}
const todoService: TodoService = new TodoService();
export default todoService;
// will be kept only for the demo
export function getTodoList() {
return endpoint.get("/todos");
}
Now what was that?, well, basically the RestfulEndpoint
class manages the main restful api requests in 6 major methods
-
list(queryStringParams: any)
: to get list of data, will make GET/todos
request. -
get(id: number | string)
: to get single record/data. will make GET/todos/:id
request. -
create(data: any)
: create a new record, will make POST/todos
request. -
update(id: number | string, data: any)
: update existing record, will make PUT/todos/:id
request. -
patch(id: number | string, data: any)
: perform patches over a record, will make PATCH/todos/:id
request. -
delete(id: number | string)
: delete record, will make DELETE/todos/:id
request.
let's see how to use it in our real world code.
// TodoList.tsx
import { useEffect, useState } from "react";
import todoService from 'src/services/todo-service';
export default function TodoList() {
const [items, setItems] = useState([]);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
todoService.list().then((response) => {
setItems(response.data);
console.log(response.data);
setLoading(false);
});
}, []);
if (isLoading) return <h1>Loading....</h1>;
return (
<>
{items.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</>
);
}
We just called the list
method to make a request,
We can also defines query params to the list request by passing an object to it, for example
todoService.list({
limit: 15,
active: 1,
}); // will make GET /todos?limit=15&active=1
To get a single record, the service.get()
method will achieve this for you.
const todoId = 1;
todoService.get(todoId).then(response => {
console.log(response.data);
});
Aborting Requests
Another good feature is whether you want to catch the request in a variable so you may abort/cancel it anytime you like, for example when sending request in useEffect, we can use the lastRequest
feature to achieve this.
// TodoList.tsx
import { lastRequest } from "@mongez/http";
import { useEffect, useState } from "react";
import todoService from 'src/services/todo-service';
export default function TodoList() {
const [items, setItems] = useState([]);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
todoService.list().then((response) => {
setItems(response.data);
console.log(response.data);
setLoading(false);
});
.catch((error) => {
console.log(error);
});
// catch the request
let request = lastRequest();
return () => {
request.abort();
};
}, []);
if (isLoading) return <h1>Loading....</h1>;
return (
<>
{items.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</>
);
}
Put To Post
In some languages such as PHP, you can not directly upload files or images using PUT
request method, alternatively some frameworks like Laravel made a workaround to solve this issue by making POST
request with _method
key paired with PUT
as its value so you can listen to the request as PUT
request, this also can be the same here by setting the putToPost
key in the http configurations to true but your code will still the same.
Before using the request will be something like
// some-service.ts
import endpoint from '@mongez/http';
export function updateProfile(data: any) {
return endpoint.put('/me', data);
}
updateProfile({
username: 'hasan',
password: '123'
});
// PUT /me request and { username: hasan, password: 123 } as data
Now let's active the flag.
// http-config.ts
import { setHttpConfigurations } from '@mongez/http';
setHttpConfigurations({
baseUrl: 'https://jsonplaceholder.typicode.com',
putToPost: true,
});
After activating it
// some-service.ts
import endpoint from '@mongez/http';
export function updateProfile(data: any) {
return endpoint.put('/me', data);
}
updateProfile({
username: 'hasan',
password: '123'
});
// PUT /me request and { username: hasan, password: 123, _method: PUT } as data
So you won't change anything in your code, and if your api ever would neglect this feature and allow direct uploads, you just disable the flag and we're all good.
HTTP Events / Interceptors
The last feature that i'd like to talk about is The request events or interceptors, this is extremely helpful way to manipulate the requests before it is sent or even the response when it is done.
This can be done using endpointEvents
handler, it provides 4 good interceptors to work with.
-
beforeSending
interceptor: triggered before sending the request and allows you to modify any request before it's started, like adding additional headers, modify data and so on. -
onSuccess
interceptor: triggered after the response is sent and its a success response with status code like200
or200
, can be used to modify the response data before you use it in your actual code for example before using it in your component calls. -
onError
interceptor: triggered after the response is sent and its a failure response with status code like400
or500
, this can be useful with401
requests if the user has no longer access to the project at anytime you can log the user off. -
onResponse
interceptor: triggered after the response is sent wether it is success or failed, can be used for requests logging or whatever you want.
The beforeSending
interceptor receives the axios request config for its callback, we can for example set the Authorization
header.
import { AxiosRequestConfig } from "axios";
import { AxiosRequestConfig } from "axios";
import { endpointEvents } from '@mongez/http';
// This is triggered before sending any request
endpointEvents.beforeSending((config: AxiosRequestConfig): EventSubscription => {
config.headers.Authorization = 'key xxx';
});
Conclusion
The package main purpose is to make your code organized, well structured and easy to maintain or scale, as mentioned earlier its based on Axios so any feature you might need to use in Axios can be used here as well.
For more detailed documentation about the package, feel free to visit the Github repository.
I hope you enjoy the package and any feedback is appreciated.
Enjoy and happy coding.
Posted on September 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.