No sections on this page.
The .describe() method allows you to add comprehensive documentation metadata to your procedures, which can be used to generate OpenAPI specifications and enhance your API documentation.
Add documentation to any procedure using the .describe() method:
Example Procedure with .describe()
import { z } from "zod";
import { jsandy } from "@jsandy/rpc";
const { router, procedure } = jsandy.init();
const userRouter = router({
getUser: procedure
.input(z.object({ id: z.string() }))
.describe({
description: "Retrieves a user by their unique identifier",
summary: "Get user by ID",
schema: z.object({
id: z.string(),
name: z.string(),
email: z.string(),
createdAt: z.date(),
}),
tags: ["users"],
operationId: "getUserById"
})
.get(({ input, c }) => {
// Implementation
return c.json({
id: input.id,
name: "John Doe",
email: "john@example.com",
createdAt: new Date()
});
})
});import { z } from "zod";
import { jsandy } from "@jsandy/rpc";
const { router, procedure } = jsandy.init();
const userRouter = router({
getUser: procedure
.input(z.object({ id: z.string() }))
.describe({
description: "Retrieves a user by their unique identifier",
summary: "Get user by ID",
schema: z.object({
id: z.string(),
name: z.string(),
email: z.string(),
createdAt: z.date(),
}),
tags: ["users"],
operationId: "getUserById"
})
.get(({ input, c }) => {
// Implementation
return c.json({
id: input.id,
name: "John Doe",
email: "john@example.com",
createdAt: new Date()
});
})
});The .describe() method accepts a comprehensive configuration object:
Procedure Description Type
interface ProcedureDescription {
/** Human-readable description of what this endpoint does */
description: string;
/** Optional summary for the endpoint (shorter than description) */
summary?: string;
/** Zod schema defining the expected output/response structure */
schema?: ZodAny;
/** Optional tags for grouping endpoints in documentation */
tags?: string[];
/** Optional operation ID for OpenAPI specification */
operationId?: string;
/** Whether this endpoint is deprecated */
deprecated?: boolean;
/** Additional OpenAPI metadata */
openapi?: {
/** Security requirements for this endpoint */
security?: Array<Record<string, string[]>>;
/** Additional response definitions */
responses?: Record<string, any>;
/** Request body examples */
examples?: Record<string, any>;
};
}interface ProcedureDescription {
/** Human-readable description of what this endpoint does */
description: string;
/** Optional summary for the endpoint (shorter than description) */
summary?: string;
/** Zod schema defining the expected output/response structure */
schema?: ZodAny;
/** Optional tags for grouping endpoints in documentation */
tags?: string[];
/** Optional operation ID for OpenAPI specification */
operationId?: string;
/** Whether this endpoint is deprecated */
deprecated?: boolean;
/** Additional OpenAPI metadata */
openapi?: {
/** Security requirements for this endpoint */
security?: Array<Record<string, string[]>>;
/** Additional response definitions */
responses?: Record<string, any>;
/** Request body examples */
examples?: Record<string, any>;
};
}For GET endpoints, input schemas become query parameters in the OpenAPI spec:
GET Endpoint with .describe()
const searchPosts = procedure
.input(z.object({
query: z.string().min(1),
limit: z.number().min(1).max(100).default(10),
category: z.enum(["tech", "business", "lifestyle"]).optional()
}))
.describe({
description: "Search blog posts with pagination and filtering",
summary: "Search posts",
schema: z.object({
posts: z.array(z.object({
id: z.string(),
title: z.string(),
excerpt: z.string()
})),
pagination: z.object({
total: z.number(),
hasNext: z.boolean()
})
}),
tags: ["posts", "search"],
operationId: "searchPosts"
})
.get(({ input, c }) => {
// Implementation
});const searchPosts = procedure
.input(z.object({
query: z.string().min(1),
limit: z.number().min(1).max(100).default(10),
category: z.enum(["tech", "business", "lifestyle"]).optional()
}))
.describe({
description: "Search blog posts with pagination and filtering",
summary: "Search posts",
schema: z.object({
posts: z.array(z.object({
id: z.string(),
title: z.string(),
excerpt: z.string()
})),
pagination: z.object({
total: z.number(),
hasNext: z.boolean()
})
}),
tags: ["posts", "search"],
operationId: "searchPosts"
})
.get(({ input, c }) => {
// Implementation
});For POST endpoints, input schemas become request body specifications:
POST Endpoint with .describe()
const createPost = procedure
.input(z.object({
title: z.string().min(1).max(200),
content: z.string().min(10),
tags: z.array(z.string()).optional(),
publishAt: z.date().optional()
}))
.describe({
description: "Creates a new blog post with the provided content",
summary: "Create blog post",
schema: z.object({
id: z.string(),
slug: z.string(),
status: z.enum(["draft", "published"]),
createdAt: z.date()
}),
tags: ["posts", "content"],
operationId: "createPost"
})
.post(({ input, c }) => {
// Implementation
});const createPost = procedure
.input(z.object({
title: z.string().min(1).max(200),
content: z.string().min(10),
tags: z.array(z.string()).optional(),
publishAt: z.date().optional()
}))
.describe({
description: "Creates a new blog post with the provided content",
summary: "Create blog post",
schema: z.object({
id: z.string(),
slug: z.string(),
status: z.enum(["draft", "published"]),
createdAt: z.date()
}),
tags: ["posts", "content"],
operationId: "createPost"
})
.post(({ input, c }) => {
// Implementation
});Add authentication requirements to specific endpoints:
Security Requirements with .describe()
const getAdminStats = procedure
.describe({
description: "Retrieves system statistics (admin only)",
summary: "Get admin statistics",
schema: z.object({
totalUsers: z.number(),
systemHealth: z.string()
}),
tags: ["admin"],
openapi: {
security: [{ bearerAuth: [] }]
}
})
.get(({ c }) => {
// Implementation
});const getAdminStats = procedure
.describe({
description: "Retrieves system statistics (admin only)",
summary: "Get admin statistics",
schema: z.object({
totalUsers: z.number(),
systemHealth: z.string()
}),
tags: ["admin"],
openapi: {
security: [{ bearerAuth: [] }]
}
})
.get(({ c }) => {
// Implementation
});Define additional response codes and schemas:
Custom Error Responses with .describe()
const deleteUser = procedure
.input(z.object({ id: z.string() }))
.describe({
description: "Permanently deletes a user account",
summary: "Delete user",
tags: ["users"],
openapi: {
responses: {
404: {
description: "User not found",
content: {
"application/json": {
schema: {
type: "object",
properties: {
error: { type: "string", example: "USER_NOT_FOUND" },
message: { type: "string" }
}
}
}
}
},
409: {
description: "Cannot delete user with active subscriptions",
content: {
"application/json": {
schema: {
type: "object",
properties: {
error: { type: "string", example: "USER_HAS_ACTIVE_SUBSCRIPTIONS" },
subscriptions: { type: "array", items: { type: "string" } }
}
}
}
}
}
}
}
})
.post(({ input, c }) => {
// Implementation
});const deleteUser = procedure
.input(z.object({ id: z.string() }))
.describe({
description: "Permanently deletes a user account",
summary: "Delete user",
tags: ["users"],
openapi: {
responses: {
404: {
description: "User not found",
content: {
"application/json": {
schema: {
type: "object",
properties: {
error: { type: "string", example: "USER_NOT_FOUND" },
message: { type: "string" }
}
}
}
}
},
409: {
description: "Cannot delete user with active subscriptions",
content: {
"application/json": {
schema: {
type: "object",
properties: {
error: { type: "string", example: "USER_HAS_ACTIVE_SUBSCRIPTIONS" },
subscriptions: { type: "array", items: { type: "string" } }
}
}
}
}
}
}
}
})
.post(({ input, c }) => {
// Implementation
});Use the built-in generator to create OpenAPI specifications:
Automatic OpenAPI Generation
import { generateOpenAPISpec, addOpenAPIRoutes } from "@jsandy/rpc";
import { Hono } from "hono";
const app = new Hono();
// Add automatic documentation routes
await addOpenAPIRoutes(app, userRouter, {
title: "User Management API",
version: "1.0.0",
description: "Comprehensive user management with authentication",
servers: [
{ url: "https://api.example.com", description: "Production" },
{ url: "http://localhost:8080", description: "Development" }
],
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT"
}
}
}, {
specPath: "/openapi.json", // OpenAPI spec endpoint
docsPath: "/docs" // Swagger UI endpoint
});import { generateOpenAPISpec, addOpenAPIRoutes } from "@jsandy/rpc";
import { Hono } from "hono";
const app = new Hono();
// Add automatic documentation routes
await addOpenAPIRoutes(app, userRouter, {
title: "User Management API",
version: "1.0.0",
description: "Comprehensive user management with authentication",
servers: [
{ url: "https://api.example.com", description: "Production" },
{ url: "http://localhost:8080", description: "Development" }
],
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT"
}
}
}, {
specPath: "/openapi.json", // OpenAPI spec endpoint
docsPath: "/docs" // Swagger UI endpoint
});For more control, generate the spec manually:
Manual OpenAPI Generation
const openAPISpec = await generateOpenAPISpec(userRouter, {
title: "My API",
version: "2.0.0",
description: "API documentation",
basePath: "/api/v2"
});
// Serve the specification
app.get("/api-spec.json", (c) => c.json(openAPISpec));
// Custom Swagger UI with additional configuration
app.get("/api-docs", (c) => {
const html = createSwaggerUI("/api-spec.json", "My API Docs");
return c.html(html);
});const openAPISpec = await generateOpenAPISpec(userRouter, {
title: "My API",
version: "2.0.0",
description: "API documentation",
basePath: "/api/v2"
});
// Serve the specification
app.get("/api-spec.json", (c) => c.json(openAPISpec));
// Custom Swagger UI with additional configuration
app.get("/api-docs", (c) => {
const html = createSwaggerUI("/api-spec.json", "My API Docs");
return c.html(html);
});The .describe() method works seamlessly with method chaining:
Method Chaining with .describe()
const complexProcedure = procedure
.input(userInputSchema) // Add input validation
.describe({ // Add documentation
description: "Complex operation",
tags: ["complex"]
})
.use(authMiddleware) // Add authentication
.use(rateLimitMiddleware) // Add rate limiting
.get(({ input, ctx, c }) => { // Define handler
// Implementation with full type safety
});const complexProcedure = procedure
.input(userInputSchema) // Add input validation
.describe({ // Add documentation
description: "Complex operation",
tags: ["complex"]
})
.use(authMiddleware) // Add authentication
.use(rateLimitMiddleware) // Add rate limiting
.get(({ input, ctx, c }) => { // Define handler
// Implementation with full type safety
});Retrieve documentation metadata programmatically:
Accessing Documentation Metadata
// Get all operations with their descriptions
const operations = userRouter.getAllOperations();
// Get description for a specific operation
const getUserDescription = userRouter.getOperationDescription("getUser");
console.log(getUserDescription?.description);
console.log(getUserDescription?.tags);// Get all operations with their descriptions
const operations = userRouter.getAllOperations();
// Get description for a specific operation
const getUserDescription = userRouter.getOperationDescription("getUser");
console.log(getUserDescription?.description);
console.log(getUserDescription?.tags);The .describe() method is designed to work alongside @hono/zod-openapi for maximum compatibility:
Note: Most examples use
zod/v4for standard schemas. The@hono/zod-openapiimport provides additional OpenAPI-specific enhancements and should be used when you need advanced OpenAPI metadata on your schemas.
Integration with @hono/zod-openapi
// Use enhanced Zod schemas with OpenAPI metadata
import { z } from '@hono/zod-openapi';
const UserSchema = z.object({
id: z.string().openapi({ example: '123' }),
name: z.string().openapi({ example: 'John Doe' }),
email: z.string().email().openapi({ example: 'john@example.com' })
}).openapi('User');
const getUserProcedure = procedure
.input(z.object({
id: z.string().openapi({
param: { name: 'id', in: 'path' },
example: '123'
})
}))
.describe({
description: "Get user with enhanced OpenAPI metadata",
schema: UserSchema,
tags: ["users"]
})
.get(({ input, c }) => {
// Implementation
});// Use enhanced Zod schemas with OpenAPI metadata
import { z } from '@hono/zod-openapi';
const UserSchema = z.object({
id: z.string().openapi({ example: '123' }),
name: z.string().openapi({ example: 'John Doe' }),
email: z.string().email().openapi({ example: 'john@example.com' })
}).openapi('User');
const getUserProcedure = procedure
.input(z.object({
id: z.string().openapi({
param: { name: 'id', in: 'path' },
example: '123'
})
}))
.describe({
description: "Get user with enhanced OpenAPI metadata",
schema: UserSchema,
tags: ["users"]
})
.get(({ input, c }) => {
// Implementation
});