Build your own Promise-Based HTTP Client like axios/fetch : Ohey
Shamim Bin Nur
Posted on June 11, 2024
Introduction
Today, I'm excited to share the journey of creating my very own npm package: Ohey. Ohey is a promise-based HTTP client built on top of XMLHttpRequest
. Whether you're new to web development or an experienced coder, understanding how Ohey works will give you insights into building and using HTTP clients in JavaScript. Let's dive into the details of how I built this package!
Why Ohey?
With numerous HTTP clients like Axios and Fetch, you might wonder why I decided to create Ohey. The primary motivation was to learn more about HTTP requests and promises by building something from scratch.
Setting Up the Project
First, I set up a new npm project. You might already know npm, it’s a package manager for JavaScript that allows you to manage your project's dependencies. Here’s how you can set up a new npm project
-
Open your terminal and create a new directory for your project:
mkdir ohey cd ohey
-
Initialize a new npm project:
npm init -y
This creates a package.json
file, which holds the metadata for your project.
Writing the Ohey Function
The core functionality of Ohey lies in the single ohey
function. This function takes an endpoint and an options object and returns a promise that resolves or rejects based on the outcome of the HTTP request. Here’s the complete code for Ohey:
function ohey (
endpoint,
{
method = "GET",
baseUrl = "",
timeout = 0,
body,
headers = { "Content-Type": "application/json" }
} = {}
) {
// Concatenate the baseURL and endpoint
const url = `${baseUrl}${endpoint}`;
// Define a promise and return it.
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
if (timeout > 0) {
setTimeout(() => {
xhr.abort();
reject(new Error("Request timed out"));
}, timeout);
}
// Initialize an HTTP request
xhr.open(method, url, true);
// Etarate through header, extract keys and value.
for (const [key, value] of Object.entries(headers)) {
xhr.setRequestHeader(key, value);
}
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
let responseType = "Text";
try {
responseType = "JSON";
resolve({ data: JSON.parse(xhr.responseText), responseType, responseCode: xhr.status, method });
} catch (error) {
resolve({ data: xhr.responseText, responseType, responseCode: xhr.status, method });
}
} else {
reject(new Error(`Request failed with status: ${xhr.status}`));
}
};
xhr.onerror = () => {
reject(new Error("Network error"));
};
// Send the request with the body parameter
xhr.send(body);
});
};
// Export the function as a module
module.exports = ohey;
Breaking Down the Code
Let's dive deeper into each part of the ohey
function to understand how it works.
Function Declaration and Default Parameters
The ohey
function is declared with two main parameters: endpoint
and an options object with several default values.
function ohey (
endpoint,
{
method = "GET",
baseUrl = "",
timeout = 0,
body,
headers = { "Content-Type": "application/json" }
} = {}
) {
- endpoint: The specific API endpoint you want to hit (e.g., "/users").
- method: The HTTP method to use (default is "GET").
- baseUrl: The base URL for the request (e.g., "https://api.example.com").
- timeout: The request timeout duration in milliseconds (default is 0, meaning no timeout).
- body: The request payload, used primarily for POST, PUT, and PATCH requests.
- headers: An object representing the request headers (default includes "Content-Type: application/json").
Constructing the URL
The full URL for the request is constructed by concatenating the baseUrl
and the endpoint
.
const url = `${baseUrl}${endpoint}`;
Returning a Promise
The ohey
function returns a Promise, which allows asynchronous operations to be handled more gracefully.
return new Promise((resolve, reject) => {
Creating the XMLHttpRequest Object
An instance of XMLHttpRequest
is created to handle the HTTP request.
const xhr = new XMLHttpRequest();
Handling Timeout
If a timeout value is specified, a timer is set to abort the request if it exceeds the given duration.
if (timeout > 0) {
setTimeout(() => {
xhr.abort();
reject(new Error("Request timed out"));
}, timeout);
}
- xhr.abort(): Cancels the request.
- reject: The promise is rejected with a "Request timed out" error.
Configuring the XMLHttpRequest
The HTTP method and URL are set using xhr.open
. The true
parameter indicates that the request is asynchronous.
xhr.open(method, url, true);
Setting Request Headers
Custom headers are added to the request using a for...of
loop to iterate over the headers
object, extract the keys and values, and set them on the request header.
for (const [key, value] of Object.entries(headers)) {
xhr.setRequestHeader(key, value);
}
Handling the Response
The onload
event is triggered when the request completes successfully. It checks the HTTP status code to determine if the request was successful. Any response code 200 to 299 typically means the request has hit the server successfully.
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
let responseType = "Text";
try {
responseType = "JSON";
resolve({ data: JSON.parse(xhr.responseText), responseType, responseCode: xhr.status, method });
} catch (error) {
resolve({ data: xhr.responseText, responseType, responseCode: xhr.status, method });
}
} else {
reject(new Error(`Request failed with status: ${xhr.status}`));
}
};
- xhr.status: The HTTP status code of the response.
-
resolve: If the request is successful, the promise is resolved with the response data.
- responseType: Indicates whether the response is JSON or plain text.
- responseCode: The HTTP status code.
- method: The HTTP method used for the request.
- JSON.parse: Attempts to parse the response as JSON. If parsing fails, the response is returned as plain text.
Handling Network Errors
The onerror
event is triggered if there is a network error. The promise is rejected with a "Network error" message.
xhr.onerror = () => {
reject(new Error("Network error"));
};
Sending the Request
Finally, the request is sent using xhr.send
, with the body
parameter included for methods like POST.
xhr.send(body);
});
}
Exporting the Function
The ohey
function is exported so it can be used in other modules.
module.exports = ohey;
Publishing the Package
To share Ohey with the world, I published it to the npm registry. Here's how you can publish your own package by following these two steps.:
-
Login to npm:
npm login
-
Publish the Package:
npm publish
Note: Make sure there’s no other package available with the same name on the NPM registry.
And that’s it! Your package is now available on npm for others to install and use.
Conclusion
Understanding the code behind Ohey may give you a basic foundation for working with HTTP requests in JavaScript. By building this package, I aimed to create a simple yet functional HTTP client that leverages the flexibility of promises. I hope this breakdown helps you appreciate the inner workings of Ohey and inspires you to create your own projects! Happy coding!
Posted on June 11, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.