2026-03-18 13:02:58 -07:00

74 lines
2.1 KiB
TypeScript

// Server-side proxy helper for Next.js API routes.
// Forwards requests to the NestJS backend, including the Bearer token.
import { NextRequest, NextResponse } from "next/server";
const BASE_URL = process.env.LEDGERONE_API_URL ?? "http://localhost:3051";
export function getBackendUrl(path: string): string {
return `${BASE_URL}/api/${path.replace(/^\//, "")}`;
}
interface ProxyOptions {
method?: string;
body?: BodyInit | null;
extraHeaders?: Record<string, string>;
search?: string;
}
export async function proxyRequest(
req: NextRequest,
backendPath: string,
options: ProxyOptions = {}
): Promise<NextResponse> {
const url = new URL(req.url);
const search = options.search !== undefined ? options.search : url.search;
const targetUrl = `${getBackendUrl(backendPath)}${search}`;
const method = options.method ?? req.method;
const auth = req.headers.get("authorization") ?? "";
const contentType = req.headers.get("content-type") ?? "";
const headers: Record<string, string> = { ...options.extraHeaders };
if (auth) headers["Authorization"] = auth;
let body: BodyInit | null | undefined = undefined;
if (method !== "GET" && method !== "HEAD") {
if (options.body !== undefined) {
body = options.body;
if (contentType) headers["Content-Type"] = contentType;
} else if (contentType.includes("multipart/form-data")) {
body = await req.formData();
// Do not set Content-Type — fetch sets it with boundary automatically
} else {
body = await req.text();
if (contentType) headers["Content-Type"] = contentType;
}
}
try {
const res = await fetch(targetUrl, {
method,
headers,
body: body ?? undefined,
});
const payload = await res.text();
return new NextResponse(payload, {
status: res.status,
headers: {
"Content-Type": res.headers.get("content-type") ?? "application/json",
},
});
} catch {
return NextResponse.json(
{
data: null,
meta: { timestamp: new Date().toISOString(), version: "v1" },
error: { message: "Backend unavailable." },
},
{ status: 503 }
);
}
}