Skip to main content

Declaring schemas

A central part of @zimic/http is an HTTP schema, which is a TypeScript type describing the structure of an API. The schema serves as a centralized source of truth about paths, parameters, and returns of the service, and can be used to automatically type your requests and responses with @zimic/fetch and @zimic/interceptor.

TIP: OpenAPI Typegen

For APIs with an OpenAPI documentation (e.g. Swagger), the zimic-http typegen CLI can automatically infer the types and generate the schema for you. This is a great way to keep your schema is up to date and save time on manual type definitions.

A @zimic/http schema is defined with the HttpSchema type.

schema.ts
import { HttpSchema } from '@zimic/http';

// Declaring the base types
interface User {
name: string;
username: string;
}

interface UserCreationBody {
name: string;
username: string;
}

interface UserListSearchParams {
name?: string;
orderBy?: ('name:asc' | 'name:desc')[];
}

// Declaring the schema
type Schema = HttpSchema<{
'/users': {
POST: {
request: {
body: UserCreationBody;
};
response: {
201: { body: User };
400: { body: { message?: string; issues?: string[] } };
409: { body: { message?: string } };
};
};

GET: {
request: {
headers: { authorization: string };
searchParams: UserListSearchParams;
};
response: {
200: { body: User[] };
401: { body: { message?: string } };
500: { body: { message?: string } };
};
};
};

'/users/:id': {
GET: {
request: {
headers: { authorization: string };
};
response: {
200: { body: User };
401: { body: { message?: string } };
403: { body: { message?: string } };
404: { body: { message?: string } };
500: { body: { message?: string } };
};
};
};
}>;

This example contains three endpoints, POST /users, GET /users, and GET /users/:id, each with their own request and response types. GET /users and GET /users/:userId require authentication via the authorization header. POST /users receive a body with some data, while GET /users accepts search parameters to filter the results. All three endpoints have successful and error responses, detailed with their respective status codes and body types.

Declaring paths

At the root of the schema, you declare the paths of the API as keys. Path parameters are automatically inferred from the path and have : as prefix, followed by the name of the parameter.

import { HttpSchema } from '@zimic/http';

type Schema = HttpSchema<{
'/users': {
// ...
};
'/users/:id': {
// ...
};
'/posts': {
// ...
};
}>;

Declaring methods

Each path can have one or more HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS). The method names are case-sensitive and must be in uppercase.

import { HttpSchema } from '@zimic/http';

type Schema = HttpSchema<{
'/users': {
GET: {
// ...
};

POST: {
// ...
};
};

// ...
}>;

Declaring requests

Each method can have a request property, which defines the shape of the requests accepted in the endpoint.

Declaring requests with search params

Search parameters (query) are declared in the searchParams property.

import { HttpSchema } from '@zimic/http';

interface UserListSearchParams {
query?: string;
}

type Schema = HttpSchema<{
'/users': {
GET: {
request: {
searchParams: UserListSearchParams;
};
};
};
}>;

Declaring requests with body

Declaring requests with JSON body

To declare a JSON body, use the type directly.

import { HttpSchema } from '@zimic/http';

interface UserCreationBody {
username: string;
}

type Schema = HttpSchema<{
'/users': {
POST: {
request: {
body: UserCreationBody;
};
};
};
}>;

Declaring requests with FormData body

Use HttpFormData to declare a request with FormData body.

import { HttpSchema, HttpFormData } from '@zimic/http';

interface FileUploadData {
files: File[];
description?: string;
}

type Schema = HttpSchema<{
'/files': {
POST: {
request: {
body: HttpFormData<FileUploadData>;
};
};
};
}>;

Declaring requests with binary body

Blob, ArrayBuffer, and ReadableStream are frequently used types for binary data. Use one of these types to declare a binary body.

import { HttpSchema } from '@zimic/http';

type Schema = HttpSchema<{
'/upload': {
POST: {
request: {
body: Blob;
};
};
};
}>;

Declaring requests with plain-text body

Plain-text bodies can be declared as a string.

import { HttpSchema } from '@zimic/http';

type Schema = HttpSchema<{
'/content': {
POST: {
request: {
body: string;
};
};
};
}>;

Declaring requests with URL-encoded body

Bodies with URL-encoded data can be declared with HttpSearchParams.

import { HttpSchema, HttpSearchParams } from '@zimic/http';

interface UserCreationSearchParams {
query?: string;
}

type Schema = HttpSchema<{
'/users': {
POST: {
request: {
body: HttpSearchParams<UserCreationSearchParams>;
};
};
};
}>;

Declaring responses

Each method can also have a response, which defines the schema of the results returned by the server. The status codes are used as keys.

Declaring responses with body

Declaring responses with JSON body

To declare a response with a JSON body, use the type directly.

interface User {
name: string;
username: string;
}

type Schema = HttpSchema<{
'/users/:id': {
GET: {
response: {
200: {
body: User;
};
404: {
body: { message?: string };
};
};
};
};
}>;

Declaring responses with FormData body

To declare a response with FormData, use HttpFormData.

import { HttpSchema, HttpFormData } from '@zimic/http';

interface FileUploadData {
files: File[];
description?: string;
}

type Schema = HttpSchema<{
'/files': {
POST: {
response: {
200: {
body: HttpFormData<FileUploadData>;
};
};
};
};
}>;

Declaring responses with binary body

To define a response with binary data, use Blob, ArrayBuffer, or ReadableStream, similar to how you would declare a request with binary data.

import { HttpSchema } from '@zimic/http';

type Schema = HttpSchema<{
'/upload': {
POST: {
response: {
200: { body: Blob };
};
};
};
}>;

Declaring responses with plain-text body

Plain-text bodies can be declared as a string.

import { HttpSchema } from '@zimic/http';

type Schema = HttpSchema<{
'/content': {
POST: {
response: {
200: {
body: string;
};
};
};
};
}>;

Declaring responses with URL-encoded body

Use HttpSearchParams to declare a response with URL-encoded data.

import { HttpSchema, HttpSearchParams } from '@zimic/http';

interface UserCreationSearchParams {
query?: string;
}

type Schema = HttpSchema<{
'/users': {
POST: {
response: {
200: {
body: HttpSearchParams<UserCreationSearchParams>;
};
};
};
};
}>;

Declaring responses with status code ranges

Sometimes, endpoints can return a range of status codes, such as 5XX, meaning any status greater than or equal to 500. In these cases, you can use the HttpStatusCode type, which contains all standard HTTP status codes:

TypeRange
HttpStatusCode.Information1XX
HttpStatusCode.Success2XX
HttpStatusCode.Redirection3XX
HttpStatusCode.ClientError4XX
HttpStatusCode.ServerError5XX
import { HttpSchema, HttpStatusCode } from '@zimic/http';

interface User {
name: string;
username: string;
}

type Schema = HttpSchema<{
'/users': {
GET: {
response: {
200: User[];
} & {
// 4XX
[StatusCode in HttpStatusCode.ClientError]: {
body: { message?: string; code: 'client_error' };
};
} & {
// 5XX
[StatusCode in HttpStatusCode.ServerError]: {
body: { message?: string; code: 'server_error' };
};
};
};
};
}>;

In this example, GET /users may return a successful response with a 200 status code, or any status code in the 4XX (HttpStatusCode.ClientError) or 5XX (HttpStatusCode.ServerError) ranges.