SvelteKit - Setting up an API reverse proxy
Introduction
I am currently working on a SvelteKit app that needs to communicate with a separate backend API written in Kotlin (say, my-website.com
and api.my-website.com
). To work around various CORS issues, I need to setup a reverse proxy for my API.
What is a Reverse Proxy?
Let’s say I setup a reverse proxy at my-website.com/api-proxy
. This means that everytime my web app wants to fetch data from my API, it would first make the request to an endpoint within the webapp’s server at my-website.com/api-proxy
, which would then pass that request along to the API, and forward the response it receives back to the webapp.
Solution
I tried using Sveltekit’s Actions but ran into a bunch of issues. Too much “magic” was happening. I realized that I’d rather skip any intervention on Sveltekit’s behalf here: all I want is an escape hatch that will forward my requests (& subsequent responses) as-is.
To do that, I ended up intercepting the API proxy requests in the handle hook, like so:
import type { Handle } from "@sveltejs/kit";
const MY_API_BASE_URL = "https://api.my-website.com";
const PROXY_PATH = "/api-proxy";
const handleApiProxy: Handle = async ({ event }) => {
const origin = event.request.headers.get("Origin");
// reject requests that don't come from the webapp, to avoid your proxy being abused.
if (!origin || new URL(origin).origin !== event.url.origin) {
throw error(403, "Request Forbidden.");
}
// strip `/api-proxy` from the request path
const strippedPath = event.url.pathname.substring(PROXY_PATH.length);
// build the new URL path with your API base URL, the stripped path and the query string
const urlPath = `${MY_API_BASE_URL}${strippedPath}${event.url.search}`;
const proxiedUrl = new URL(urlPath);
// Strip off header added by SvelteKit yet forbidden by underlying HTTP request
// library `undici`.
// https://github.com/nodejs/undici/issues/1470
event.request.headers.delete("connection");
return fetch(proxiedUrl.toString(), {
// propagate the request method and body
body: event.request.body,
method: event.request.method,
headers: event.request.headers,
}).catch((err) => {
console.log("Could not proxy API request: ", err);
throw err;
});
};
export const handle: Handle = async ({ event, resolve }) => {
// intercept requests to `/api-proxy` and handle them with `handleApiProxy`
if (event.url.pathname.startsWith(PROXY_PATH)) {
return await handleApiProxy({ event, resolve });
}
// ...the rest of your `handle` logic goes here
};
And that’s it! This snippet will grab any request made to /api-proxy
, even if it’s a nested path like /api-proxy/users/123
, and forward it to api.my-website.com/users/123
. It will also forward & respect methods (POST
, UPDATE
, DELETE
, etc.) and query parameters, so POST /api-proxy/users?name=John
will be forwarded to POST api.my-website.com/users?name=John
.