T
TanStack2mo ago
deep-jade

Can I proxy?

Hi, I’m building a CMS system with TanStack Start where the backend will handle HTML updates and API responses. On the frontend, I need to make requests to my backend API, but I don’t want the actual API URL to be exposed to the client. What is the recommended way to set up proxying in TanStack Start so that the frontend can call the backend without revealing the real API endpoint?
11 Replies
deep-jade
deep-jadeOP2mo ago
In Next.js, I used to create a file like /api/proxy.ts to handle proxying. How can I achieve the same in TanStack Start? Is there a better way to do this, or should I avoid this approach altogether from the beginning?
deep-jade
deep-jadeOP2mo ago
example
No description
extended-salmon
extended-salmon2mo ago
You should be able to achieve similar result using a global middleware (or just regular ones if you dont want it in all routes), add a request middleware, reconstruct the new url in the middleware, then pass it as context to the server handler which does the fetch. or fetch in the middlware it self but probably best in a handler function https://tanstack.com/start/latest/docs/framework/react/guide/middleware#request-middleware
Middleware | TanStack Start React Docs
What is Middleware? Middleware allows you to customize the behavior of both server routes like GET/POST/etc (including requests to SSR your application) and server functions created with createServerF...
deep-jade
deep-jadeOP2mo ago
Could you give me a little more detail? I'm new to this. Would you recommend I do it in the function or in the middleware?
extended-salmon
extended-salmon2mo ago
something like this
import { createFileRoute } from '@tanstack/react-router';
import { createMiddleware, json } from '@tanstack/react-start';

// this could be a global middleware
export const refetchDifferentEndpoint = createMiddleware({ type: 'request' })
.server(
async ({ next, request }) => {
const body = await request.json<{endpoint: string}>()

if (!body.endpoint.startsWith("/api/")) {
throw json({message: "some error"}, {status: 400 })
}

// logic to get token via servr fucntion or headers etc.

const newUrl = `${process.env.URL}${body.endpoint}`;
// can also add the body in the middleware
return await next({ context: { newUrl, headers: {Authorization : ""} } });
}
)

export const Route = createFileRoute('/api/some-api-route')({
server: {
handlers: ({ createHandlers }) =>
createHandlers({
POST: {
middleware: [refetchDifferentEndpoint],
handler: async ({ context }) => {
await fetch(context.newUrl, { headers: context.headers });
return json({ message: "success"})
},
},
}),
},
})
import { createFileRoute } from '@tanstack/react-router';
import { createMiddleware, json } from '@tanstack/react-start';

// this could be a global middleware
export const refetchDifferentEndpoint = createMiddleware({ type: 'request' })
.server(
async ({ next, request }) => {
const body = await request.json<{endpoint: string}>()

if (!body.endpoint.startsWith("/api/")) {
throw json({message: "some error"}, {status: 400 })
}

// logic to get token via servr fucntion or headers etc.

const newUrl = `${process.env.URL}${body.endpoint}`;
// can also add the body in the middleware
return await next({ context: { newUrl, headers: {Authorization : ""} } });
}
)

export const Route = createFileRoute('/api/some-api-route')({
server: {
handlers: ({ createHandlers }) =>
createHandlers({
POST: {
middleware: [refetchDifferentEndpoint],
handler: async ({ context }) => {
await fetch(context.newUrl, { headers: context.headers });
return json({ message: "success"})
},
},
}),
},
})
deep-jade
deep-jadeOP4w ago
import { createMiddleware, createStart } from "@tanstack/react-start";

const BaseURL = process.env.BASE_URL as string | undefined;
const SiteID = process.env.SITEID as string | undefined;

const apiProxy = createMiddleware().server(
async ({ request, next, context, pathname }) => {
const url = new URL(request.url);
if (!url.pathname.startsWith("/api/")) {
return next();
}

if (!BaseURL || !SiteID) {
return {
request,
pathname,
context,
response: new Response("Missing BASE_URL or SITEID", {
status: 500,
}),
};
}

const upstreamPath = url.pathname.replace(/^\/api(\/|$)/, "/");
const target = new URL(
upstreamPath + url.search,
ensureTrailingSlash(BaseURL),
).toString();

const headers = new Headers(request.headers);
headers.set("SiteID", SiteID);

const init: RequestInit = {
method: request.method,
headers,
body: needsBody(request.method) ? request.body : undefined,
redirect: "manual",
};

const resp = await fetch(target, init);

const outHeaders = new Headers(resp.headers);

return {
request,
pathname,
context,
response: new Response(resp.body, {
status: resp.status,
statusText: resp.statusText,
headers: outHeaders,
}),
};
},
);

function needsBody(method: string) {
return ["GET", "HEAD"].includes(method.toUpperCase()) === false;
}

function ensureTrailingSlash(u: string) {
return u.endsWith("/") ? u : `${u}/`;
}

export const startInstance = createStart(() => ({
requestMiddleware: [apiProxy],
}));
import { createMiddleware, createStart } from "@tanstack/react-start";

const BaseURL = process.env.BASE_URL as string | undefined;
const SiteID = process.env.SITEID as string | undefined;

const apiProxy = createMiddleware().server(
async ({ request, next, context, pathname }) => {
const url = new URL(request.url);
if (!url.pathname.startsWith("/api/")) {
return next();
}

if (!BaseURL || !SiteID) {
return {
request,
pathname,
context,
response: new Response("Missing BASE_URL or SITEID", {
status: 500,
}),
};
}

const upstreamPath = url.pathname.replace(/^\/api(\/|$)/, "/");
const target = new URL(
upstreamPath + url.search,
ensureTrailingSlash(BaseURL),
).toString();

const headers = new Headers(request.headers);
headers.set("SiteID", SiteID);

const init: RequestInit = {
method: request.method,
headers,
body: needsBody(request.method) ? request.body : undefined,
redirect: "manual",
};

const resp = await fetch(target, init);

const outHeaders = new Headers(resp.headers);

return {
request,
pathname,
context,
response: new Response(resp.body, {
status: resp.status,
statusText: resp.statusText,
headers: outHeaders,
}),
};
},
);

function needsBody(method: string) {
return ["GET", "HEAD"].includes(method.toUpperCase()) === false;
}

function ensureTrailingSlash(u: string) {
return u.endsWith("/") ? u : `${u}/`;
}

export const startInstance = createStart(() => ({
requestMiddleware: [apiProxy],
}));
I did it this way. If you say this is incorrect usage, I'll use the example you gave. Thanks in advance, friend. I redirect every request starting with /api/ here, and from here I send the request to my own API :D update
flat-fuchsia
flat-fuchsia4w ago
I'm pretty sure you could just use rewrites for this Although I'm not sure if rewrites can modify the request headers, you could put siteID in the url Maybe it would work IDK you can test it
import { createRouter } from "@tanstack/react-router"
import { createIsomorphicFn } from "@tanstack/react-start"
import { getRequest } from "@tanstack/react-start/server"

// Import the generated route tree
import { routeTree } from "./routeTree.gen"

const isomorphicRewrite = createIsomorphicFn()
.server(({ url }: { url: URL }) => {
// modify the header as needed
const request = getRequest()

// request.headers.set("Authorization", `Bearer test`)

// modify the url as needed and return it

return url
})
.client(({ url }: { url: URL }) => {
return url
})

// Create a new router instance
export const getRouter = () => {
return createRouter({
routeTree,
scrollRestoration: true,
defaultPreloadStaleTime: 0,
rewrite: {
input: isomorphicRewrite
}
})
}
import { createRouter } from "@tanstack/react-router"
import { createIsomorphicFn } from "@tanstack/react-start"
import { getRequest } from "@tanstack/react-start/server"

// Import the generated route tree
import { routeTree } from "./routeTree.gen"

const isomorphicRewrite = createIsomorphicFn()
.server(({ url }: { url: URL }) => {
// modify the header as needed
const request = getRequest()

// request.headers.set("Authorization", `Bearer test`)

// modify the url as needed and return it

return url
})
.client(({ url }: { url: URL }) => {
return url
})

// Create a new router instance
export const getRouter = () => {
return createRouter({
routeTree,
scrollRestoration: true,
defaultPreloadStaleTime: 0,
rewrite: {
input: isomorphicRewrite
}
})
}
What you want to do is create an isomorphic function with a server function only inside of it, then run it here
deep-jade
deep-jadeOP4w ago
hemmmm I'll give it a try. ty I'll take a look right away
flat-fuchsia
flat-fuchsia4w ago
I know it'll definitely work for proxying the URL, it's changing the headers that I'm uncertain about
deep-jade
deep-jadeOP4w ago
btw I'd like to ask you a favor. If you have any ideas, could you help me with this too? https://discord.com/channels/719702312431386674/1435433858056327210 I'll take care of it somehow That's enough for me thank you my friend
flat-fuchsia
flat-fuchsia4w ago
I'm not sure about the IntLayer stuff atm but I'm gonna make a demo project with it soon and then I'll see how it works

Did you find this page helpful?