# HttpClient

Internal HTTP client that wraps native fetch with typed methods, automatic serialization, and path parameter resolution.

## Overview

`HttpClient` is the low-level HTTP client at the core of Courier. It wraps the native `fetch` API and provides typed HTTP method functions (`get`, `post`, `put`, `patch`, `del`, `head`, `options`, `query`). You never create an `HttpClient` directly — it is created internally by `executeRequest`.

> **Internal class**
>
> `HttpClient` is used internally by Courier. Understanding it helps with debugging and writing the closure an endpoint passes to `.via()`, but you don't instantiate it yourself.

## HTTP Methods

At definition time `.via()` hands its closure an `HttpWire` — a recorder that
captures which method and path the endpoint addresses. At request time the same
closure runs against an `HttpClient` bound to the source, whose six methods carry
the call out:

```ts
get: <T>(url?, variables?, options?) => Promise<HttpResponse<T>>
post: <T>(url?, variables?, options?) => Promise<HttpResponse<T>>
put: <T>(url?, variables?, options?) => Promise<HttpResponse<T>>
patch: <T>(url?, variables?, options?) => Promise<HttpResponse<T>>
del: <T>(url?, variables?, options?) => Promise<HttpResponse<T>>
head: <T>(url?, options?) => Promise<HttpResponse<T>>
```

## HttpResponse

Every method returns an `HttpResponse` wrapping status, headers, and typed data:

```ts
type HttpResponse<T = unknown> = {
  status: number    // HTTP status code
  headers: Headers  // Response headers
  data: T           // Parsed response body
}
```

## Variable Handling

How variables are sent depends on the HTTP method:

- **GET, DELETE, HEAD** — variables become search parameters
- **POST, PUT, PATCH** — variables become JSON body (or FormData if detected)

```ts
// GET: variables → search params
http.get("", { page: 1, limit: 10 })
// → GET /users?page=1&limit=10

// POST: variables → JSON body
http.post("", { name: "Jane", email: "jane@test.com" })
// → POST /users  body: { name: "Jane", email: "jane@test.com" }

// FormData detected automatically
http.post("", formData)
// → POST /users  body: FormData (multipart)
```

## Path Parameters

`:param` syntax in URLs is replaced with matching variable values. Remaining variables go to body or search params:

```ts
http.get(":id", { id: "123", include: "posts" })
// → GET /users/123?include=posts

http.put(":id", { id: "123", name: "Alice" })
// → PUT /users/123  body: { name: "Alice" }

http.get(":userId/posts/:postId", { userId: "1", postId: "42" })
// → GET /users/1/posts/42
```

## Streaming

When `stream: true` is set in the endpoint options, the response `data` is a `ReadableStream`:

```ts
endpoint.get(":channel/stream").variables<{ channel: string }>().response<ReadableStream>()
```

## Context

The `Context` object provides access to the incoming request for header forwarding. Its cookies are forwarded where the source or the endpoint names `cookie`:

```ts
type Context = {
  req?: Request      // Incoming request
  res?: Response     // Outgoing response
  headers?: Headers  // Headers to forward
}
```

## Error Handling

`HttpClient` converts HTTP error responses (status >= 400) into `HTTPError` objects with `status`, `headers`, and parsed `data`. Network errors produce `HTTPError` with `status: 0`.

## Related

- [HTTP Client Guide](/docs/core-concepts/instance) — in-depth usage patterns
- [defineGroup](/docs/api/define-group) — where endpoints describe the call they make
- [HTTPError](/docs/api/http-error) — error class thrown by HttpClient
- [Types](/docs/api/types) — all type definitions
