Typed client
HttpClient is typed by the server type with no runtime dependency on the server.
Setup
import type is enough - the server module is never evaluated, so nothing starts and no port opens.
import type { AppRouter } from "./server.ts";
import { HttpClient, HttpClientError } from "jsr:@webtools/expressapi";
const client = new HttpClient<AppRouter>({
baseUrl: "http://localhost:5050",
headers: { authorization: "Bearer token" },
});
const user = await client.get("/users/:id", { params: { id: "42" } });
// ^ { id: string; name: string }
const created = await client.post("/users", { body: { name: "Charlie" } });URLs, methods, params, query, body, and response shapes are checked. input is optional when the route needs none of them. headers may always be passed and is merged over the client’s default headers.
await client.get("/nope"); // ❌ unknown URL
await client.post("/users/:id", { params: { id: "1" } }); // ❌ no POST on this URL
await client.get("/users/:id"); // ❌ params are required
await client.get("/users/:id", { params: { id: 1 } }); // ❌ id must be a stringErrors
Any response outside the 2xx range throws an HttpClientError carrying the parsed body:
try {
await client.get("/users/:id", { params: { id: "42" } });
} catch (error) {
if (error instanceof HttpClientError) {
console.error(error.status); // 404
console.error(error.body); // parsed response body
console.error(error.url); // full requested URL
}
}Empty and non-JSON bodies
- Empty responses (
204) yieldnull - Non-JSON responses are returned as text rather than throwing
Options
new HttpClient<AppRouter>({
baseUrl: "http://localhost:5050", // trailing slashes trimmed
headers: { authorization: "Bearer token" },
fetch: customFetch, // alternative fetch, useful in tests
});REST + client recipe: REST API.