Skip to main content

Basic Usage

This page explains how to create a client and write common HTTP requests.

Create a client

Create a client with the API server's base URL.

import { dench } from 'dench-fetch';

const api = dench('https://api.example.com');

The api client can be reused for every request that shares the same base URL.

GET request

Pass an API path and expected JSON response type to get<T>(), then execute it with toJson().

type Post = {
id: number;
title: string;
body: string;
};

const post = await api
.get<Post>('/posts/1')
.toJson();

Use toResponse() when you need the native Response instead of parsed JSON.

const response = await api
.get('/health')
.toResponse();

console.log(response.status);

POST request

Pass POST and PUT request bodies to a body helper such as sendJson().

type CreatedPost = {
id: number;
title: string;
};

const created = await api
.post<CreatedPost>('/posts')
.sendJson({
title: 'New post',
body: 'Post content',
})
.toJson();

sendJson(data) serializes the value with JSON.stringify() and sets the Content-Type: application/json header.

PUT request

const updated = await api
.put<Post>('/posts/1')
.sendJson({
title: 'Updated title',
body: 'Updated content',
})
.toJson();

DELETE request

await api
.delete('/posts/1')
.toResponse();

DELETE requests use the read-style builder and do not provide body helpers.

Body formats

POST and PUT builders provide helpers for common body formats.

FormData

const form = new FormData();
form.append('name', 'dench');
form.append('file', file);

await api
.post('/uploads')
.sendForm(form)
.toResponse();

sendForm() requires a FormData instance. It does not set the Content-Type header directly, allowing the runtime to include the multipart boundary.

Blob

const blob = new Blob(['binary data']);

await api
.post('/files')
.sendBlob(blob)
.toResponse();

URL-encoded data

await api
.post('/sessions')
.sendUrlEncoded({
username: 'dench',
password: 'secret',
})
.toResponse();

sendUrlEncoded() converts the value to URLSearchParams and sends it as application/x-www-form-urlencoded.

Raw body

await api
.post('/raw')
.sendRaw('raw body')
.toResponse();

sendRaw() places a BodyInit value into the request body without transforming it and sets Content-Type: application/octet-stream.

Authentication and request options

auth() adds a Bearer authorization header by default.

const profile = await api
.get<Profile>('/profile')
.auth('access-token')
.toJson();

Pass a DenchAuthType to use another authentication prefix.

import { DenchAuthType } from 'dench-fetch';

const response = await api
.get('/secure')
.auth('encoded-credentials', DenchAuthType.BASIC)
.toResponse();

Use the provided enums and builder methods to configure cookies and Fetch API options.

import {
HTTPCache,
HTTPCredentials,
HTTPMode,
HTTPRedirect,
HTTPReferrerPolicy,
} from 'dench-fetch';

const result = await api
.get<Result>('/result')
.credentials(HTTPCredentials.INCLUDE)
.mode(HTTPMode.CORS)
.cache(HTTPCache.NO_CACHE)
.redirect(HTTPRedirect.FOLLOW)
.referrerPolicy(HTTPReferrerPolicy.NO_REFERRER)
.toJson();

Timeout and cancellation

timeout(ms) aborts the request after the specified number of milliseconds.

const result = await api
.get<Result>('/slow')
.timeout(5000)
.toJson();

Pass an AbortController to cancel the request manually.

const controller = new AbortController();

const request = api
.get<Result>('/slow')
.abort(controller)
.toJson();

controller.abort();

await request;

When abort(controller) and timeout(ms) are used together, the provided controller is aborted when the timeout expires.

Error handling

HTTP failure responses and network errors are thrown as exceptions.

try {
const post = await api.get<Post>('/posts/missing').toJson();
} catch (error) {
console.error('Failed to process request:', error);
}

Register an error() callback for shared logging.

const post = await api
.get<Post>('/posts/1')
.error((error) => {
console.error('API request failed:', error);
})
.toJson();

The error() callback does not consume the error. The same error is rethrown after the callback runs.

Reusing common settings

Create a builder with common settings, then branch requests with copy() and api().

const authenticated = api
.get()
.auth('access-token')
.timeout(3000);

const users = authenticated.copy().api<User[]>('/users');
const posts = authenticated.copy().api<Post[]>('/posts');

const [userList, postList] = await Promise.all([
users.toJson(),
posts.toJson(),
]);

This pattern avoids repeating authentication and timeout configuration while keeping each request path and response type independent.