executeRequest
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-csrfcookie. - During Vue SSR, incoming request headers are proxied to the upstream call so cookies flow through.
- Response
Set-Cookieheaders 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>>
| Parameter | Type | Description |
|---|---|---|
url | string | Endpoint URL to fetch |
method | 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH' | HTTP method |
body | object | For GET, sent as query parameters. For other methods, sent as the JSON body |
customHeaders | Record<string, string> | Extra headers merged onto the request |
customOptions | FetchOptions<'json'> | Additional ofetch options (timeout, signal, retry, etc.) |
context | ApiContext | Server-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: truemeans the upstream call returned a successful JSON payload.datais the typed body.ok: falsemeans the upstream call failed, returned a non-2xx status, or produced an empty body.reasonis 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 addsX-CSRF-Tokento the headers. - Uses the global
$fetchas 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:
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.