Skip to content
TypeWire

@tahanabavi/typefetch

documents v1.10.0

The typed HTTP client every other package is built around.

Middleware System

TypeFetch supports middleware for logging, authentication, caching, retries, encryption, and custom request behavior.

ts
client.use(async (ctx, next) => {
console.log("Request:", ctx.url);

const response = await next();

console.log("Response:", response.status);

return response;
});

Middlewares run in registration order before the request, then unwind in reverse order after the response.

ts
client.use(firstMiddleware);
client.use(secondMiddleware);

Execution flow:

txt
firstMiddleware before
secondMiddleware before
fetch
secondMiddleware after
firstMiddleware after

Built-in Middlewares

Depending on how you export your middlewares, they can be registered directly or as factories.

Direct middleware example:

ts
client.use(loggingMiddleware, {
debug: true,
logRequest: true,
logResponse: true,
});

Factory middleware example:

ts
client.use(cacheMiddleware({ ttl: 60_000 }));
client.use(retryMiddleware({ maxRetries: 3, delay: 300 }));

Custom Middleware

A middleware receives:

ts
type Middleware = (
ctx: MiddlewareContext,
next: () => Promise<Response>,
options?: unknown,
) => Promise<Response>;

Example:

ts
client.use(async (ctx, next) => {
const startedAt = Date.now();

const response = await next();

console.log(`${ctx.init.method} ${ctx.url} took ${Date.now() - startedAt}ms`);

return response;
});

With options:

ts
const timingMiddleware = async (ctx, next, options) => {
const response = await next();

if (options?.debug) {
console.log("Timing middleware enabled");
}

return response;
};

client.use(timingMiddleware, {
debug: true,
});

Endpoint-Level Headers

Headers can be defined on the endpoint.

ts
const contracts = {
user: {
createUser: {
method: "POST",
path: "/users",
request: z.object({
body: z.object({
name: z.string(),
}),
}),
response: z.object({
id: z.string(),
name: z.string(),
}),
headers: {
"X-App": "typefetch",
},
},
},
} as const;

Headers can also be generated from input:

ts
headers: (input) => ({
"X-Tenant": input.headers?.["X-Tenant"] ?? "default",
});

Per-request headers can be passed through the structured request input:

ts
await api.user.createUser({
headers: {
"X-Request-ID": "req-123",
},
body: {
name: "Taha",
},
});