Skip to main content

Overview

KilomeTracker uses a Backend for Frontend (BFF) pattern. Every file at src/app/api/**/route.js is a server-side proxy handler — page components never call the external backend directly. All client-side fetch() calls go to /api/* routes on the same Next.js origin.
Browser → /api/vehicles (Next.js) → API_BASE_URL/api/vehicles (backend)
This keeps the JWT token out of reach of browser JavaScript.
API_BASE_URL is a server-side environment variable. It is never exposed to the browser. Set it in .env.local:
API_BASE_URL=https://your-backend-url.com

How each proxy route works

1

Read the JWT from the HTTP-only cookie

The bffFetch helper reads the token cookie using Next.js cookies(). Because the cookie is httpOnly, browser JavaScript cannot access it — only server-side code can.
import { cookies } from 'next/headers';

const cookieStore = await cookies();
const token = cookieStore.get('token')?.value;
2

Return 401 if no token is present

If the request arrives without a valid cookie (unauthenticated user), bffFetch returns a 401 response immediately without making a network call to the backend.
if (!token) {
  return new Response(JSON.stringify({ error: 'No autorizado' }), {
    status: 401,
    headers: { 'Content-Type': 'application/json' },
  });
}
3

Forward the request to the backend

The proxy attaches the token as a Bearer header and forwards the request — including the original HTTP method, body, and any additional headers — to API_BASE_URL.
return fetch(`${process.env.API_BASE_URL}${path}`, {
  ...options,
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`,
    ...options?.headers,
  },
});
4

Return the backend response to the client

The route handler receives the backend response and returns it to the browser using NextResponse.json().
import { NextResponse } from 'next/server';
import { bffFetch } from '@/lib/backendFetch';

export async function GET() {
  const response = await bffFetch('/api/vehicles');

  if (!response.ok) {
    const err = await response.json().catch(() => ({}));
    return NextResponse.json(
      { error: err.error || 'Error' },
      { status: response.status }
    );
  }

  const data = await response.json();
  return NextResponse.json(data);
}
The bffFetch helper (src/lib/backendFetch.ts) encapsulates steps 1–3 and is used by all proxy routes except login and logout, which manage the cookie directly.

API route structure

All proxy routes live under src/app/api/.
Route groupEndpoints
api/auth/login, logout, register, me, updateprofile, updatepassword
api/auth/users/List users (GET); get, deactivate, reactivate a user by ID
api/auth/users/[id]/role/Update user role (PUT)
api/vehicles/List and create vehicles
api/vehicles/[alias]/Get, update, deactivate a vehicle by alias
api/vehicles/[alias]/stats/Comprehensive vehicle statistics
api/vehicles/[alias]/fuel-efficiency/Fuel efficiency metrics
api/vehicles/[alias]/reactivate/Reactivate a deactivated vehicle
api/routes/List, create, get, update, delete routes
api/refuels/List, create, get, update, delete refuels
api/refuels/vehicle/[alias]/analysis/Fuel analysis for a specific vehicle
api/maintenance/List and create maintenance records
api/maintenance/[id]/Get, update, delete a maintenance record
api/maintenance/upcoming/Maintenance due soon
api/expenses/List and create expenses
api/expenses/[id]/Get, update, delete an expense
api/expenses/summary/Aggregated spending by category
api/expenses/upcoming/Recurring expenses due in the next 30 days

Client-side fetch pattern

All page components use "use client" and call /api/* routes using the browser’s fetch API. They never reference API_BASE_URL or call the external backend directly.
// Correct — calls the Next.js proxy
const response = await fetch('/api/vehicles');
const data = await response.json();

// Never do this — exposes the backend URL and bypasses token handling
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/vehicles`);
For endpoints that require a request body, pass it as JSON:
const response = await fetch('/api/routes', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(formData),
});
The JWT cookie is attached automatically by the browser on every same-origin request — no manual token management is needed in client code.

Build docs developers (and LLMs) love