| name | stacks-http |
| description | Use when working with HTTP utilities in a Stacks application — HTTP status codes, making outbound HTTP requests via HttxClient, reactive fetch composables (useFetch/createFetch), or HTTP-related helpers. Covers @stacksjs/http, @stacksjs/httx, and the fetch composables in @stacksjs/composables. |
| license | MIT |
| compatibility | Bun >= 1.3.0, TypeScript |
| allowed-tools | Read Edit Write Bash Grep Glob |
Stacks HTTP
Key Paths
- Core package:
storage/framework/core/http/src/
- Composables (useFetch/createFetch):
storage/framework/core/composables/src/useFetch.ts, storage/framework/core/composables/src/createFetch.ts
- Buddy CLI command:
storage/framework/core/buddy/src/commands/http.ts
- httx dependency (installed):
node_modules/@stacksjs/httx/
- Package:
@stacksjs/http (status codes), @stacksjs/httx (HTTP client), @stacksjs/composables (useFetch/createFetch)
Source Files
http/src/
└── index.ts # Response enum — all HTTP status codes (100-511)
composables/src/
├── useFetch.ts # Chainable reactive fetch composable (builder pattern)
└── createFetch.ts # Factory for pre-configured useFetch with baseUrl
buddy/src/commands/
└── http.ts # `buddy http [domain]` CLI command using HttxClient
Response Enum — HTTP Status Codes (index.ts)
The @stacksjs/http package exports a single Response enum with all standard HTTP status codes:
import { Response } from '@stacksjs/http'
Response.HTTP_OK
Response.HTTP_CREATED
Response.HTTP_NO_CONTENT
Response.HTTP_MOVED_PERMANENTLY
Response.HTTP_NOT_MODIFIED
Response.HTTP_BAD_REQUEST
Response.HTTP_UNAUTHORIZED
Response.HTTP_FORBIDDEN
Response.HTTP_NOT_FOUND
Response.HTTP_UNPROCESSABLE_ENTITY
Response.HTTP_TOO_MANY_REQUESTS
Response.HTTP_INTERNAL_SERVER_ERROR
Response.HTTP_SERVICE_UNAVAILABLE
Full Status Code Categories
- 1xx Informational:
HTTP_CONTINUE (100), HTTP_SWITCHING_PROTOCOLS (101)
- 2xx Success:
HTTP_OK (200), HTTP_CREATED (201), HTTP_ACCEPTED (202), HTTP_NON_AUTHORITATIVE_INFORMATION (203), HTTP_NO_CONTENT (204), HTTP_RESET_CONTENT (205), HTTP_PARTIAL_CONTENT (206)
- 3xx Redirection:
HTTP_MULTIPLE_CHOICES (300), HTTP_MOVED_PERMANENTLY (301), HTTP_FOUND (302), HTTP_SEE_OTHER (303), HTTP_NOT_MODIFIED (304), HTTP_USE_PROXY (305), HTTP_UNUSED (306), HTTP_TEMPORARY_REDIRECT (307), HTTP_PERMANENT_REDIRECT (308)
- 4xx Client Error:
HTTP_BAD_REQUEST (400) through HTTP_UNAVAILABLE_FOR_LEGAL_REASONS (451) — includes HTTP_I_AM_A_TEAPOT (418)
- 5xx Server Error:
HTTP_INTERNAL_SERVER_ERROR (500) through HTTP_NETWORK_AUTHENTICATION_REQUIRED (511)
HttxClient — Outbound HTTP Client (@stacksjs/httx)
The @stacksjs/httx package provides the HttxClient class for making outbound HTTP requests. It wraps Bun's fetch() with retry logic, timeouts, timing data, and Result-based error handling via ts-error-handling.
import { HttxClient } from '@stacksjs/httx'
const client = new HttxClient(config?: Partial<HttxConfig>)
const result = await client.request<T>(url: string, options: RequestOptions): Promise<Result<HttxResponse<T>, Error>>
HttxConfig
interface HttxConfig {
verbose?: boolean | string[]
defaultHeaders?: Record<string, string>
baseUrl?: string
timeout?: number
retry?: RetryOptions
}
RequestOptions
interface RequestOptions {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'
query?: Record<string, string>
form?: boolean
multipart?: boolean
json?: boolean
unix?: string
proxy?: string
downloadProgress?: (progress: number) => void
retry?: RetryOptions
stream?: boolean
acceptHeader?: string
verbose?: boolean
timeout?: number
body?: BodyInit | <, >
?:
}
RetryOptions
interface RetryOptions {
retries?: number
retryDelay?: number
retryOn?: number[]
shouldRetry?: (error: Error, attempt: number) => boolean
}
HttxResponse
interface HttxResponse<T = unknown> {
status: number
statusText: string
headers: Headers
data: T
timings: {
start: number
end: number
duration: number
}
}
Result Pattern (from ts-error-handling)
client.request() returns a Result<HttxResponse<T>, Error>. Use .match() to handle success/failure:
const result = await client.request('https://api.example.com/users', {
method: 'GET',
})
result.match({
ok: (response) => {
console.log(response.status)
console.log(response.timings.duration)
console.log(response.data)
},
err: (error) => {
console.error(error.message)
},
})
Or use result.isOk / result.value / result.error directly.
Error Classes
class HttxError extends Error {
readonly context?: Record<string, unknown>
}
class HttxRequestError extends HttxError {
readonly method: string
readonly url: string
readonly statusCode?: number
}
class HttxTimeoutError extends HttxError {
readonly method: string
readonly url: string
readonly timeout: number
}
class HttxNetworkError extends HttxError {
readonly method: string
readonly url: string
readonly originalError?: Error
}
class HttxResponseError extends HttxRequestError {
readonly :
?:
}
Utility Exports
import { debugLog, sleep } from '@stacksjs/httx'
debugLog(category: string, message: string | (() => string), verbose?: boolean | string[]): void
sleep(ms: number): Promise<void>
Config Exports
import { config, defaultConfig, getConfig } from '@stacksjs/httx'
const config: HttxConfig
const defaultConfig: HttxConfig
function getConfig(): Promise<HttxConfig>
useFetch — Reactive Fetch Composable (@stacksjs/composables)
A chainable, reactive fetch composable using the builder pattern. Uses native fetch() under the hood (not HttxClient). Returns reactive Ref values from @stacksjs/stx.
import { useFetch } from '@stacksjs/composables'
const { data, error, isFetching } = await useFetch('/api/posts').get().json()
FetchBuilder Interface
interface FetchBuilder {
get: () => FetchBuilder
post: (body?: string) => FetchBuilder
patch: (body?: string) => FetchBuilder
put: (body?: string) => FetchBuilder
delete: () => FetchBuilder
json: () => UseFetchResult
}
UseFetchResult Interface
interface UseFetchResult {
data: Ref<any>
error: Ref<any>
isFetching: Ref<boolean>
then: (resolve: (value: { data: Ref<any>, error: Ref<any> }) => void) => Promise<void>
}
Usage Examples
import { useFetch } from '@stacksjs/composables'
const { data, error } = await useFetch('/api/users').get().json()
const { data, error } = await useFetch('/api/users')
.post(JSON.stringify({ name: 'Alice', email: 'alice@example.com' }))
.json()
const { data, error } = await useFetch('/api/users/1')
.patch(JSON.stringify({ name: 'Updated' }))
.json()
const { data, error } = await useFetch('/api/users/1').delete().json()
const result = useFetch('/api/items').get().json()
result.isFetching.value
result..
result..
How json() Works Internally
- Builds a
RequestInit with the configured method and Accept: application/json header
- For POST/PATCH/PUT with a body, also sets
Content-Type: application/json
- Calls native
fetch(url, init)
- On success (
response.ok), parses JSON into data.value
- On HTTP error, parses JSON into
error.value
- On network error, sets
error.value to the caught Error
- Always sets
isFetching.value = false in finally
createFetch — Pre-configured Fetch Factory (@stacksjs/composables)
Creates a reusable useFetch instance with a pre-configured base URL.
import { createFetch } from '@stacksjs/composables'
interface CreateFetchOptions {
baseUrl?: string
options?: RequestInit
}
const useApi = createFetch({ baseUrl: 'https://api.example.com' })
const { data, error } = await useApi('/users').get().json()
URL Resolution
createFetch joins the base URL and path by stripping trailing/leading slashes:
baseUrl: 'https://api.example.com/' + '/users' → 'https://api.example.com/users'
baseUrl: 'https://api.example.com' + 'users' → 'https://api.example.com/users'
CLI Command — buddy http
buddy http [domain]
buddy http example.com
buddy http -v example.com
The CLI command uses HttxClient from @stacksjs/httx:
- Prepends
https:// if the URL does not start with http
- Logs status code, status text, and duration
- Prints response body (string or pretty-printed JSON)
Gotchas
@stacksjs/http is status codes only — it exports a single Response enum with HTTP status code constants. It does NOT contain an HTTP client, request helpers, or fetch utilities
- The actual HTTP client is
@stacksjs/httx — a separate package (not part of @stacksjs/http). It is used by buddy http and available for general use
useFetch uses native fetch(), not HttxClient — despite @stacksjs/httx being available, the useFetch composable calls the global fetch() directly
useFetch only supports .json() format — there is no .text(), .blob(), or .arrayBuffer() method. The only terminal method is .json()
- Method chaining overrides previous method — calling
.post('body').get() will send a GET request (last method wins, body is cleared for GET/DELETE)
createFetch ignores the options parameter — the CreateFetchOptions.options field is declared in the interface but not used in the implementation
useFetch body is only sent for POST/PATCH/PUT — GET and DELETE requests strip the body even if one was set
- The
Response enum name conflicts with the global Response — importing Response from @stacksjs/http shadows the native Fetch API Response type. Use a named import alias if both are needed: import { Response as HttpStatus } from '@stacksjs/http'
- HttxClient uses Result pattern —
request() returns Result<HttxResponse, Error> from ts-error-handling, not a plain Promise. Use .match(), .isOk, or .value/.error to unwrap
useFetch returns reactive Refs — data, error, and are from . Access values via property