DEV Community

Hasan Zohdy
Hasan Zohdy

Posted on

Mongez Http, A Axios Based package to manage http requests better than you do in your projects

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',    
});
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now we handled posting data to serer using three methods:

  1. Passing Plain objects.
  2. Passing FormDta objects.
  3. 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");
}
Enter fullscreen mode Exit fullscreen mode

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>
      ))}
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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>
      ))}
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Now let's active the flag.

// http-config.ts

import { setHttpConfigurations } from '@mongez/http';

setHttpConfigurations({
    baseUrl: 'https://jsonplaceholder.typicode.com',    
    putToPost: true,
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

  1. 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.
  2. onSuccess interceptor: triggered after the response is sent and its a success response with status code like 200 or 200, 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.
  3. onError interceptor: triggered after the response is sent and its a failure response with status code like 400 or 500, this can be useful with 401 requests if the user has no longer access to the project at anytime you can log the user off.
  4. 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';
});
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)