Files
neo/frontend/server/api/[...path].ts
Francisco Gaona 0e2f3dddbc WIP - BFF
2026-02-04 00:21:06 +01:00

112 lines
3.1 KiB
TypeScript

import { defineEventHandler, getMethod, readBody, getQuery, createError, getHeader } from 'h3'
import { getSubdomainFromRequest } from '~/server/utils/tenant'
import { getSessionToken } from '~/server/utils/session'
/**
* Catch-all API proxy that forwards requests to the NestJS backend
* Injects x-tenant-subdomain header and Authorization from HTTP-only cookie
*/
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const method = getMethod(event)
const path = event.context.params?.path || ''
// Get subdomain and session token
const subdomain = getSubdomainFromRequest(event)
const token = getSessionToken(event)
const backendUrl = config.backendUrl || 'http://localhost:3000'
// Build the full URL with query parameters
const query = getQuery(event)
const queryString = new URLSearchParams(query as Record<string, string>).toString()
const fullUrl = `${backendUrl}/api/${path}${queryString ? `?${queryString}` : ''}`
// Build headers to forward
const headers: Record<string, string> = {
'Content-Type': getHeader(event, 'content-type') || 'application/json',
}
// Add subdomain header for backend tenant resolution
if (subdomain) {
headers['x-tenant-subdomain'] = subdomain
}
// Add auth token from HTTP-only cookie
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
// Forward additional headers that might be needed
const acceptHeader = getHeader(event, 'accept')
if (acceptHeader) {
headers['Accept'] = acceptHeader
}
try {
// Prepare fetch options
const fetchOptions: RequestInit = {
method,
headers,
}
// Add body for methods that support it
if (['POST', 'PUT', 'PATCH'].includes(method)) {
const body = await readBody(event)
if (body) {
fetchOptions.body = JSON.stringify(body)
}
}
// Make request to backend
const response = await fetch(fullUrl, fetchOptions)
// Handle non-JSON responses (like file downloads)
const contentType = response.headers.get('content-type')
if (!response.ok) {
// Try to get error details
let errorMessage = `Backend error: ${response.status}`
let errorData = null
try {
errorData = await response.json()
errorMessage = errorData.message || errorMessage
} catch {
// Response wasn't JSON
}
throw createError({
statusCode: response.status,
statusMessage: errorMessage,
data: errorData,
})
}
// Return empty response for 204 No Content
if (response.status === 204) {
return null
}
// Handle JSON responses
if (contentType?.includes('application/json')) {
return await response.json()
}
// Handle other content types (text, etc.)
return await response.text()
} catch (error: any) {
// Re-throw H3 errors
if (error.statusCode) {
throw error
}
console.error(`Proxy error for ${method} /api/${path}:`, error)
throw createError({
statusCode: 502,
statusMessage: 'Failed to connect to backend service',
})
}
})