Documentation Index
Fetch the complete documentation index at: https://robintail-express-zod-api-69.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Overview
ResultHandler is responsible for transmitting consistent responses containing the endpoint output or errors. While Express Zod API provides a default handler, you can create custom ones to match your API’s requirements.
Default Result Handler
The defaultResultHandler sets the HTTP status code and ensures the following response type:
type DefaultResponse<OUT> =
| { status: "success"; data: OUT } // Positive response
| { status: "error"; error: { message: string } }; // Negative response
Creating a Custom Result Handler
Here’s a template for creating your own result handler:
import { z } from "zod";
import {
ResultHandler,
ensureHttpError,
getMessageFromError,
} from "express-zod-api";
const customResultHandler = new ResultHandler({
positive: (data) => ({
schema: z.object({ data }),
mimeType: "application/json", // optional, can be array
}),
negative: z.object({ error: z.string() }),
handler: ({ error, input, output, request, response, logger }) => {
if (error) {
const { statusCode } = ensureHttpError(error);
const message = getMessageFromError(error);
return void response.status(statusCode).json({ error: message });
}
response.status(200).json({ data: output });
},
});
Using Custom Result Handlers
After creating your custom ResultHandler, use it when creating an EndpointsFactory:
import { EndpointsFactory } from "express-zod-api";
const endpointsFactory = new EndpointsFactory(customResultHandler);
const myEndpoint = endpointsFactory.build({
input: z.object({ name: z.string() }),
output: z.object({ greeting: z.string() }),
handler: async ({ input }) => ({
greeting: `Hello, ${input.name}!`,
}),
});
Status Code Variations
For REST APIs that require different response schemas for different status codes:
import { ResultHandler } from "express-zod-api";
import { z } from "zod";
const statusDependingHandler = new ResultHandler({
positive: (data) => ({
statusCode: [201, 202], // Created or will be created
schema: z.object({ status: z.literal("created"), data }),
}),
negative: [
{
statusCode: 409, // Conflict: entity already exists
schema: z.object({ status: z.literal("exists"), id: z.number() }),
},
{
statusCode: [400, 500], // Validation or internal error
schema: z.object({ status: z.literal("error"), reason: z.string() }),
},
],
handler: ({ error, response, output }) => {
if (error) {
const httpError = ensureHttpError(error);
const doesExist =
httpError.statusCode === 409 &&
"id" in httpError &&
typeof httpError.id === "number";
return void response
.status(httpError.statusCode)
.json(
doesExist
? { status: "exists", id: httpError.id }
: { status: "error", reason: httpError.message },
);
}
response.status(201).json({ status: "created", data: output });
},
});
Empty Response (204 No Content)
For endpoints that don’t return content:
const noContentHandler = new ResultHandler({
positive: { statusCode: 204, mimeType: null, schema: z.never() },
negative: { statusCode: 404, mimeType: null, schema: z.never() },
handler: ({ error, response }) => {
response.status(error ? ensureHttpError(error).statusCode : 204).end();
},
});
const deleteFactory = new EndpointsFactory(noContentHandler);
const deleteUserEndpoint = deleteFactory.build({
method: "delete",
input: z.object({ id: z.string() }),
output: z.object({}), // Empty output
handler: async ({ input }) => {
// Delete user logic
return {};
},
});
Add custom headers to responses:
const customHeaderHandler = new ResultHandler({
positive: (data) => ({
schema: z.object({ data }),
mimeType: "application/json",
}),
negative: z.object({ error: z.string() }),
handler: ({ error, output, response }) => {
// Add custom headers
response.set("X-API-Version", "2.0");
response.set("X-Request-Id", crypto.randomUUID());
if (error) {
const { statusCode } = ensureHttpError(error);
return void response.status(statusCode).json({ error: error.message });
}
response.status(200).json({ data: output });
},
});
Resource Cleanup
Clean up resources at the end of request processing:
import { ResultHandler } from "express-zod-api";
const cleanupHandler = new ResultHandler({
positive: (data) => ({
schema: z.object({ data }),
mimeType: "application/json",
}),
negative: z.object({ error: z.string() }),
handler: ({ ctx, error, output, response }) => {
// Cleanup logic
if ("db" in ctx && ctx.db) {
ctx.db.connection.close(); // Example cleanup
}
if (error) {
const { statusCode } = ensureHttpError(error);
return void response.status(statusCode).json({ error: error.message });
}
response.status(200).json({ data: output });
},
});
Multiple MIME Types
Support multiple MIME types in responses:
const multiMimeHandler = new ResultHandler({
positive: (data) => ({
schema: z.object({ data }),
mimeType: ["application/json", "application/xml"], // Array of MIME types
}),
negative: z.object({ error: z.string() }),
handler: ({ error, output, response, request }) => {
const acceptsXml = request.accepts("xml");
if (error) {
return void response.status(ensureHttpError(error).statusCode).json({
error: error.message,
});
}
if (acceptsXml) {
// Return XML
response.type("xml").send(convertToXml(output));
} else {
// Return JSON
response.json({ data: output });
}
},
});
Real-World Example
Here’s a complete example from the Express Zod API examples:
import { EndpointsFactory, ResultHandler } from "express-zod-api";
import { z } from "zod";
const statusDependingFactory = new EndpointsFactory(
new ResultHandler({
positive: (data) => ({
statusCode: [201, 202],
schema: z.object({ status: z.literal("created"), data }),
}),
negative: [
{
statusCode: 409,
schema: z.object({ status: z.literal("exists"), id: z.number() }),
},
{
statusCode: [400, 500],
schema: z.object({ status: z.literal("error"), reason: z.string() }),
},
],
handler: ({ error, response, output }) => {
if (error) {
const httpError = ensureHttpError(error);
const doesExist =
httpError.statusCode === 409 &&
"id" in httpError &&
typeof httpError.id === "number";
return void response
.status(httpError.statusCode)
.json(
doesExist
? { status: "exists", id: httpError.id }
: { status: "error", reason: httpError.message },
);
}
response.status(201).json({ status: "created", data: output });
},
}),
);