Using bodies
Bodies are used to send data in a request and receive data in a response. Request bodies are commonly used to include a payload of data that the server will process, whereas response bodies may contain the result returned by the server. A body can contain various types of data, such as JSON, XML, and binary data such as images and files.
Using request bodies​
Request bodies are declared in your schema using the request.body
property.
JSON request body​
JSON bodies are one of the most common ways to send data in requests. To use a JSON body in a request, declare its type in your schema.
import { HttpSchema } from '@zimic/http';
interface User {
id: string;
username: string;
}
type Schema = HttpSchema<{
'/users': {
POST: {
request: {
body: { username: string };
};
response: {
201: { body: User };
};
};
};
}>;
Then, use the body
option to send the data in your fetch request. Note that you should set the content-type
header
to application/json
to indicate that the body is in JSON format, otherwise it may be interpreted as a plain text.
Also, serialize the body with
JSON.stringify()
before
sending it.
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
const response = await fetch('/users', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ username: 'me' }),
});
content-type
header inference@zimic/http
automatically infers the type of the content-type
header as application/json
if the request body is a
JSON type. You can override this behavior by explicitly setting a different type in your schema.
type Schema = HttpSchema<{
'/users': {
POST: {
request: {
headers: {
'content-type': 'application/json; charset=utf-8';
};
body: { username: string };
};
response: {
201: { body: User };
};
};
};
}>;
The inference is limited to typing, so you still need to set the header when making requests with JSON bodies. This follows behavior of the Fetch API.
FormData
request body​
FormData
is a special type of body to construct a set of
key-value pairs with variable types of data. A common use case is to upload files to a server.
To send a FormData
body, declare its type in your schema. Use the
HttpFormData
to indicate that the body is a FormData
type.
import { HttpFormData, HttpSchema } from '@zimic/http';
interface AvatarFormDataSchema {
image: File;
}
type Schema = HttpSchema<{
'/users/:userId/avatar': {
PUT: {
request: {
headers?: { 'content-type'?: 'multipart/form-data' };
body: HttpFormData<AvatarFormDataSchema>;
};
response: {
200: { body: { url: string } };
};
};
};
}>;
After that, create an HttpFormData
instance and add the data using
set
or
append
.
import { HttpFormData } from '@zimic/http';
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
// Getting an uploaded file from an input element
const imageInput = document.querySelector<HTMLInputElement>('input[type="file"]');
const imageFile = imageInput!.files![0];
const formData = new HttpFormData<AvatarFormDataSchema>();
formData.append('image', imageFile);
const response = await fetch(`/users/${userId}/avatar`, {
method: 'PUT',
headers: { 'content-type': 'multipart/form-data' },
body: formData,
});
Depending on your runtime, the content-type
header may be set automatically when using a FormData
body. In that
case, you don't need to set it manually.
const response = await fetch(`/users/${userId}/avatar`, {
method: 'PUT',
body: formData,
});
Binary request body​
Binary bodies are used to send raw binary data in requests. To send a binary body, declare its type in your
schema. Blob
,
ArrayBuffer
, and
ReadableStream
are common types for binary data.
import { HttpSchema } from '@zimic/http';
interface Video {
id: string;
url: string;
}
type Schema = HttpSchema<{
'/upload': {
POST: {
request: {
headers?: { 'content-type'?: string };
body: Blob;
};
};
response: {
201: { body: Video };
};
};
}>;
Then, use the body
option to send the data in your fetch request. Make sure to set the content-type
header, such as
video/mp4
, image/png
, or application/octet-stream
for generic binary data. Learn more about
MIME types to use in your
requests.
import fs from 'fs';
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
// Getting a file from the file system
const videoBuffer = await fs.promises.readFile('video.mp4');
const videoFile = new File([videoBuffer], 'video.mp4');
const response = await fetch('/upload', {
method: 'POST',
headers: { 'content-type': 'video/mp4' },
body: videoFile,
});
Request streaming​
When working with large files or chunked data, you can use
ReadableStream
and duplex: 'half'
to stream the request
body. This allows you to send data in smaller parts, which can be more efficient and reduce memory usage.
import { HttpSchema } from '@zimic/http';
interface Video {
id: string;
url: string;
}
type Schema = HttpSchema<{
'/upload': {
POST: {
request: {
headers?: { 'content-type'?: string };
body: ReadableStream;
};
};
response: {
201: { body: Video };
};
};
}>;
import fs from 'fs';
import { Readable } from 'stream';
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
// Streaming a file from the file system
const fileStream = fs.createReadStream('video.mp4');
const requestStream = Readable.toWeb(fileStream);
const response = await fetch('/upload', {
method: 'POST',
headers: { 'content-type': 'video/mp4' },
body: requestStream,
duplex: 'half',
});
Plain-text request body​
Plain-text bodies can be declared as a string.
import { HttpSchema } from '@zimic/http';
type Schema = HttpSchema<{
'/content': {
POST: {
request: {
body: string;
};
response: {
201: {};
};
};
};
}>;
After that, send a plain-text body as a string in your fetch request.
const response = await fetch('/content', {
method: 'POST',
body: 'text',
});
URL-encoded request body​
Bodies with URL-encoded data can be declared with HttpSearchParams
.
import { HttpSchema, HttpSearchParams } from '@zimic/http';
interface UserCreationSearchParams {
username: string;
}
type Schema = HttpSchema<{
'/users': {
POST: {
request: {
headers?: { 'content-type'?: 'application/x-www-form-urlencoded' };
body: HttpSearchParams<UserCreationSearchParams>;
};
};
};
}>;
Then, use the body
option to send the data in your fetch request. The HttpSearchParams
type will be automatically
serialized to a URL-encoded string, and the content-type
header will be set to application/x-www-form-urlencoded
.
import { HttpSearchParams } from '@zimic/http';
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
const searchParams = new HttpSearchParams<UserCreationSearchParams>({
username: 'me',
});
const response = await fetch('/users', {
method: 'POST',
body: searchParams,
});
Using response bodies​
Response bodies are declared in your schema using the response.<status>.body
property.
JSON response body​
To receive a JSON response body, declare its type in your schema.
import { HttpSchema } from '@zimic/http';
interface User {
id: string;
username: string;
}
type Schema = HttpSchema<{
'/users/:userId': {
GET: {
response: {
200: {
body: User;
};
};
};
};
}>;
Then, use response.json()
to parse the response body as
JSON. The result is automatically typed according to your schema.
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
const response = await fetch('/users/1', {
method: 'GET',
});
const user = await response.json();
FormData
response body​
To receive a FormData
response body, declare its type in your schema. Use the
HttpFormData
to indicate that the body is a FormData
type.
import { HttpFormData, HttpSchema } from '@zimic/http';
interface AvatarFormDataSchema {
image: File;
}
type Schema = HttpSchema<{
'/users/:userId/avatar': {
GET: {
response: {
200: {
headers?: { 'content-type'?: 'multipart/form-data' };
body: HttpFormData<AvatarFormDataSchema>;
};
};
};
};
}>;
Then, use response.formData()
to parse the response
body as FormData
. The result is automatically typed according to your schema.
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
const response = await fetch(`/users/${user.id}/avatar`, {
method: 'GET',
});
const formData = await response.formData();
Binary response body​
To receive a binary response body, declare its type in your schema.
import { HttpSchema } from '@zimic/http';
interface Video {
id: string;
url: string;
}
type Schema = HttpSchema<{
'/videos/:videoId': {
GET: {
response: {
200: {
headers?: { 'content-type'?: string };
body: Blob;
};
};
};
};
}>;
Then, use response.blob()
or
response.arrayBuffer()
to parse the response body
as binary data.
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
const response = await fetch(`/videos/${video.id}`, {
method: 'GET',
});
const videoBlob = await response.blob();
Response streaming​
Similarly to request streaming, you can stream the response body using
ReadableStream
. This allows you to receive large files or
chunked data in smaller parts, which can reduce memory usage and improve performance.
import { HttpSchema } from '@zimic/http';
type Schema = HttpSchema<{
'/videos/:videoId': {
GET: {
response: {
200: {
headers?: { 'content-type'?: string };
body: ReadableStream;
};
};
};
};
}>;
import fs from 'fs';
import stream, { Readable } from 'stream';
import { ReadableStream as NodeReadableStream } from 'stream/web';
const response = await fetch(`/videos/${video.id}`, {
method: 'GET',
});
const responseStream = Readable.fromWeb(response.body as NodeReadableStream);
const fileStream = fs.createWriteStream('video.mp4');
// Stream the response body to a local file
await stream.promises.pipeline(responseStream, fileStream);
Plain-text response body​
To receive a plain-text response body, declare its type in your schema.
import { HttpSchema } from '@zimic/http';
type Schema = HttpSchema<{
'/content': {
GET: {
response: {
200: {
body: string;
};
};
};
};
}>;
Then, use response.text()
to parse the response body as
plain text.
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
const response = await fetch('/content', {
method: 'GET',
});
const content = await response.text();
URL-encoded response body​
To receive a URL-encoded response body, declare its type in your schema. Use the
HttpSearchParams
to indicate that the body is a URL-encoded type.
import { HttpSchema, HttpSearchParams } from '@zimic/http';
type Schema = HttpSchema<{
'/users': {
GET: {
response: {
200: {
headers?: { 'content-type'?: 'application/x-www-form-urlencoded' };
body: HttpSearchParams<{ username: string }>;
};
};
};
};
}>;
Then, use response.formData()
to parse the response
body as URL-encoded data. The result is automatically typed according to your schema.
import { createFetch } from '@zimic/fetch';
const fetch = createFetch<Schema>({
baseURL: 'http://localhost:3000',
});
const response = await fetch('/users', {
method: 'GET',
});
const searchParams = await response.formData();