@mongez/http
TypeScript icon, indicating that this package has built-in type declarations

2.2.10 • Public • Published

Mongez Http

An Axios Based Package and Promise based HTTP client for the browser and node.js.

Why not using axios directly instead?

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.

Features

  1. Everything that axios provides.
  2. Easily convert PUT requests to POST requests with _method=PUT appended to the request body.
  3. Easily set Authorization header for all requests.
  4. Easily create Restful API endpoints with a single class.
  5. Properly guides you to create your endpoints functions.
  6. Empowers Cache Management.
  7. Easily abort requests.
  8. Easily listen to endpoint events, before sending, on success, on error, and on complete.

Installation

yarn add @mongez/http

Or

npm i @mongez/http

Usage

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.

Configurations

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.

Converting Put requests to Post requests

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.

Setting Authorization Header

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.

Restful Endpoint

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

Current Endpoint

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

List records

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) => {
  //
});

Get single record

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) => {
  //
});

Create new record

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.
});

Update Record

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.
});

Patching Record

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.
});

Publishing records

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",
});

Deleting Record

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.
});

Bulk Delete

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.
  });

Creating Custom methods

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.

Acceptable Http Data

For POST PUT requests, there are four acceptable formats of data:

  1. object: which will send the request as json.
  2. HTMLFormElement which accepts an instance of HTMLFormElement and request data will be sent as form data.
  3. FormData which accepts an instance of FormData and request data will be sent as form data.
  4. string which will be sent as a string.

Let's see an example for each format.

Object Data 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) => {
  //
});

HTML Form Element Format

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>
  );
}

Form Data Format

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>
  );
}

Endpoint Events

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.

Aborting Requests

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();

Caching

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.

Legacy Version 1 Documentation

If you're still using Version 1, you can see its documentation in Version 1 Documentation Section.

Change Log

  • 2.2.4 (16 Apr 2023)
    • Updated Dependencies.
    • Now setAuthorizationHeader if didn't return a value the Authorization 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 to lastRequest() function.

Package Sidebar

Install

npm i @mongez/http

Weekly Downloads

67

Version

2.2.10

License

MIT

Unpacked Size

112 kB

Total Files

58

Last publish

Collaborators

  • hassanzohdy