diff --git a/src/middleware.ts b/src/middleware.ts index 27d1316..193c04e 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -2,6 +2,13 @@ import { NextRequest, NextResponse } from "next/server"; import { JWTService } from "@/server/services/jwt.service"; export async function middleware(req: NextRequest) { + // Generate a standard UUID (v4) + const responseId = crypto.randomUUID(); + + // Clone headers to inject it into the request for downstream use + const requestHeaders = new Headers(req.headers); + requestHeaders.set("x-response-id", responseId); + const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS?.split(",") ?? []; const origin = req.headers.get("origin"); @@ -28,6 +35,7 @@ export async function middleware(req: NextRequest) { "Content-Type, Authorization, X-Requested-With", ); response.headers.set("Access-Control-Max-Age", "86400"); + response.headers.set("x-response-id", responseId); return response; } } @@ -45,7 +53,13 @@ export async function middleware(req: NextRequest) { } } - const response = NextResponse.next(); + // Pass along the modified request headers + const response = NextResponse.next({ + request: { + headers: requestHeaders, + }, + }); + response.headers.set("Vary", "Accept-Encoding"); // For non-OPTIONS requests, add the Access-Control-Allow-Origin header if the origin is allowed. @@ -53,6 +67,9 @@ export async function middleware(req: NextRequest) { response.headers.set("Access-Control-Allow-Origin", origin); } + // Attach the same UUID to the response headers + response.headers.set("x-response-id", responseId); + return response; } diff --git a/src/server/utils/api-response.ts b/src/server/utils/api-response.ts index cbaace1..ec151ca 100644 --- a/src/server/utils/api-response.ts +++ b/src/server/utils/api-response.ts @@ -25,6 +25,7 @@ export class ApiResponse { data: T, message: string = "Success", status: number = 200, + headers?: HeadersInit ): NextResponse> { return NextResponse.json( { @@ -32,7 +33,10 @@ export class ApiResponse { message, data, }, - { status }, + { + status, + headers + } ); } @@ -46,12 +50,14 @@ export class ApiResponse { * @param errors Optional field-level error map (e.g. from Zod). * @param req The originating request – used to populate `instance`. * Falls back to `"unknown"` when omitted. + * @param headers Optional additional headers. */ static error( detail: string = "Internal Server Error", status: number = 500, errors: Record | null = null, req?: NextRequest, + headers?: HeadersInit ): NextResponse { const instance = req?.nextUrl?.pathname ?? "unknown"; @@ -61,6 +67,7 @@ export class ApiResponse { status, headers: { "Content-Type": "application/problem+json", + ...Object.fromEntries(new Headers(headers).entries()), }, }); } @@ -72,11 +79,13 @@ export class ApiResponse { */ static problemDetails( problem: ProblemDetails, + headers?: HeadersInit ): NextResponse { return NextResponse.json(problem, { status: problem.status, headers: { "Content-Type": "application/problem+json", + ...Object.fromEntries(new Headers(headers).entries()), }, }); }