How to Create a Basic Reverse Proxy

November 30, 2024 (1mo ago)

Hey anon đź‘‹ Welcome to my blog!

I'm really excited to share this with you—it's my very first blog post! 🎉 This one is based on a project i created a few months ago.

As the title suggests, today we're diving into Reverse Proxies. Now, you might be thinking, "What even are proxies?" or "How do they work?" or maybe even "Why would I need one?" Don't worry—I've got you covered! I'll break it all down for you, step by step.

In this post, you'll learn:

So, Let's get started! 🚀


So, what even is a Proxy?

Before diving into reverse proxies, let's first understand what a proxy is. At its core, a proxy is like a middleman that stands between two entities—in most cases, a client (like your browser) and a server (where websites or applications live). Instead of the client directly talking to the server, the proxy handles the communication.

Types of Proxies

There are two main types of proxies you'll come across:

Forward Proxy

A forward proxy sits in front of the client. It takes requests from the client, sends them to the server, and then returns the server's response to the client. Forward proxies are commonly used for:

Reverse Proxy

A reverse proxy, on the other hand, sits in front of the server. When a client sends a request, it goes to the reverse proxy first, which then forwards it to the appropriate server. The server’s response is then sent back to the client via the proxy.

A reverse proxy acts like a gatekeeper, deciding how and where requests should be handled. It provides several advantages, which we'll explore next.

Why Use a Reverse Proxy?

Reverse proxies are incredibly powerful and have become a staple in modern web infrastructure. Here are some key use cases:

TL;DR

A forward proxy acts as an intermediary for the client, helping it communicate with the server. A reverse proxy, on the other hand, manages client requests on behalf of the server. Now that you know what proxies are and how they work, it's time for the fun part, building one from scratch! Let's dive in! 🚀


It's BUILD Time!

We'll be building our reverse proxy using Node.js for the runtime, Express to create a server and Commander to handle command-line functionality.

Step 1: Prerequisites

First, ensure you have Node.js and npm installed on your system. If not, you can download them from nodejs.org.

Step 2: Set Up Your Project

Next, create a directory, cd into it and initialize a node project:

mkdir reverse-proxy
cd reverse-proxy
npm init -y

This creates a new package.json file with default values, which we'll use to manage dependencies.

Step 3: Install Dependencies

Install the required libraries using npm:

npm i express commander

Step 4: Configure the bin Field

To turn this into a proper CLI tool, we need to configure the bin field in package.json. Add this section below the dependencies:

"bin": {
    "reverse-proxy": "./bin/index.js"
  }

What's the bin Field?

When building a CLI tool, the bin field allows users to run your command directly from the terminal, without needing to specify the full script path. Here's what it does:

Once this is set up, users can run your tool by typing reverse-proxy in the terminal.

Step 5: Organize Your Project

  1. Create a folder named bin
  2. Inside the bin folder, create a file named index.js

And that's it for the setup! Now, we'll dive into writing the actual logic.


1. Shebang and Imports

At the very top of the file, we add:

#!/usr/bin/env node
import { Command } from "commander";
import express from "express";

2. Setup for Commander and Express

Next, we initialize the necessary modules:

const program = new Command();
const app = express();
let currentURL = "";

3. Defining the CLI Command

We use Commander to configure our CLI tool:

program
  .name("reverse-proxy")
  .description("A Proxy Server CLI")
  .version("1.0.0");

4. Adding CLI Options

We specify the options that users can pass to the CLI.

program
  .option("-p, --port <port>", "port number")
  .option("-o, --origin <origin_url>", "origin URL")
  .description("Start a Proxy Server");

The above code defines CLI options for --port (proxy server's port) and --origin (target URL), both required to start the proxy. The last line defines the description for the command.

5. Defining the Logic

.action((options) => {
    const port = options.port;
    const origin = options.origin;
 
    if (!port) {
      console.error("Required Argument --port <port_number>");
      return;
    }
 
    if (!origin) {
      console.error("Required Argument --origin <origin_url>");
      return;
    }
 
    const portNum = parseInt(port);
    currentURL = origin;
  });

Now, add a method called 'action' to the above chain of methods and add this code to it. What's happening here is we have created two constants port and origin taking values from the options parameter. The options variable is what captures the values we enter when we run the command.

Next, we just check if port and origin exists or not. if they don't, an error message is printed and command execution is stopped. Then, The port value is converted to an integer using parseInt() and stored in portNum for further use. The origin URL is stored in currentURL, which will be used later in the proxy logic.

Now, inside the action method itself, add the following code.

app.use("*", async (req, res) => {
  const targetUrl = currentURL + req.baseUrl;
 
  try {
    const response = await fetch(targetUrl, {
      method: req.method,
      headers: req.headers,
      body: req.method !== "GET" ? req.body : undefined,
    });
 
    const body = await response.text();
    res.status(response.status).send(body);
  } catch (error) {
    console.log(error);
    res.status(500).send({
      error: "Failed to fetch the resource from the target URL",
      details: error.message,
    });
  }
});

6. Starting the Server

Still inside the action method, we start the Express server on port portNum.

app.listen(portNum, () => {
  console.log(
    `Reverse Proxy started for URL: ${currentURL} at http://localhost:${portNum}`
  );
});

7. Parsing the CLI Input

Add this line at the bottom of the code.

program.parse();

This processes the command-line input and triggers the appropriate action defined in the program.action block


Example Usage

reverse-proxy --port 8080 --origin https://jsonplaceholder.typicode.com

With this, your reverse proxy is live, forwarding requests from http://localhost:3000 to https://jsonplaceholder.typicode.com.


Conclusion

And it's DONE! 🎉 Congratulations on building your very own Reverse Proxy from scratch. You've not only gained a deeper understanding of how proxies work, but you've also written a fully functional CLI tool using Node.js, Express, and Commander.

The full codebase is available on my GitHub Repo. Feel free to explore it, suggest improvements, or even contribute! 🫡

Since this is my first blog post, I'd really appreciate any feedback or suggestions you have by sending me an email. Whether it's about the code, the blog structure, or just general advice, feel free to let me know.

Stay tuned for the next blog!