Skip to Content
Dev ModeArchitecture

Generated API Services Architecture

When you run the generator, Reex API Builder creates a structured api-services directory in the src folder of your project. Understanding this structure is key to leveraging the tool effectively.

Folder Structure Diagram

Here is the folder structure that gets generated:

src/ └── api-services/ ├── .reex/ # Internal config & project metadata │ ├── config.ts # Internal config & definition types │ └── metadata.json # Project synchronization metadata ├── auth-methods/ # Modular authentication strategies & guards ├── custom/ # Custom reusable UI components ├── definitions/ # Your API implementations (editable) ├── generated/ # Auto-generated React Query hooks ├── hooks/ # Authentication & custom hooks ├── providers/ # Application providers (e.g. ReexProvider) ├── types/ # TypeScript interfaces ├── api.config.ts # Centralized user configuration ├── core.ts # Core HTTP configuration and proxy └── ...

.reex/ - Internal Configuration & Metadata

Contains internal types and metadata used for project synchronization and type enforcement.

.reex/config.ts: Internal interfaces (ReexDefinition and ReexConfig).

View code

.reex/config.ts
// @internal — No changes needed /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Enforces the basic structure of Reex definition files. * Use with the `satisfies` operator for maximum IntelliSense: * export const myApi = { ... } satisfies ReexDefinition; */ export type ReexDefinition = Record<string, (...args: any[]) => Promise<any>>; /** * Enforces the structure of the Reex configuration file (api.config.ts). * Use with the `satisfies` operator for maximum IntelliSense. */ export interface ReexConfig { baseURL: string; unwrapResponseData: boolean; enableApiLogging: boolean; auth: { loginRoute?: string; refreshEndpoint: string; accessTokenKey: string; refreshTokenKey: string; extractTokens: (responseBody: any) => { accessToken?: string; refreshToken?: string }; refreshWithCookie: () => Promise<import("axios").AxiosResponse<any>>; refreshWithToken: (storedRefreshToken: string) => Promise<import("axios").AxiosResponse<any>>; }; }

.reex/metadata.json: Project metadata.

View code

.reex/metadata.json
{ "version": "6.0.2", "framework": "react", "updatedAt": "2026-08-04T05:00:00.000Z" }

core.ts - Core API Client

This file contains the core Axios configuration, interceptors, and the main API client that dynamically uses the active strategy.

core.ts: Request interceptors & auth logic.

View code

core.ts
// @internal — No changes needed /* eslint-disable @typescript-eslint/no-explicit-any */ import axios, { AxiosError, type AxiosInstance, type AxiosRequestConfig, type InternalAxiosRequestConfig, } from "axios"; import type { ApiError, TokenProvider } from "./auth-methods/types"; import { apiConfig } from "./api.config"; import { proxyTokenProvider } from "./auth-methods/manager"; import { pushNotification } from "./hooks/notification"; const API_TIMEOUT = 30000; const REFRESH_TIMEOUT = 10000; // Helper to extract a human-readable string from various error response formats const formatErrorMessage = (data: any): string | undefined => { if (!data) return undefined; if (typeof data.error === "string") return data.error; if (typeof data.message === "string") return data.message; if (typeof data.msg === "string") return data.msg; if (data.detail !== undefined && data.detail !== null) { if (typeof data.detail === "string") return data.detail; if (Array.isArray(data.detail)) { return data.detail .map((err: any) => { const field = Array.isArray(err.loc) && err.loc.length ? err.loc[err.loc.length - 1] : undefined; const msg = err.msg || "Invalid value"; return field ? `${field}: ${msg}` : msg; }) .join("; "); } if (typeof data.detail === "object") { return data.detail.message || data.detail.msg || "Invalid request"; } } return undefined; }; const DEFAULT_CONFIG: AxiosRequestConfig = { baseURL: apiConfig.baseURL, timeout: API_TIMEOUT, headers: { "Content-Type": "application/json" }, }; /** Creates an Axios client with auto token injection and 401 refresh/retry */ export const createApiClient = ( tokenProvider?: TokenProvider, ): AxiosInstance => { const client = axios.create(DEFAULT_CONFIG); let isRefreshing = false; let failedQueue: Array<{ resolve: (token: string | null) => void; reject: (error: any) => void; }> = []; const processQueue = (error: any, token: string | null = null) => { failedQueue.forEach((prom) => { if (error) { prom.reject(error); } else { prom.resolve(token); } }); failedQueue = []; }; // Request interceptor: inject token + custom headers client.interceptors.request.use( async (config: InternalAxiosRequestConfig) => { config.baseURL = apiConfig.baseURL; if (config.data instanceof FormData && config.headers) { delete config.headers["Content-Type"]; } if (tokenProvider) { const token = await tokenProvider.getToken(); if (token && token !== "cookie-active" && config.headers) { config.headers.Authorization = `Bearer ${token}`; } const customHeaders = await tokenProvider.getCustomHeaders?.(); if (customHeaders && config.headers) { Object.entries(customHeaders).forEach(([key, value]) => { if (typeof config.headers.set === "function") { config.headers.set(key, value); } else { config.headers[key] = value; } }); } } if (apiConfig.enableApiLogging) { console.log( `[API Request] ${config.method?.toUpperCase()} ${config.url}`, config.params || config.data, ); } return config; }, (error) => Promise.reject(error), ); // Response interceptor: unwrap data, handle 401 refresh, normalize errors client.interceptors.response.use( (response) => { const rawData = response?.data; const res = apiConfig.unwrapResponseData ? (rawData?.data ?? rawData) : rawData; if (apiConfig.enableApiLogging) { console.log( `[API Success] ${response.config.method?.toUpperCase()} ${response.config.url}`, res, ); } return res; }, async (error: AxiosError) => { const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean; }; // Handle 401 with token refresh if ( error.response?.status === 401 && !originalRequest._retry && tokenProvider?.refreshToken ) { if (isRefreshing) { return new Promise<string | null>((resolve, reject) => { const timeoutId = setTimeout(() => { reject( new Error("Token refresh timeout - request took too long"), ); }, REFRESH_TIMEOUT); failedQueue.push({ resolve: (token) => { clearTimeout(timeoutId); resolve(token); }, reject: (err) => { clearTimeout(timeoutId); reject(err); }, }); }) .then((token) => { if (token && originalRequest.headers) { originalRequest.headers.Authorization = `Bearer ${token}`; } return client(originalRequest); }) .catch((err) => Promise.reject(err)); } originalRequest._retry = true; isRefreshing = true; try { const newToken = await tokenProvider.refreshToken(); if (newToken && originalRequest.headers) { originalRequest.headers.Authorization = `Bearer ${newToken}`; processQueue(null, newToken); return client(originalRequest); } throw new Error("Refresh failed to return a valid token"); } catch (refreshError) { processQueue(refreshError, null); // Refresh failed — signal the app to log the user out and clear the session. if (typeof window !== "undefined") { window.dispatchEvent(new Event("auth:logout")); } // Notify the user. To redirect instead of showing a toast, handle "auth:logout" in your auth guard. pushNotification({ type: "error", message: formatErrorMessage((error as any).response?.data) || "Authentication failed. Please log in again.", statusCode: 401, }); // Return early so the generic error handler below does not also fire a notification. return Promise.reject(refreshError); } finally { isRefreshing = false; } } else if (error.response?.status === 401) { // No token refresh configured — log the user out immediately. if (typeof window !== "undefined") { window.dispatchEvent(new Event("auth:logout")); } pushNotification({ type: "error", message: formatErrorMessage(error.response?.data) || "Authentication failed. Please log in again.", statusCode: 401, }); // Return early so the generic error handler below does not also fire a notification. throw error; } // Normalize the raw Axios error into a consistent ApiError shape. const responseData = error.response?.data as any; const apiError: ApiError = { message: formatErrorMessage(responseData) || error.message || "An unexpected error occurred", code: responseData?.error || responseData?.code, statusCode: error.response?.status, originalError: error, }; if (originalRequest) { const endpoint = `${originalRequest.method?.toUpperCase()} ${originalRequest.url}`; console.log(`[API ERROR - ${endpoint}]`, { message: apiError.message, code: apiError.code, statusCode: apiError.statusCode, }); } // Surface the error to the user. Customize the UI in AppNotification.tsx. pushNotification({ type: "error", message: apiError.message, statusCode: apiError.statusCode, }); throw apiError; }, ); return client; }; /** * The globally configured API client instance. * It dynamically uses the Active Strategy defined in the AuthProvider. */ export const apiClient = createApiClient(proxyTokenProvider);

auth-methods/ - Authentication Strategies

Provides modular authentication strategies and guards.

AuthProvider.tsx: Sets the global strategy and provides the guards.

View code

auth-methods/AuthProvider.tsx
"use client"; import React, { useState } from "react"; import { CookieAuthGuard } from "./cookie-auth/CookieAuthGuard"; import { NextAuthGuard } from "./next-auth/NextAuthGuard"; import { JwtAuthGuard } from "./jwt-auth/JwtAuthGuard"; import { setActiveStrategy, type AuthStrategy } from "./manager"; interface AuthProviderProps { strategy: AuthStrategy; children: React.ReactNode; } /** * Global AuthProvider that wraps the application and conditionally renders * the appropriate AuthGuard based on the configured strategy. * It also configures the global API client to use the correct TokenProvider. */ export const AuthProvider = ({ strategy, children }: AuthProviderProps) => { // Synchronously configure the API client's Strategy on the first render useState(() => { setActiveStrategy(strategy); return true; }); switch (strategy) { case "cookie": return <CookieAuthGuard>{children}</CookieAuthGuard>; case "next-auth": return <NextAuthGuard>{children}</NextAuthGuard>; case "jwt": return <JwtAuthGuard>{children}</JwtAuthGuard>; default: console.warn(`Unknown auth strategy: ${strategy}. Falling back to jwt.`); return <JwtAuthGuard>{children}</JwtAuthGuard>; } };

manager.ts: Strategy switcher and dynamic proxy.

View code

auth-methods/manager.ts
import { cookieTokenProvider } from "./cookie-auth/provider"; import { jwtTokenProvider } from "./jwt-auth/provider"; import { nextAuthTokenProvider } from "./next-auth/provider"; import { type TokenProvider } from "./types"; export type AuthStrategy = "jwt" | "cookie" | "next-auth"; let activeTokenProvider: TokenProvider = jwtTokenProvider; /** * Updates the globally active token provider based on the chosen strategy. * Used internally by the <AuthProvider> component. */ export const setActiveStrategy = (strategy: AuthStrategy) => { switch (strategy) { case "cookie": activeTokenProvider = cookieTokenProvider; break; case "next-auth": activeTokenProvider = nextAuthTokenProvider; break; case "jwt": activeTokenProvider = jwtTokenProvider; break; } }; /** * Proxy provider that dynamically resolves to the currently active token provider. * This ensures the API client always uses the correctly configured strategy without * needing to recreate the Axios instance. */ export const proxyTokenProvider: TokenProvider = { getToken: () => activeTokenProvider.getToken(), get refreshToken() { return activeTokenProvider.refreshToken; }, get setTokens() { return activeTokenProvider.setTokens; }, get setSession() { return activeTokenProvider.setSession; }, get clearTokens() { return activeTokenProvider.clearTokens; }, get getCustomHeaders() { return activeTokenProvider.getCustomHeaders; }, get setCustomHeaders() { return activeTokenProvider.setCustomHeaders; }, get removeCustomHeader() { return activeTokenProvider.removeCustomHeader; }, }; /** * Use this function to access the active token provider from an external file. * This is the recommended way to get or set auth tokens after login. */ export function getActiveProvider(): TokenProvider | null { return activeTokenProvider ?? null; }

types.ts: Auth-related interfaces.

View code

auth-methods/types.ts
/** Standardized API error response */ export interface ApiError { message: string; code?: string; statusCode?: number; originalError?: unknown; } /** Pluggable authentication strategy */ export interface TokenProvider { /** Get current access token */ getToken: () => Promise<string | null> | string | null; /** Refresh expired token */ refreshToken?: () => Promise<string | null>; /** Return custom headers to inject into all requests */ getCustomHeaders?: () => | Promise<Record<string, string>> | Record<string, string>; /** Replace custom headers for all future requests */ setCustomHeaders?: (headers: Record<string, string>) => void; /** Remove a specific custom header */ removeCustomHeader?: (key: string) => void; /** Set new auth tokens (called on login/signup for token-based auth) */ setTokens?: (tokens: { accessToken: string; refreshToken?: string }) => void; /** Activate or set session on login (handles tokens, cookie-based sessions, and optional headers) */ setSession?: ( tokens?: { accessToken?: string; refreshToken?: string }, headers?: Record<string, string>, ) => void; /** Clear all auth tokens (called on logout) */ clearTokens?: () => void; }

Cookie Auth Strategy

View code

auth-methods/cookie-auth/CookieAuthGuard.tsx
// @internal — No changes needed "use client"; import { useEffect, useState } from "react"; import { cookieTokenProvider } from "./provider"; import { apiClient } from "../../../api-services/core"; /** * Restores the user session on mount via the refresh endpoint. */ export const CookieAuthGuard = ({ children, }: { children: React.ReactNode; }) => { // Configure API client synchronously on mount useState(() => { apiClient.defaults.withCredentials = true; return true; }); useEffect(() => { // Attempt to restore access token from refresh cookie or localStorage const initAuth = async () => { try { await cookieTokenProvider.refreshToken?.(); } catch (error) { console.warn("Auth initialization failed:", error); } }; initAuth(); }, []); return <>{children}</>; };
auth-methods/cookie-auth/provider.ts
// @internal — No changes needed import { type TokenProvider } from "../types"; import { apiConfig } from "../../api.config"; let isCookieAuthActive: boolean = false; let customHeaders: Record<string, string> | null = null; let refreshPromise: Promise<string | null> | null = null; // Used to track auth status across tabs, without exposing the actual token to JS const COOKIE_FLAG_KEY = "reex_is_cookie_auth_active"; interface CookieTokenProvider extends TokenProvider { setSession: ( tokens?: { accessToken?: string; refreshToken?: string }, headers?: Record<string, string>, ) => void; clearTokens: () => void; setCustomHeaders: (headers: Record<string, string>) => void; getCustomHeaders: () => Record<string, string>; removeCustomHeader: (key: string) => void; } /** * Cookie-Only Token Provider * - Access Token: Managed securely by the browser via HttpOnly cookies * - Refresh Mechanism: Automatic via HttpOnly cookies * - Frontend State: Maintained via a simple boolean flag */ export const cookieTokenProvider: CookieTokenProvider = { getToken: () => { // Return a proxy value so `useIsAuthenticated` knows we are logged in. // The actual token is sent automatically by the browser via cookies. if (isCookieAuthActive) return "cookie-active"; if (typeof window !== "undefined") { return localStorage.getItem(COOKIE_FLAG_KEY) === "true" ? "cookie-active" : null; } return null; }, getCustomHeaders: () => { if (customHeaders) return customHeaders; if (typeof window !== "undefined") { const stored = localStorage.getItem("reex_custom_headers"); if (stored) { try { return JSON.parse(stored); } catch { return {}; } } } return {}; }, setCustomHeaders: (headers) => { customHeaders = headers; if (typeof window !== "undefined") { localStorage.setItem("reex_custom_headers", JSON.stringify(headers)); } }, removeCustomHeader: (key) => { if (customHeaders) { delete customHeaders[key]; } if (typeof window !== "undefined") { const stored = localStorage.getItem("reex_custom_headers"); if (stored) { try { const headers = JSON.parse(stored); delete headers[key]; localStorage.setItem("reex_custom_headers", JSON.stringify(headers)); } catch { // ignore JSON parse / storage errors } } } }, setSession: (_tokens, headers) => { // The actual token is set by the backend via Set-Cookie headers. // We just set a primitive flag so the frontend knows the user is logged in. isCookieAuthActive = true; if (headers) { cookieTokenProvider.setCustomHeaders(headers); } if (typeof window !== "undefined") { localStorage.setItem(COOKIE_FLAG_KEY, "true"); window.dispatchEvent(new Event("auth:login")); } }, clearTokens: () => { isCookieAuthActive = false; customHeaders = null; refreshPromise = null; if (typeof window !== "undefined") { localStorage.removeItem(COOKIE_FLAG_KEY); localStorage.removeItem("reex_custom_headers"); } }, refreshToken: async () => { if (typeof window !== "undefined" && localStorage.getItem(COOKIE_FLAG_KEY) !== "true") { return null; } if (refreshPromise) { return refreshPromise; } refreshPromise = (async () => { try { await apiConfig.auth.refreshWithCookie(); // If the refresh succeeds, the browser now has the updated cookie. isCookieAuthActive = true; if (typeof window !== "undefined") { localStorage.setItem(COOKIE_FLAG_KEY, "true"); window.dispatchEvent(new Event("auth:login")); } return "cookie-active"; } catch { // Refresh failed, clear the session flag isCookieAuthActive = false; if (typeof window !== "undefined") { localStorage.removeItem(COOKIE_FLAG_KEY); } return null; } finally { refreshPromise = null; } })(); return refreshPromise; }, };

JWT (Token) Strategy

View code

auth-methods/jwt-auth/JwtAuthGuard.tsx
// @internal — No changes needed "use client"; import { useEffect } from "react"; import { jwtTokenProvider } from "./provider"; import { apiConfig } from "../../api.config"; /** * Restores the user session on mount by exchanging the stored refresh token. */ export const JwtAuthGuard = ({ children, }: { children: React.ReactNode; }) => { useEffect(() => { // Attempt to restore session from stored tokens const initAuth = async () => { const hasRefreshToken = !!localStorage.getItem( apiConfig.auth.refreshTokenKey, ); const hasAccessToken = !!localStorage.getItem( apiConfig.auth.accessTokenKey, ); if ((hasRefreshToken || hasAccessToken) && jwtTokenProvider?.refreshToken) { try { await jwtTokenProvider.refreshToken(); } catch (error) { console.warn("Session restoration failed:", error); } } }; initAuth(); }, []); return <>{children}</>; };
auth-methods/jwt-auth/provider.ts
// @internal — No changes needed import { apiConfig } from "../../api.config"; import { type TokenProvider } from "../types"; /** * JWT Token Provider * - Access Token & Refresh Token: Persisted in localStorage * * Edit `api.config.ts` to customize routes and storage keys, and refresh payload. */ let accessToken: string | null = null; let customHeaders: Record<string, string> | null = null; let refreshPromise: Promise<string | null> | null = null; /** * Decodes the JWT `exp` claim and checks whether it has passed. * No library needed — JWT payloads are plain base64url-encoded JSON. * Returns false for non-JWT strings so opaque tokens are never blocked. */ const isTokenExpired = (token: string): boolean => { try { const payload = JSON.parse(atob(token.split(".")[1])); return typeof payload.exp === "number" && payload.exp * 1000 < Date.now(); } catch { return false; } }; interface JwtTokenProvider extends TokenProvider { setTokens: (tokens: { accessToken: string; refreshToken?: string }) => void; setSession: ( tokens?: { accessToken?: string; refreshToken?: string }, headers?: Record<string, string>, ) => void; clearTokens: () => void; setCustomHeaders: (headers: Record<string, string>) => void; getCustomHeaders: () => Record<string, string>; removeCustomHeader: (key: string) => void; } export const jwtTokenProvider: JwtTokenProvider = { getToken: () => { const token = accessToken ?? ( typeof window !== "undefined" ? localStorage.getItem(apiConfig.auth.accessTokenKey) : null ); if (token) { if (isTokenExpired(token)) { accessToken = null; if (typeof window !== "undefined") { localStorage.removeItem(apiConfig.auth.accessTokenKey); const hasRefreshToken = !!localStorage.getItem(apiConfig.auth.refreshTokenKey); if (!hasRefreshToken) { window.dispatchEvent(new Event("auth:logout")); return null; } } } else { return token; } } if (typeof window !== "undefined") { const storedRefreshToken = localStorage.getItem(apiConfig.auth.refreshTokenKey); if (storedRefreshToken && jwtTokenProvider.refreshToken) { return jwtTokenProvider.refreshToken(); } } return null; }, getCustomHeaders: () => { if (customHeaders) return customHeaders; if (typeof window !== "undefined") { const stored = localStorage.getItem("reex_custom_headers"); if (stored) { try { return JSON.parse(stored); } catch { return {}; } } } return {}; }, setCustomHeaders: (headers) => { customHeaders = headers; if (typeof window !== "undefined") { localStorage.setItem("reex_custom_headers", JSON.stringify(headers)); } }, removeCustomHeader: (key) => { if (customHeaders) { delete customHeaders[key]; } if (typeof window !== "undefined") { const stored = localStorage.getItem("reex_custom_headers"); if (stored) { try { const headers = JSON.parse(stored); delete headers[key]; localStorage.setItem("reex_custom_headers", JSON.stringify(headers)); } catch { // ignore JSON parse / storage errors } } } }, // Called on login/signup success with raw tokens setTokens: (tokens) => { const newAccessToken = tokens.accessToken; const refreshToken = tokens.refreshToken; accessToken = newAccessToken; if (typeof window !== "undefined") { if (refreshToken) { localStorage.setItem(apiConfig.auth.refreshTokenKey, refreshToken); localStorage.removeItem(apiConfig.auth.accessTokenKey); } else if (newAccessToken) { localStorage.setItem(apiConfig.auth.accessTokenKey, newAccessToken); localStorage.removeItem(apiConfig.auth.refreshTokenKey); } window.dispatchEvent(new Event("auth:login")); } }, // Called on login with tokens and optional custom headers setSession: (tokens, headers) => { if (tokens?.accessToken) { jwtTokenProvider.setTokens(tokens as { accessToken: string; refreshToken?: string }); } if (headers) { jwtTokenProvider.setCustomHeaders(headers); } if (typeof window !== "undefined" && !tokens?.accessToken) { window.dispatchEvent(new Event("auth:login")); } }, // Called on logout clearTokens: () => { accessToken = null; customHeaders = null; refreshPromise = null; if (typeof window !== "undefined") { localStorage.removeItem(apiConfig.auth.refreshTokenKey); localStorage.removeItem(apiConfig.auth.accessTokenKey); localStorage.removeItem("reex_custom_headers"); } }, // Exchange refresh token for a new access token refreshToken: async () => { // If a refresh is already in progress, await the existing promise instead of making a new request if (refreshPromise) { return refreshPromise; } refreshPromise = (async () => { try { if (typeof window === "undefined") return null; const storedRefreshToken = localStorage.getItem( apiConfig.auth.refreshTokenKey, ); // Fallback: use stored access token if no refresh token if (!storedRefreshToken) { const storedAccessToken = localStorage.getItem( apiConfig.auth.accessTokenKey, ); if (storedAccessToken) { accessToken = storedAccessToken; return accessToken; } throw new Error("No refresh token found"); } const response = await apiConfig.auth.refreshWithToken(storedRefreshToken); const isRawAxiosResponse = response?.config && response?.headers && response?.status; const unwrappedData = isRawAxiosResponse ? response.data : response; const responseData = apiConfig.unwrapResponseData ? (unwrappedData?.data ?? unwrappedData) : unwrappedData; const extracted = apiConfig.auth.extractTokens(responseData); if (!extracted?.accessToken && !extracted?.refreshToken) { console.error( "\n[Reex Auth Error] 🛑 Token extraction failed during refresh.\n" + "Both 'accessToken' and 'refreshToken' are empty.\n" + "Please update the `extractTokens` function in your `api.config.ts` so it correctly maps to your backend's response structure.\n" ); } const { accessToken: newAccess = null, refreshToken: newRefresh } = extracted || {}; accessToken = newAccess; if (newRefresh) { localStorage.setItem(apiConfig.auth.refreshTokenKey, newRefresh); } if (newAccess && typeof window !== "undefined") { window.dispatchEvent(new Event("auth:login")); } return accessToken; } catch { console.warn("Refresh failed, logging out..."); accessToken = null; localStorage.removeItem(apiConfig.auth.refreshTokenKey); localStorage.removeItem(apiConfig.auth.accessTokenKey); return null; } finally { refreshPromise = null; } })(); return refreshPromise; }, };

NextAuth Strategy

View code

auth-methods/next-auth/NextAuthGuard.tsx
// @internal — No changes needed "use client"; import { SessionProvider } from "next-auth/react"; import { useEffect } from "react"; /** * Authentication guard wrapper for NextAuth.js */ export const NextAuthGuard = ({ children }: { children: React.ReactNode }) => { return ( <SessionProvider> <NextAuthGuardInner>{children}</NextAuthGuardInner> </SessionProvider> ); }; const NextAuthGuardInner = ({ children }: { children: React.ReactNode }) => { // Listen for auth:logout events dispatched by core.ts when token refresh fails useEffect(() => { const handleLogout = () => { import("next-auth/react").then(({ signOut }) => signOut()); }; window.addEventListener("auth:logout", handleLogout); return () => window.removeEventListener("auth:logout", handleLogout); }, []); return <>{children}</>; };
auth-methods/next-auth/provider.ts
// @internal — No changes needed import { type TokenProvider } from "../types"; import { getSession } from "next-auth/react"; // Extend the Session type to include accessToken declare module "next-auth" { interface Session { accessToken?: string; } } let customHeaders: Record<string, string> | null = null; // TokenProvider for NextAuth.js - retrieves token from session interface NextAuthTokenProvider extends TokenProvider { setTokens?: (tokens: { accessToken: string; refreshToken?: string }) => void; setSession?: ( tokens?: { accessToken?: string; refreshToken?: string }, headers?: Record<string, string>, ) => void; clearTokens: () => void; setCustomHeaders: (headers: Record<string, string>) => void; getCustomHeaders: () => Record<string, string>; } export const nextAuthTokenProvider: NextAuthTokenProvider = { getToken: async () => { const session = await getSession(); return (session?.accessToken as string) || null; }, getCustomHeaders: () => { if (customHeaders) return customHeaders; if (typeof window !== "undefined") { const stored = localStorage.getItem("reex_custom_headers"); if (stored) { try { return JSON.parse(stored); } catch { return {}; } } } return {}; }, setCustomHeaders: (headers) => { customHeaders = headers; if (typeof window !== "undefined") { localStorage.setItem("reex_custom_headers", JSON.stringify(headers)); } }, refreshToken: undefined, // Placeholder methods for compatibility with other providers // NextAuth manages these internally via session setTokens: () => { if (process.env.NODE_ENV === "development") { console.warn( "[NextAuthTokenProvider] setTokens is a no-op. NextAuth manages tokens via session.", ); } }, setSession: (_tokens, headers) => { if (headers) { nextAuthTokenProvider.setCustomHeaders(headers); } }, clearTokens: () => { customHeaders = null; if (typeof window !== "undefined") { localStorage.removeItem("reex_custom_headers"); } if (process.env.NODE_ENV === "development") { console.warn( "[NextAuthTokenProvider] clearTokens token part is a no-op. Use signOut() from next-auth/react instead.", ); } }, };

hooks/ - App State & Notifications

Provides centralized application state management, particularly for notifications.

notification.ts: Notification context and standalone dispatcher.

View code

hooks/notification.ts
"use client"; import { createContext } from "react"; export type NotificationType = "success" | "error" | "warning" | "info"; export interface ApiNotification { id: string; type: NotificationType; message: string; statusCode?: number; timestamp: string; } export const NOTIFICATION_EVENT = "api:notification" as const; // Suppresses duplicate toasts when the same error fires multiple times in quick succession. const DEDUPE_WINDOW_MS = 2500; const recentNotifications = new Map<string, ReturnType<typeof setTimeout>>(); /** * Pushes an error or status notification outside of React (e.g. from API service files). * Identical notifications within 2.5 s are suppressed to prevent duplicate toasts. * * @example * pushNotification({ type: "error", message: "Failed to load profile", statusCode: 500 }); */ export const pushNotification = ( notification: Omit<ApiNotification, "id" | "timestamp">, ) => { if (typeof window === "undefined") return; const dedupeKey = `${notification.type}:${notification.statusCode ?? ""}:${notification.message}`; if (recentNotifications.has(dedupeKey)) return; // Auto-expire the key so the same error can reappear after the window closes. const timer = setTimeout(() => recentNotifications.delete(dedupeKey), DEDUPE_WINDOW_MS); recentNotifications.set(dedupeKey, timer); window.dispatchEvent( new CustomEvent(NOTIFICATION_EVENT, { detail: { ...notification, id: crypto.randomUUID(), timestamp: new Date().toISOString(), }, }), ); }; export interface NotificationContextValue { notifications: ApiNotification[]; dismiss: (id: string) => void; dismissAll: () => void; pushNotification: ( notification: Omit<ApiNotification, "id" | "timestamp">, ) => void; } export const NotificationContext = createContext<NotificationContextValue | null>( null, );

useNotification.ts: Consumer hook for notifications.

View code

hooks/useNotification.ts
import { useContext } from "react"; import { NotificationContext } from "./notification"; /** * Hook to consume the centralized notification state. * Must be used within a <NotificationProvider>. */ export const useNotification = () => { const context = useContext(NotificationContext); if (!context) { throw new Error( "useNotification must be used within a NotificationProvider", ); } return context; };

providers/ - Application Providers

ReexProvider.tsx: Sets the global strategy and provides the guards and notifications.

View code

providers/ReexProvider.tsx
// @internal — No changes needed "use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { type ReactNode, useState } from "react"; import { AuthProvider } from "../auth-methods/AuthProvider"; import type { AuthStrategy } from "../auth-methods/manager"; import AppNotification from "../custom/notification/AppNotification/AppNotification"; import { NotificationProvider } from "./NotificationProvider"; export function ReexProvider({ children, strategy, }: { children: ReactNode; strategy: AuthStrategy; }) { const [queryClient] = useState(() => new QueryClient()); return ( <QueryClientProvider client={queryClient}> <NotificationProvider> <AuthProvider strategy={strategy}>{children}</AuthProvider> <AppNotification /> </NotificationProvider> </QueryClientProvider> ); }

NotificationProvider.tsx: Notification context provider.

View code

providers/NotificationProvider.tsx
// @internal — No changes needed import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { type ApiNotification, NOTIFICATION_EVENT, pushNotification, NotificationContext, type NotificationContextValue, } from "../hooks/notification"; export interface UseNotificationOptions { maxNotifications?: number; autoDismissMs?: number | false; } const DEFAULT_MAX = 10; const DEFAULT_AUTO_DISMISS_MS = 5000; export const NotificationProvider = ({ children, options, }: { children: ReactNode; options?: UseNotificationOptions; }) => { const { maxNotifications = DEFAULT_MAX, autoDismissMs = DEFAULT_AUTO_DISMISS_MS, } = options ?? {}; const [notifications, setNotifications] = useState<ApiNotification[]>([]); const timersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>( new Map(), ); const dismiss = useCallback((id: string) => { setNotifications((prev) => prev.filter((n) => n.id !== id)); const timer = timersRef.current.get(id); if (timer) { clearTimeout(timer); timersRef.current.delete(id); } }, []); const dismissAll = useCallback(() => { setNotifications([]); timersRef.current.forEach((timer) => clearTimeout(timer)); timersRef.current.clear(); }, []); useEffect(() => { if (typeof window === "undefined") return; // Captured for safe cleanup in React Strict Mode const timers = timersRef.current; const handleNotification = (event: Event) => { const notification = (event as CustomEvent<ApiNotification>).detail; if (!notification?.id) return; setNotifications((prev) => { if (prev.some((n) => n.id === notification.id)) { return prev; } // Drop duplicates already on screen (guards against same-render-batch bursts). if (prev.some((n) => n.message === notification.message && n.type === notification.type)) { return prev; } const next = [...prev, notification]; if (next.length <= maxNotifications) { return next; } // Remove timers for notifications that are being evicted const removed = next.slice(0, next.length - maxNotifications); // Defer timer cleanup to keep the state updater pure. queueMicrotask(() => { removed.forEach(({ id }) => { const timer = timers.get(id); if (timer) { clearTimeout(timer); timers.delete(id); } }); }); return next.slice(next.length - maxNotifications); }); if (autoDismissMs !== false) { const timer = setTimeout(() => { dismiss(notification.id); }, autoDismissMs); timers.set(notification.id, timer); } }; window.addEventListener(NOTIFICATION_EVENT, handleNotification); return () => { window.removeEventListener(NOTIFICATION_EVENT, handleNotification); timers.forEach((timer) => clearTimeout(timer)); timers.clear(); }; }, [maxNotifications, autoDismissMs, dismiss]); const contextValue = useMemo<NotificationContextValue>( () => ({ notifications, dismiss, dismissAll, pushNotification, }), [notifications, dismiss, dismissAll], ); return ( <NotificationContext.Provider value={contextValue}> {children} </NotificationContext.Provider> ); };

definitions/ - REST API Endpoints

This folder contains sample API endpoint definitions.

  • Editable: You can make changes to these files to match your API.
  • Two-Way Sync: Changes here are reflected in the Reex API Builder UI.

Note on File Uploads: Instead of manually setting the header inside the function, the recommended approach is to use the @contentType multipart/form-data JSDoc tag above the endpoint.

  1. The Reex UI uses this tag to generate the correct file upload interface.
  2. Passing a FormData object directly allows Axios to automatically calculate and append the necessary boundary= string. Manually setting the header can cause backend parsing errors!
/** * Upload user photo * @auth * @contentType multipart/form-data */ uploadPhoto: async (payload: FormData) => { return await apiClient.post(`/user/profile/photo`, payload); }

authentication.ts: Authentication endpoints (Refresh, Signup, Login, Logout).

View code

definitions/authentication.ts
import { apiClient } from "../core"; import { type ReexDefinition } from "../.reex/config"; import { type post_authRefresh } from "../types/authentication/post_authRefresh"; import { type post_authSignup } from "../types/authentication/post_authSignup"; import { type post_authLogin } from "../types/authentication/post_authLogin"; import { type post_authLogout } from "../types/authentication/post_authLogout"; export interface AuthSignupPayload { username: string; password: string; } export interface AuthLoginPayload { username: string; password: string; } export const authenticationApi = { /** @description Refresh access token */ post_authRefresh: (): Promise<post_authRefresh> => apiClient.post(`/auth/refresh`, {}), /** @description Register a new user */ post_authSignup: (payload: AuthSignupPayload): Promise<post_authSignup> => apiClient.post(`/auth/signup`, payload), /** @description Login a user */ post_authLogin: (payload: AuthLoginPayload): Promise<post_authLogin> => apiClient.post(`/auth/login`, payload), /** @description Logout a user */ post_authLogout: (): Promise<post_authLogout> => apiClient.post(`/auth/logout`, {}) } satisfies ReexDefinition;

todos.ts: CRUD operations for Todos.

View code

definitions/todos.ts
/* eslint-disable @typescript-eslint/no-explicit-any */ import { apiClient } from "../core"; import { type ReexDefinition } from "../.reex/config"; import { type get_todos } from "../types/todos/get_todos"; import { type post_todos } from "../types/todos/post_todos"; import { type put_todos } from "../types/todos/put_todos"; export interface TodosPayload { task: string; priority?: number; } export const todosApi = { /** @description Retrieve a list of todos for the authenticated user @auth */ get_todos: (): Promise<get_todos> => apiClient.get(`/todos`), /** @description Create a new todo @auth */ post_todos: (payload: TodosPayload): Promise<post_todos> => apiClient.post(`/todos`, payload), /** @description Update a todo @auth @param id The todo ID */ put_todos: (id: string): Promise<put_todos> => apiClient.put(`/todos/${id}`, {}), /** @description Delete a todo @auth @param id The todo ID */ delete_todos: (id: string): Promise<void> => apiClient.delete(`/todos/${id}`) } satisfies ReexDefinition;

generated/ - React Query Hooks

Auto-generated hooks based on your definitions/.

query.config.ts: React Query wrapper hooks. Edit this file to configure defaults like refetchOnWindowFocus.

View code

generated/query.config.ts
// @user-config — customize query defaults here import { type QueryKey, type UseMutationOptions, type UseQueryOptions, type UseQueryResult, type DefaultError, useMutation, useQuery, } from "@tanstack/react-query"; import { getActiveProvider } from "../auth-methods/manager"; // 1. Mutation Wrapper export const useApiMutation = < TData = unknown, TVariables = void, TError = DefaultError, TContext = unknown, >( mutationFn: (variables: TVariables) => Promise, options?: Omit< UseMutationOptions<TData, TError, TVariables, TContext>, "mutationFn" >, ) => { return useMutation<TData, TError, TVariables, TContext>({ mutationFn, ...options, }); }; // 2. Query Wrapper export const useApiQuery = < TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( queryKey: TQueryKey, queryFn: () => Promise, options?: Omit< UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, "queryKey" | "queryFn" >, ): UseQueryResult<TData, TError> => { const isEnabled = options?.enabled !== false ? !!getActiveProvider()?.getToken?.() : false; return useQuery<TQueryFnData, TError, TData, TQueryKey>({ queryKey, queryFn, refetchOnWindowFocus: false, // Skip retrying auth/client errors (401, 403, 404, 422) — these won't resolve on retry. retry: (failureCount: number, error: unknown) => { const e = error as { statusCode?: number; status?: number; response?: { status?: number } }; const status = e?.statusCode ?? e?.status ?? e?.response?.status; if (status === 401 || status === 403 || status === 404 || status === 422) { return false; } return failureCount < 1; }, ...options, enabled: isEnabled, }); };

index.ts: Barrel exports.

View code

generated/index.ts
// Generated file - DO NOT EDIT export * from "./useAuthenticationQueries"; export * from "./useTodosQueries";

useAuthenticationQueries.ts: Hooks for Authentication.

View code

generated/useAuthenticationQueries.ts
/* eslint-disable @typescript-eslint/no-explicit-any */ // Generated file - DO NOT EDIT import { useQueryClient, type UseMutationOptions } from "@tanstack/react-query"; import { authenticationApi } from "../definitions/authentication"; import { useApiMutation } from "./query.config"; // Helper Types type ApiData any> = Awaited<ReturnType>; type ApiVars any> = Parameters[0]; export const authenticationKeys = { all: ["authentication"] as const, }; export const usePostAuthSignupMutation = ( options?: Omit< UseMutationOptions<ApiData, Error, ApiVars>, "mutationFn" > & { invalidate?: boolean } ) => { const queryClient = useQueryClient(); return useApiMutation(authenticationApi.post_authSignup, { ...options, onSuccess: (data, variables, context) => { if (options?.invalidate !== false) { queryClient.invalidateQueries({ queryKey: authenticationKeys.all }); } (options?.onSuccess as any)?.(data, variables, context); }, }); }; export const usePostAuthLoginMutation = ( options?: Omit< UseMutationOptions<ApiData, Error, ApiVars>, "mutationFn" > & { invalidate?: boolean } ) => { const queryClient = useQueryClient(); return useApiMutation(authenticationApi.post_authLogin, { ...options, onSuccess: (data, variables, context) => { if (options?.invalidate !== false) { queryClient.invalidateQueries({ queryKey: authenticationKeys.all }); } (options?.onSuccess as any)?.(data, variables, context); }, }); }; export const usePostAuthLogoutMutation = ( options?: Omit< UseMutationOptions<ApiData, Error, ApiVars>, "mutationFn" > & { invalidate?: boolean } ) => { const queryClient = useQueryClient(); return useApiMutation(authenticationApi.post_authLogout, { ...options, onSuccess: (data, variables, context) => { if (options?.invalidate !== false) { queryClient.invalidateQueries({ queryKey: authenticationKeys.all }); } (options?.onSuccess as any)?.(data, variables, context); }, }); };

useTodosQueries.ts: Hooks for Todos.

View code

generated/useTodosQueries.ts
/* eslint-disable @typescript-eslint/no-explicit-any */ // Generated file - DO NOT EDIT import { useQueryClient, type UseMutationOptions, type UseQueryOptions } from "@tanstack/react-query"; import { todosApi } from "../definitions/todos"; import { useApiQuery, useApiMutation } from "."; // Helper Types type ApiData any> = Awaited<ReturnType>; type ApiVars any> = Parameters[0]; export const todosKeys = { all: ["todos"] as const, get_todos: (params?: ApiVars) => [...todosKeys.all, "get_todos", params] as const, }; export const useGetTodosQuery = >( options?: Omit< UseQueryOptions<ApiData, Error, TData>, "queryKey" | "queryFn" > ) => useApiQuery( todosKeys.get_todos(), () => todosApi.get_todos(), options ); export const usePostTodosMutation = ( options?: Omit< UseMutationOptions<ApiData, Error, ApiVars>, "mutationFn" > & { invalidate?: boolean } ) => { const queryClient = useQueryClient(); return useApiMutation(todosApi.post_todos, { ...options, onSuccess: (data, variables, context) => { if (options?.invalidate !== false) { queryClient.invalidateQueries({ queryKey: todosKeys.all }); } (options?.onSuccess as any)?.(data, variables, context); }, }); };

hooks/ - Custom Hooks

useAuthSession.ts: Utility hook for useAuthSession.

hooks/useAuthSession.ts
// @internal — No changes needed import { useCallback, useEffect, useState } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { getActiveProvider } from "../auth-methods/manager"; import { apiConfig } from "../api.config"; export type AuthStatus = "loading" | "authenticated" | "unauthenticated"; /** * Hook to manage authentication state, active sessions, and logout teardown. * * @example * const { status, isAuthenticated, setSession, clearSession } = useAuthSession(); * if (status === "loading") return <Loading />; * if (!isAuthenticated) return <Login />; */ export const useAuthSession = () => { const provider = getActiveProvider(); const queryClient = useQueryClient(); const [status, setStatus] = useState<AuthStatus>("loading"); useEffect(() => { const checkAuth = async () => { try { const token = await provider?.getToken?.(); setStatus(token ? "authenticated" : "unauthenticated"); } catch { setStatus("unauthenticated"); } }; checkAuth(); // Listen to dispatch events to update UI instantly without refresh const handleLogin = () => setStatus("authenticated"); const handleLogout = () => { provider?.clearTokens?.(); queryClient.cancelQueries(); queryClient.clear(); setStatus("unauthenticated"); if (typeof window !== "undefined") { const route = apiConfig.auth?.loginRoute || window.origin; window.location.href = route; } }; if (typeof window !== "undefined") { window.addEventListener("auth:login", handleLogin); window.addEventListener("auth:logout", handleLogout); } return () => { if (typeof window !== "undefined") { window.removeEventListener("auth:login", handleLogin); window.removeEventListener("auth:logout", handleLogout); } }; }, [provider, queryClient]); const getToken = useCallback( () => provider?.getToken?.() ?? null, [provider], ); const setSession = useCallback( ( tokens?: { accessToken?: string; refreshToken?: string }, headers?: Record<string, string>, ) => { if (provider?.setSession) { provider.setSession(tokens, headers); } else if (tokens?.accessToken && provider?.setTokens) { provider.setTokens(tokens as { accessToken: string; refreshToken?: string }); if (headers && provider?.setCustomHeaders) { provider.setCustomHeaders(headers); } } setStatus("authenticated"); }, [provider], ); const clearSession = useCallback(async () => { provider?.clearTokens?.(); // Clear React Query cache await queryClient.cancelQueries(); queryClient.clear(); setStatus("unauthenticated"); if (typeof window !== "undefined") { window.dispatchEvent(new Event("auth:logout")); const route = apiConfig.auth?.loginRoute || window.origin; window.location.href = route; } }, [provider, queryClient]); return { status, isAuthenticated: status === "authenticated", isLoading: status === "loading", getToken, setSession, clearSession, }; };

useNotification.ts: Utility hook for useNotification.

hooks/useNotification.ts
import type { ReactNode } from "react"; import React, { createContext, useCallback, useContext, useEffect, useRef, useState, } from "react"; export type NotificationType = "success" | "error" | "warning" | "info"; export interface ApiNotification { id: string; type: NotificationType; message: string; statusCode?: number; timestamp: string; } export interface UseNotificationOptions { maxNotifications?: number; autoDismissMs?: number | false; } export const NOTIFICATION_EVENT = "api:notification" as const; const DEFAULT_MAX = 10; const DEFAULT_AUTO_DISMISS_MS = 5000; interface NotificationContextValue { notifications: ApiNotification[]; dismiss: (id: string) => void; dismissAll: () => void; pushNotification: ( notification: Omit<ApiNotification, "id" | "timestamp">, ) => void; } const NotificationContext = createContext<NotificationContextValue | null>( null, ); // Suppresses duplicate toasts when the same error fires multiple times in quick succession. const DEDUPE_WINDOW_MS = 2500; const recentNotifications = new Map<string, ReturnType<typeof setTimeout>>(); /** * Pushes an error or status notification outside of React (e.g. from API service files). * Identical notifications within 2.5 s are suppressed to prevent duplicate toasts. * * @example * pushNotification({ type: "error", message: "Failed to load profile", statusCode: 500 }); */ export const pushNotification = ( notification: Omit<ApiNotification, "id" | "timestamp">, ) => { if (typeof window === "undefined") return; const dedupeKey = `${notification.type}:${notification.statusCode ?? ""}:${notification.message}`; if (recentNotifications.has(dedupeKey)) return; // Auto-expire the key so the same error can reappear after the window closes. const timer = setTimeout(() => recentNotifications.delete(dedupeKey), DEDUPE_WINDOW_MS); recentNotifications.set(dedupeKey, timer); window.dispatchEvent( new CustomEvent(NOTIFICATION_EVENT, { detail: { ...notification, id: crypto.randomUUID(), timestamp: new Date().toISOString(), }, }), ); }; export const NotificationProvider = ({ children, options, }: { children: ReactNode; options?: UseNotificationOptions; }) => { const { maxNotifications = DEFAULT_MAX, autoDismissMs = DEFAULT_AUTO_DISMISS_MS, } = options ?? {}; const [notifications, setNotifications] = useState<ApiNotification[]>([]); const timersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>( new Map(), ); const dismiss = useCallback((id: string) => { setNotifications((prev) => prev.filter((n) => n.id !== id)); const timer = timersRef.current.get(id); if (timer) { clearTimeout(timer); timersRef.current.delete(id); } }, []); const dismissAll = useCallback(() => { setNotifications([]); timersRef.current.forEach((timer) => clearTimeout(timer)); timersRef.current.clear(); }, []); useEffect(() => { if (typeof window === "undefined") return; // Captured for safe cleanup in React Strict Mode const timers = timersRef.current; const handleNotification = (event: Event) => { const notification = (event as CustomEvent<ApiNotification>).detail; if (!notification?.id) return; setNotifications((prev) => { if (prev.some((n) => n.id === notification.id)) return prev; // Drop duplicates already on screen (guards against same-render-batch bursts). if (prev.some((n) => n.message === notification.message && n.type === notification.type)) return prev; const next = [...prev, notification]; return next.length > maxNotifications ? next.slice(next.length - maxNotifications) : next; }); if (autoDismissMs !== false) { const timer = setTimeout(() => { setNotifications((prev) => prev.filter((n) => n.id !== notification.id), ); timers.delete(notification.id); }, autoDismissMs); timers.set(notification.id, timer); } }; window.addEventListener(NOTIFICATION_EVENT, handleNotification); return () => { window.removeEventListener(NOTIFICATION_EVENT, handleNotification); timers.forEach((timer) => clearTimeout(timer)); timers.clear(); }; }, [maxNotifications, autoDismissMs]); const contextValue: NotificationContextValue = { notifications, dismiss, dismissAll, pushNotification, // Reference the standalone function for ease of use from the hook }; return React.createElement( NotificationContext.Provider, { value: contextValue }, children, ); }; /** * Hook to consume the centralized notification state. * Must be used within a <NotificationProvider>. */ export const useNotification = () => { const context = useContext(NotificationContext); if (!context) { throw new Error( "useNotification must be used within a NotificationProvider", ); } return context; };

useHeaders.ts: Utility hook for custom header management.

hooks/useHeaders.ts
import { useCallback } from "react"; import { getActiveProvider } from "../auth-methods/manager"; /** * Hook to manage custom API headers securely. */ export const useHeaders = () => { const provider = getActiveProvider(); const getCustomHeaders = useCallback( () => provider?.getCustomHeaders?.() ?? {}, [provider], ); const setCustomHeaders = useCallback( (headers: Record<string, string>) => { provider?.setCustomHeaders?.(headers); }, [provider], ); const removeCustomHeader = useCallback( (key: string) => { provider?.removeCustomHeader?.(key); }, [provider], ); return { getCustomHeaders, setCustomHeaders, removeCustomHeader }; };

providers/ - Query Provider

View code

providers/QueryProvider.tsx
"use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useState } from "react"; export function QueryProvider({ children }: { children: React.ReactNode }) { const [queryClient] = useState(() => new QueryClient()); return {children}; }

types/ - TypeScript Interfaces

View Authentication Types

types/authentication/post_authLogin.ts
export type post_authLogin = unknown;
types/authentication/post_authLogout.ts
export type post_authLogout = unknown;
types/authentication/post_authRefresh.ts
export type post_authRefresh = unknown;
types/authentication/post_authSignup.ts
export type post_authSignup = unknown;

View Todo Types

types/todos/get_todos.ts
export type get_todos = unknown;
types/todos/post_todos.ts
export type post_todos = unknown;

api.config.ts - User Configuration

This file is designed specifically for you to edit. It contains constants and mappings customized to your backend settings, merging both core API and authentication settings.

api.config.ts: Centralized configuration file.

View code

api.config.ts
// @user-config — Update routes, keys, and endpoints to match your backend. import axios from "axios"; import { type ReexConfig } from "./.reex/config"; // Replace this with your API base URL or an environment variable // (e.g. process.env.NEXT_PUBLIC_API_URL). const BASE_URL = "https://example.com/api/v1"; export const apiConfig: ReexConfig = { baseURL: BASE_URL, // Set to `true` if your API wraps every response in a top-level `data` property. // When enabled, also turn on "Unwrap Response Data" in the Reex UI before // saving or copying generated response interfaces. unwrapResponseData: false, // Enable request/response logging. // For development only, consider: // process.env.NODE_ENV === "development" // or // import.meta.env.DEV enableApiLogging: false, auth: { // --------------------------------------------------------------------------- // General authentication configuration // --------------------------------------------------------------------------- // The route users will be redirected to when logged out. // If left undefined, it defaults to the application root (window.origin). loginRoute: "/login", // Refresh token endpoint. Update this to match your backend. refreshEndpoint: `${BASE_URL}/auth/refresh`, // --------------------------------------------------------------------------- // JWT / Bearer token authentication // Use these options when the `ReexProvider` auth strategy is `"jwt"`. // --------------------------------------------------------------------------- // Send the refresh token to your backend to obtain a new access token. // Rename `refreshToken` in the request body if your backend expects // a different field name (e.g. `refresh_token`). refreshWithToken: async (storedRefreshToken: string) => axios.post(apiConfig.auth.refreshEndpoint, { refreshToken: storedRefreshToken, }), // Extract authentication tokens from your backend response. // // Note: `responseBody` is affected by the `unwrapResponseData` setting. // - When `unwrapResponseData` is `true`, `responseBody` contains the // unwrapped `data` object. // - When `unwrapResponseData` is `false`, `responseBody` contains the // full response body. // // Update these paths to match your API response structure. extractTokens: (responseBody: Record<string, unknown>) => ({ accessToken: responseBody?.accessToken as string | undefined, refreshToken: responseBody?.refreshToken as string | undefined, }), // Storage key names for authentication tokens. // Change these if you prefer different key names. // // Note: The access token is stored in memory by default. // It is only persisted to localStorage if your backend does not // return a refresh token. accessTokenKey: "access_token", refreshTokenKey: "refresh_token", // --------------------------------------------------------------------------- // Cookie-based authentication // Use this when the `ReexProvider` auth strategy is `"cookie"`. // --------------------------------------------------------------------------- // Request a new access token using HttpOnly cookies. refreshWithCookie: async () => axios.post(apiConfig.auth.refreshEndpoint, undefined, { withCredentials: true, // Add any headers required by your backend when refreshing tokens. // headers: { "X-Client-ID": "1234" }, }), }, };
Last updated on