executeRequest

Universal fetch wrapper that auto-injects the CSRF header on the client, proxies request headers during SSR, captures Set-Cookie responses for transparent token rotation, and returns a normalized Results envelope.

executeRequest is the recommended way to make authenticated requests from Vue components, pages, and server routes. It wraps ofetch with three things already wired:

  • On the client, the CSRF header is injected automatically from the __Host-csrf cookie.
  • During Vue SSR, incoming request headers are proxied to the upstream call so cookies flow through.
  • Response Set-Cookie headers are captured during SSR and re-attached to the outgoing response, which makes token rotation transparent.

Every response is normalized to a Results<T> envelope so call sites can branch on a single ok field rather than unwinding fetch errors.

import { executeRequest } from 'auth-h3client/client'

const result = await executeRequest<{ name: string }>('/api/profile', 'GET')

if (result.ok) {
  console.log(result.data.name)
}

Signature

function executeRequest<T>(
  url: string,
  method: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH',
  body?: object,
  customHeaders?: Record<string, string>,
  customOptions?: FetchOptions<'json'>,
  context?: ApiContext,
): Promise<Results<T>>
ParameterTypeDescription
urlstringEndpoint URL to fetch
method'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'HTTP method
bodyobjectFor GET, sent as query parameters. For other methods, sent as the JSON body
customHeadersRecord<string, string>Extra headers merged onto the request
customOptionsFetchOptions<'json'>Additional ofetch options (timeout, signal, retry, etc.)
contextApiContextServer-side context (see below)
interface ApiContext {
  fetcher?: H3Event$Fetch | $Fetch<unknown, NitroFetchRequest>
  event?: H3Event
  headers?: Record<string, string>
}

Return type

type Results<T> =
  | { ok: true; data: T; date: string }
  | { ok: false; reason: string; date: string }
  • ok: true means the upstream call returned a successful JSON payload. data is the typed body.
  • ok: false means the upstream call failed, returned a non-2xx status, or produced an empty body. reason is a short message suitable for logging or displaying to the user.

executeRequest never throws. All errors are caught and returned as an { ok: false } envelope so call sites stay linear.


Client-side behavior

  • Reads the CSRF token via getCsrfToken() and adds X-CSRF-Token to the headers.
  • Uses the global $fetch as the default fetcher.
const result = await executeRequest<{ ok: boolean }>(
  '/api/account/settings',
  'POST',
  { theme: 'dark' },
)

Server-side behavior (SSR)

During Vue SSR, the composable needs to proxy the incoming request headers so cookies are forwarded to the gateway route. Pass the SSR context explicitly:

server handler or Nuxt composable
const result = await executeRequest<Data>(
  '/api/downstream',
  'GET',
  {},
  {},
  {},
  {
    headers: useRequestHeaders(),
    event: useRequestEvent(),
    fetcher: useRequestFetch(),
  },
)

When context.headers is provided, they are merged into the outgoing request. When context.event is provided, any Set-Cookie headers on the upstream response are appended to the outgoing response via appendResponseHeader, which is how token rotation surfaces to the browser during SSR.


Query vs body

For GET requests, the body argument is sent as a query object. For all other methods, it is sent as the JSON body. There is no way to send both a query and a body from the same call: if you need query parameters on a POST, append them to the URL directly or pass them via customOptions.query.


Timeout and error handling

Each call has a default timeout of 15 seconds. The underlying fetch is configured with ignoreResponseError: true, so non-2xx responses resolve instead of throwing. executeRequest inspects the status and returns an { ok: false } envelope for:

  • Non-2xx status codes
  • Missing response body
  • Fetch-level errors (network, abort, timeout)
  • Responses whose body already contains { ok: false }

See CSRF Protection for the CSRF header contract and the double-submit pattern executeRequest implements.

Logo