This is Article 3 in my Reverse Proxy Series.
That 30-line proxy from the last article? It works.
But it's a long way off from something you can deem as production-ready.
Why Our Quick Start Version Will Hurt You Eventually
Your current proxy is a single if/else statement. It breaks when:
- Someone visits the subdomain instead of the main domain
- You need to hand off the project to a team
- You want to do some testing
- SEO gets involved
We're going to fix all of that with Hono.js. A tiny framework that makes routing simpler
Enter Hono.js
Hono simplifies our if-else statements and data retrieval from the request. It's tiny, fast, and makes your code readable.
First you need to run bun i hono in your terminal to install the hono.js packages
Hono saves us from writing an enormous chain of if-else statements.
Instead of:
if (url.pathname.startsWith('/blog')) {
// blog stuff
} else if (url.pathname.startsWith('/help')) {
// help stuff
} else {
// main stuff
}You get:
app.get('/blog/*', handleBlog)
app.get('/help/*', handleHelp)
app.get('*', handleMain)A basic example with Hono.js
Make sure Hono is installed in your repo
Replace your current index.js file with the following and run bun wrangler dev, then open the localhost URL (or press b for the shortcut).
import { Hono } from "hono";
const app = new Hono();
app.get("*", async (c) => {
const { pathname } = new URL(c.req.url);
let res = await fetch("https://jamesbattye.dev" + pathname);
return res;
});
export default app;Ok nice. We're exactly where we were a minute ago, but in 11 lines of code instead of 30. Brilliant /s
Adding more routes
Now we'll add another project. /blog.
This initial approach will be a bit wordy, then we'll clean it up after
import { Hono } from "hono";
const app = new Hono();
// Make a variable which holds our projects. This will be easy to add to later
const projects = {
main: "https://jamesbattye.dev",
blog: "https://webflow.com",
};
// if the request is for the blog page
app.get("/blog", async (c) => {
const { pathname } = new URL(c.req.url);
let res = await fetch(projects.blog);
return res;
});
// if the request is for a subpage of the blog folder
app.get("/blog/*", async (c) => {
const { pathname } = new URL(c.req.url);
let res = await fetch(projects.blog + pathname);
return res;
});
// everything else
app.get("*", async (c) => {
const { pathname } = new URL(c.req.url);
let res = await fetch(projects.main + pathname);
return res;
});
export default app;Now we're saying: if it's the blog page, get the blog home, if it's a blog subpage get the blog project + the path, otherwise get the main project.
Not bad, but it's a bit repetitive. Let's group our requests into a helper function to tidy this up.
import { Hono } from "hono";
const app = new Hono();
const projects = {
main: "https://jamesbattye.dev",
blog: "https://webflow.com",
};
// A helper function that makes the request for us, so we can keep the code tidy
const proxyTo = (target) => async (c) => {
// get the url from the request
const url = new URL(c.req.url);
const targetUrl = target + url.pathname + url.search;
// also we'll clean up the fetch request to include more meta data.
const res = await fetch(targetUrl, {
method: c.req.method,
headers: c.req.raw.headers,
body: c.req.raw.body,
redirect: "manual",
});
return res;
};
// Blog (root + children)
app.get("/blog/", proxyTo(projects.blog));
app.get("/blog/*", proxyTo(projects.blog));
// everything else
app.get("*", proxyTo(projects.main));
export default app;Now you can run bun wrangler deploy and push your new and improved reverse proxy to your worker!
We've not got a pretty decent little proxy running, but it's still not 100% there yet. We've still got to resolve these issues:
- What if someone visits your subdomain?
- How can we change the HTML the user receives?
- What's the SEO implications of all this?
- How do you store, manage and deliver the code to a client?
More on that soon.