Skip to content
rest-rpc
Esc
navigateopen⌘Jpreview
On this page

Responses

Declare status-keyed responses and choose the right return shape.

HTTP routes declare responses by status code.

responses: {
	200: todoSchema,
	404: z.object({
		code: z.literal("TODO_NOT_FOUND"),
	}),
}

There is no separate errors field. Declared non-2xx responses are part of the route contract.

Response Schemas

Response body declarations can be Standard Schema-compatible schemas.

responses: {
	200: z.object({
		id: z.string(),
		title: z.string(),
		completed: z.boolean(),
	}),
}

Use noBody() for statuses without a body.

import { noBody } from "@rest-rpc/core";

responses: {
	204: noBody(),
}

Custom and streaming bodies are covered in advanced docs.

Handler Returns

Handlers can always return an explicit response envelope.

return {
	status: 404,
	body: {
		code: "TODO_NOT_FOUND",
	},
};

If a route has exactly one successful response, the handler can return the success body directly.

return {
	id: "todo_1",
	title: "Write docs",
	completed: false,
};

That shortcut is only valid because the success status is unambiguous.

Explicit Success

Use an envelope when the status is part of the result.

create({ title }) {
	return {
		status: 201,
		body: createTodo({ title }),
	};
}

Declared Error Responses

Declared non-2xx responses need an explicit envelope.

get({ id }) {
	const todo = todos.get(id);

	if (!todo) {
		return {
			status: 404,
			body: {
				code: "TODO_NOT_FOUND",
			},
		};
	}

	return todo;
}

You can also use ContractResponseError to throw a declared error response when that’s more convenient.

throw new ContractResponseError(api.todos.get, {
	status: 404,
	body: {
		code: "TODO_NOT_FOUND",
	},
});

Client Responses

Every HTTP route exposes fetchResponse().

const response = await api.todos.get.fetchResponse({
	id: "todo_1",
});

It returns declared responses as values.

if (response.declared && response.status === 200) {
	response.body;
}

if (response.declared && response.status === 404) {
	response.body.code;
}

It also exposes undeclared responses.

if (!response.declared) {
	throw new Error(`Unexpected response: ${response.status}`);
}

Routes with one successful response also expose fetch().

const todo = await api.todos.get.fetch({ id: "todo_1" });

fetch() returns the successful response body directly. It throws when the response is undeclared or is not a declared success response. Use it when you don’t care about the status and just want the success body.

Was this page helpful?