9 | OpenAPI 3 and 3.1 schema generator and validator for Hono, itty-router and more!
10 |
11 |
12 |
13 |
14 | **Documentation**: chanfana.pages.dev
15 |
16 | **Source Code**: github.com/cloudflare/chanfana
17 |
18 |
19 |
20 | [chanfana](https://github.com/cloudflare/chanfana) **(previously known as itty-router-openapi)** is a library that adds
21 | OpenAPI schema generation and validation to any router (
22 | Hono, itty-router, etc), meant to be a
23 | powerful and lightweight
24 | library for Cloudflare Workers but runs on any runtime supported by the base router.
25 |
26 | The key features are:
27 |
28 | - OpenAPI 3 and 3.1 schema generator and validator
29 | - Fully written in typescript
30 | - [Class-based endpoints](https://chanfana.pages.dev/user-guide/first-steps/)
31 | - [Query](https://chanfana.pages.dev/user-guide/query-parameters/), [Path](https://chanfana.pages.dev/user-guide/path-parameters/), [Headers](https://chanfana.pages.dev/user-guide/header-parameters/) and [Body](https://chanfana.pages.dev/user-guide/request-body/) typescript inference
32 | - Extend existing [Hono](https://chanfana.pages.dev/routers/hono/), [itty-router](https://chanfana.pages.dev/routers/itty-router/), etc application, without touching old routes
33 |
34 | ## Getting started
35 |
36 | Get started with a template with this command:
37 |
38 | ```bash
39 | npm create cloudflare@latest -- --type openapi
40 | ```
41 |
42 | ## Installation
43 |
44 | ```bash
45 | npm i chanfana --save
46 | ```
47 |
48 | ## Minimal Hono Example
49 |
50 | ```ts
51 | import { fromHono, OpenAPIRoute } from 'chanfana'
52 | import { Hono } from 'hono'
53 | import { z } from 'zod'
54 |
55 | export type Env = {
56 | // Example bindings
57 | DB: D1Database
58 | BUCKET: R2Bucket
59 | }
60 | export type AppContext = Context<{ Bindings: Env }>
61 |
62 | export class GetPageNumber extends OpenAPIRoute {
63 | schema = {
64 | request: {
65 | params: z.object({
66 | id: z.string().min(2).max(10),
67 | }),
68 | query: z.object({
69 | page: z.number().int().min(0).max(20),
70 | }),
71 | },
72 | }
73 |
74 | async handle(c: AppContext) {
75 | const data = await this.getValidatedData()
76 |
77 | return c.json({
78 | id: data.params.id,
79 | page: data.query.page,
80 | })
81 | }
82 | }
83 |
84 | // Start a Hono app
85 | const app = new Hono<{ Bindings: Env }>()
86 |
87 | // Setup OpenAPI registry
88 | const openapi = fromHono(app)
89 |
90 | // Register OpenAPI endpoints (this will also register the routes in Hono)
91 | openapi.get('/entry/:id', GetPageNumber)
92 |
93 | // Export the Hono app
94 | export default app
95 | ```
96 |
97 | ## Feedback and contributions
98 |
99 | [chanfana](https://github.com/cloudflare/chanfana) aims to be at the core of new APIs built using
100 | Workers and define a pattern to allow everyone to
101 | have an OpenAPI-compliant schema without worrying about implementation details or reinventing the wheel.
102 |
103 | chanfana is considered stable and production ready and is being used with
104 | the [Radar 2.0 public API](https://developers.cloudflare.com/radar/) and many other Cloudflare products.
105 |
106 | You can also talk to us in the [Cloudflare Community](https://community.cloudflare.com/) or
107 | the [Radar Discord Channel](https://discord.com/channels/595317990191398933/1035553707116478495)
108 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3 | "vcs": {
4 | "enabled": false,
5 | "clientKind": "git",
6 | "useIgnoreFile": false
7 | },
8 | "files": {
9 | "ignoreUnknown": false,
10 | "ignore": ["dist", "docs", "example"]
11 | },
12 | "formatter": {
13 | "enabled": true,
14 | "indentStyle": "space",
15 | "indentWidth": 2,
16 | "lineWidth": 120
17 | },
18 | "organizeImports": {
19 | "enabled": true
20 | },
21 | "javascript": {
22 | "formatter": {
23 | "quoteStyle": "double"
24 | }
25 | },
26 | "linter": {
27 | "enabled": true,
28 | "rules": {
29 | "recommended": true,
30 | "complexity": {
31 | "noBannedTypes": "off",
32 | "noThisInStatic": "off"
33 | },
34 | "suspicious": {
35 | "noExplicitAny": "off",
36 | "noImplicitAnyLet": "off"
37 | },
38 | "performance": {
39 | "noAccumulatingSpread": "off"
40 | },
41 | "style": {
42 | "noParameterAssign": "off"
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/docs/core-concepts.md:
--------------------------------------------------------------------------------
1 | # Core Concepts of Chanfana
2 |
3 | To effectively use Chanfana, it's important to understand its core concepts. This section will break down the fundamental ideas behind Chanfana and how they work together to simplify API development and documentation.
4 |
5 | ## OpenAPI Specification: The Foundation
6 |
7 | At its heart, Chanfana is built around the [OpenAPI Specification](https://www.openapis.org/) (formerly known as Swagger Specification). OpenAPI is a standard, language-agnostic format to describe RESTful APIs. It allows both humans and computers to understand the capabilities of an API without access to source code, documentation, or network traffic inspection.
8 |
9 | **Key benefits of using OpenAPI:**
10 |
11 | * **Standardized API Descriptions:** Provides a universal language for describing APIs, making them easier to understand and integrate with.
12 | * **Automated Documentation:** Enables the generation of interactive API documentation, like Swagger UI and ReDoc, directly from the specification.
13 | * **Code Generation:** Tools can automatically generate server stubs and client SDKs from OpenAPI specifications, speeding up development.
14 | * **API Design First:** Encourages designing your API contract before writing code, leading to better API design and consistency.
15 |
16 | Chanfana leverages the OpenAPI specification to generate documentation and perform request validation, ensuring your API is well-defined and robust.
17 |
18 | ## Schema Generation and Validation: Ensuring API Quality
19 |
20 | Chanfana's primary goal is to automate the generation of OpenAPI schemas and use these schemas for request validation.
21 |
22 | * **Schema Generation:** Chanfana automatically generates OpenAPI schemas by analyzing your endpoint definitions, specifically the `schema` property within your `OpenAPIRoute` classes. It uses [zod-to-openapi](https://github.com/asteasolutions/zod-to-openapi) under the hood to convert your Zod schemas into OpenAPI schema objects. This eliminates the need to manually write verbose YAML or JSON OpenAPI specifications.
23 |
24 | * **Request Validation:** Once you define your request schemas (for body, query parameters, path parameters, and headers), Chanfana automatically validates incoming requests against these schemas. This validation happens before your `handle` method is executed. If a request doesn't conform to the schema, Chanfana will automatically return a `400 Bad Request` response with detailed validation errors, protecting your API from invalid data and ensuring data integrity.
25 |
26 | By combining schema generation and validation, Chanfana helps you build APIs that are not only well-documented but also inherently more reliable and secure.
27 |
28 | ## Routers: Hono, Itty Router, and Beyond
29 |
30 | Chanfana is designed to be router-agnostic, meaning it can be integrated with various JavaScript web routers. Currently, it provides first-class adapters for:
31 |
32 | * **[Hono](https://github.com/honojs/hono):** A small, fast, and ultrafast web framework for the Edge. Hono is a popular choice for Cloudflare Workers and other edge runtimes. Chanfana's `fromHono` adapter seamlessly extends Hono with OpenAPI capabilities.
33 |
34 | * **[itty-router](https://github.com/kwhitley/itty-router):** A tiny, functional router, also very popular for Cloudflare Workers due to its simplicity and performance. Chanfana's `fromIttyRouter` adapter brings OpenAPI support to itty-router.
35 |
36 | **Extensibility:**
37 |
38 | Chanfana's architecture is designed to be extensible. While it provides adapters for Hono and itty-router out of the box, it can be adapted to work with other routers as well. The core logic of schema generation and validation is independent of the underlying router. If you are using a different router, you can potentially create a custom adapter to integrate Chanfana.
39 |
40 | ## `OpenAPIRoute`: Building Blocks of Your API
41 |
42 | The `OpenAPIRoute` class is the central building block for defining your API endpoints in Chanfana. It's an abstract class that you extend to create concrete endpoint implementations.
43 |
44 | **Key aspects of `OpenAPIRoute`:**
45 |
46 | * **Class-Based Structure:** Encourages organizing your endpoint logic within classes, promoting code reusability and maintainability.
47 | * **Schema Definition:** The `schema` property within your `OpenAPIRoute` subclass is where you define the OpenAPI schema for your endpoint, including request and response specifications.
48 | * **`handle` Method:** This is the core method where you implement the actual logic of your endpoint. It's executed after successful request validation.
49 | * **`getValidatedData()` Method:** Provides access to the validated request data (body, query parameters, path parameters, headers) within your `handle` method, ensuring you are working with data that conforms to your schema.
50 | * **Lifecycle Hooks (e.g., `before`, `after`, `create`, `update`, `delete`, `fetch`, `list` in auto endpoints):** Some subclasses of `OpenAPIRoute`, like the predefined CRUD endpoints, offer lifecycle hooks to customize behavior at different stages of the request processing.
51 |
52 | By extending `OpenAPIRoute`, you create reusable and well-defined endpoints that automatically benefit from schema generation and validation.
53 |
54 | ## Request and Response Schemas: Defining API Contracts
55 |
56 | The `schema` property in your `OpenAPIRoute` class is crucial for defining the contract of your API endpoint. It's an object that can contain the following key properties:
57 |
58 | * **`request`:** Defines the structure of the incoming request. It can specify schemas for:
59 | * `body`: Request body (typically for `POST`, `PUT`, `PATCH` requests).
60 | * `query`: Query parameters in the URL.
61 | * `params`: Path parameters in the URL path.
62 | * `headers`: HTTP headers.
63 |
64 | * **`responses`:** Defines the possible responses your API endpoint can return. For each response, you specify:
65 | * `statusCode`: The HTTP status code (e.g., "200", "400", "500").
66 | * `description`: A human-readable description of the response.
67 | * `content`: The response body content, including the media type (e.g., "application/json") and the schema of the response body.
68 |
69 | Chanfana uses Zod schemas to define the structure of both requests and responses. Zod is a TypeScript-first schema declaration and validation library that is highly expressive and type-safe. Chanfana provides helper functions like `contentJson` to simplify defining JSON request and response bodies.
70 |
71 | ## Data Validation: Protecting Your API
72 |
73 | Data validation is automatically performed by Chanfana based on the request schemas you define in your `OpenAPIRoute` classes.
74 |
75 | **How validation works:**
76 |
77 | 1. **Request Interception:** When a request comes in for a Chanfana-managed route, Chanfana intercepts it before it reaches your `handle` method.
78 | 2. **Schema Parsing:** Chanfana parses the request data (body, query parameters, path parameters, headers) and attempts to validate it against the corresponding schemas defined in your endpoint's `schema.request` property.
79 | 3. **Zod Validation:** Under the hood, Chanfana uses Zod to perform the actual validation. Zod checks if the incoming data conforms to the defined schema rules (data types, required fields, formats, etc.).
80 | 4. **Success or Failure:**
81 | * **Success:** If the request data is valid according to the schema, Chanfana proceeds to execute your `handle` method. You can then access the validated data using `this.getValidatedData()`.
82 | * **Failure:** If the request data is invalid, Zod throws a `ZodError` exception. Chanfana catches this exception and automatically generates a `400 Bad Request` response. This response includes a JSON body containing detailed information about the validation errors, helping clients understand what went wrong and how to fix their requests.
83 |
84 | ## Error Handling: Graceful API Responses
85 |
86 | Chanfana provides a structured approach to error handling. When validation fails or when exceptions occur within your `handle` method, Chanfana aims to return informative and consistent error responses.
87 |
88 | * **Automatic Validation Error Responses:** As mentioned above, validation errors are automatically caught and transformed into `400 Bad Request` responses with detailed error messages.
89 | * **Exception Handling:** You can throw custom exceptions within your `handle` method to signal specific error conditions. Chanfana provides base exception classes like `ApiException`, `InputValidationException`, and `NotFoundException` that you can use or extend to create your own API-specific exceptions. These exceptions can be configured to automatically generate appropriate error responses and OpenAPI schema definitions for error responses.
90 | * **Consistent Error Format:** Chanfana encourages a consistent error response format (typically JSON) to make it easier for clients to handle errors from your API.
91 |
92 | By understanding these core concepts, you are well-equipped to start building robust, well-documented, and maintainable APIs with Chanfana.
93 |
94 | ---
95 |
96 | Next, let's explore how to [**Define Endpoints**](./endpoints/defining-endpoints.md) in more detail.
97 |
--------------------------------------------------------------------------------
/docs/endpoints/defining-endpoints.md:
--------------------------------------------------------------------------------
1 | # Defining Endpoints
2 |
3 | The `OpenAPIRoute` class is the cornerstone of building APIs with Chanfana. It provides a structured and type-safe way to define your API endpoints, including their schemas and logic. This guide will delve into the details of using `OpenAPIRoute` to create robust and well-documented endpoints.
4 |
5 | ## Understanding the `OpenAPIRoute` Class
6 |
7 | `OpenAPIRoute` is an abstract class that serves as the base for all your API endpoint classes in Chanfana. To create an endpoint, you will extend this class and implement its properties and methods.
8 |
9 | **Key Components of an `OpenAPIRoute` Class:**
10 |
11 | * **`schema` Property:** This is where you define the OpenAPI schema for your endpoint. It's an object that specifies the structure of the request (body, query, params, headers) and the possible responses. The `schema` is crucial for both OpenAPI documentation generation and request validation.
12 |
13 | * **`handle(...args: any[])` Method:** This **asynchronous** method contains the core logic of your endpoint. It's executed when a valid request is received. The arguments passed to `handle` depend on the router adapter you are using (e.g., Hono's `Context` object). You are expected to return a `Response` object, a Promise that resolves to a `Response`, or a plain JavaScript object (which Chanfana will automatically convert to a JSON response).
14 |
15 | * **`getValidatedData()` Method:** This **asynchronous** method is available within your `handle` method. It allows you to access the validated request data. It returns a Promise that resolves to an object containing the validated `body`, `query`, `params`, and `headers` based on the schemas you defined in the `schema.request` property. TypeScript type inference is used to provide type safety based on your schema definition.
16 |
17 | ## Basic Endpoint Structure
18 |
19 | Here's the basic structure of an `OpenAPIRoute` class:
20 |
21 | ```typescript
22 | import { OpenAPIRoute } from 'chanfana';
23 | import { z } from 'zod';
24 | import { type Context } from 'hono';
25 |
26 | class MyEndpoint extends OpenAPIRoute {
27 | schema = {
28 | // Define your OpenAPI schema here (request and responses)
29 | request: {
30 | // ... request schema (optional)
31 | },
32 | responses: {
33 | // ... response schema (required)
34 | },
35 | };
36 |
37 | async handle(c: Context) {
38 | // Implement your endpoint logic here
39 | // Access validated data using this.getValidatedData()
40 | // Return a Response, Promise, or a plain object
41 | }
42 | }
43 | ```
44 |
45 | ## Defining the `schema`
46 |
47 | The `schema` property is where you define the OpenAPI contract for your endpoint. Let's break down its components:
48 |
49 | ### Request Schema (`request`)
50 |
51 | The `request` property is an optional object that defines the structure of the incoming request. It can contain the following properties, each being a Zod schema:
52 |
53 | * **`body`:** Schema for the request body. Typically used for `POST`, `PUT`, and `PATCH` requests. You'll often use `contentJson` to define JSON request bodies.
54 | * **`query`:** Schema for query parameters in the URL. Use `z.object({})` to define the structure of query parameters.
55 | * **`params`:** Schema for path parameters in the URL path. Use `z.object({})` to define the structure of path parameters.
56 | * **`headers`:** Schema for HTTP headers. Use `z.object({})` to define the structure of headers.
57 |
58 | **Example: Request Schema with Body and Query Parameters**
59 |
60 | ```typescript
61 | import { OpenAPIRoute, contentJson } from 'chanfana';
62 | import { z } from 'zod';
63 | import { type Context } from 'hono';
64 |
65 | class ExampleEndpoint extends OpenAPIRoute {
66 | schema = {
67 | request: {
68 | body: contentJson(z.object({
69 | name: z.string().min(3),
70 | email: z.string().email(),
71 | })),
72 | query: z.object({
73 | page: z.number().int().min(1).default(1),
74 | pageSize: z.number().int().min(1).max(100).default(20),
75 | }),
76 | },
77 | responses: {
78 | // ... response schema
79 | },
80 | };
81 |
82 | async handle(c: Context) {
83 | const data = await this.getValidatedData();
84 | // data.body will be of type { name: string, email: string }
85 | // data.query will be of type { page: number, pageSize: number }
86 | console.log("Validated Body:", data.body);
87 | console.log("Validated Query:", data.query);
88 | return { message: 'Request Validated!' };
89 | }
90 | }
91 | ```
92 |
93 | ### Response Schema (`responses`)
94 |
95 | The `responses` property is a **required** object that defines the possible responses your endpoint can return. It's structured as a dictionary where keys are HTTP status codes (e.g., "200", "400", "500") and values are response definitions.
96 |
97 | Each response definition should include:
98 |
99 | * **`description`:** A human-readable description of the response.
100 | * **`content`:** (Optional) Defines the response body content. You'll often use `contentJson` to define JSON response bodies.
101 |
102 | **Example: Response Schema with Success and Error Responses**
103 |
104 | ```typescript
105 | import { OpenAPIRoute, contentJson, InputValidationException } from 'chanfana';
106 | import { z } from 'zod';
107 | import { type Context } from 'hono';
108 |
109 | class AnotherEndpoint extends OpenAPIRoute {
110 | schema = {
111 | responses: {
112 | "200": {
113 | description: 'Successful operation',
114 | content: contentJson(z.object({
115 | status: z.string().default("success"),
116 | data: z.object({ id: z.number() }),
117 | })),
118 | },
119 | ...InputValidationException.schema(),
120 | "500": {
121 | description: 'Internal Server Error',
122 | content: contentJson(z.object({
123 | status: z.string().default("error"),
124 | message: z.string(),
125 | })),
126 | },
127 | },
128 | };
129 |
130 | async handle(c: Context) {
131 | // ... your logic ...
132 | const success = Math.random() > 0.5;
133 | if (success) {
134 | return { status: "success", data: { id: 123 } };
135 | } else {
136 | throw new Error("Something went wrong!"); // Example of throwing an error
137 | }
138 | }
139 | }
140 | ```
141 |
142 | ## Implementing the `handle` Method
143 |
144 | The `handle` method is where you write the core logic of your API endpoint. It's an asynchronous method that receives arguments depending on the router adapter.
145 |
146 | **Inside the `handle` method, you typically:**
147 |
148 | 1. **Access Validated Data:** Use `this.getValidatedData()` to retrieve the validated request data. TypeScript will infer the types of `data.body`, `data.query`, `data.params`, and `data.headers` based on your schema.
149 | 2. **Implement Business Logic:** Perform the operations your endpoint is designed for (e.g., database interactions, calculations, external API calls).
150 | 3. **Return a Response:**
151 | * **Return a `Response` object directly:** You can construct a `Response` object using the built-in `Response` constructor or helper functions from your router framework (e.g., `c.json()` in Hono).
152 | * **Return a Promise that resolves to a `Response`:** If your logic is asynchronous, return a Promise that resolves to a `Response`.
153 | * **Return a plain JavaScript object:** Chanfana will automatically convert a plain JavaScript object into a JSON response with a `200 OK` status code. You can customize the status code and headers if needed by returning a `Response` object instead.
154 |
155 | **Example: `handle` Method Logic**
156 |
157 | ```typescript
158 | import { OpenAPIRoute, contentJson } from 'chanfana';
159 | import { z } from 'zod';
160 | import { type Context } from 'hono';
161 |
162 | class UserEndpoint extends OpenAPIRoute {
163 | schema = {
164 | request: {
165 | params: z.object({
166 | userId: z.string(),
167 | }),
168 | },
169 | responses: {
170 | "200": {
171 | description: 'User details retrieved',
172 | content: contentJson(z.object({
173 | id: z.string(),
174 | name: z.string(),
175 | email: z.string(),
176 | })),
177 | },
178 | // ... error responses
179 | },
180 | };
181 |
182 | async handle(c: Context) {
183 | const data = await this.getValidatedData();
184 | const userId = data.params.userId;
185 |
186 | // Simulate fetching user data (replace with actual database/service call)
187 | const user = {
188 | id: userId,
189 | name: `User ${userId}`,
190 | email: `user${userId}@example.com`,
191 | };
192 |
193 | return { ...user }; // Return a plain object, Chanfana will convert to JSON
194 | }
195 | }
196 | ```
197 |
198 | ## Accessing Validated Data with `getValidatedData()`
199 |
200 | The `getValidatedData()` method is crucial for accessing the validated request data within your `handle` method.
201 |
202 | **Key features of `getValidatedData()`:**
203 |
204 | * **Type Safety:** By using `getValidatedData()`, you get strong TypeScript type inference. The returned `data` object will have properties (`body`, `query`, `params`, `headers`) that are typed according to your schema definitions. This significantly improves code safety and developer experience.
205 | * **Asynchronous Operation:** `getValidatedData()` is an asynchronous method because it performs request validation. You need to `await` its result before accessing the validated data.
206 | * **Error Handling:** If the request validation fails, `getValidatedData()` will throw a `ZodError` exception. Chanfana automatically catches this exception and returns a `400 Bad Request` response. You typically don't need to handle validation errors explicitly within your `handle` method unless you want to customize the error response further.
207 |
208 | **Example: Using `getValidatedData()`**
209 |
210 | ```typescript
211 | import { type Context } from 'hono';
212 |
213 | async handle(c: Context) {
214 | const data = await this.getValidatedData();
215 | const userName = data.body.name; // TypeScript knows data.body.name is a string
216 | const pageNumber = data.query.page; // TypeScript knows data.query.page is a number
217 |
218 | // ... use validated data in your logic ...
219 | }
220 | ```
221 |
222 | ## Example: A Simple Greeting Endpoint
223 |
224 | Let's put it all together with a simple greeting endpoint that takes a name as a query parameter and returns a personalized greeting.
225 |
226 | ```typescript
227 | import { Hono, type Context } from 'hono';
228 | import { fromHono, OpenAPIRoute } from 'chanfana';
229 | import { z } from 'zod';
230 |
231 | export type Env = {
232 | // Example bindings, use your own
233 | DB: D1Database
234 | BUCKET: R2Bucket
235 | }
236 | export type AppContext = Context<{ Bindings: Env }>
237 |
238 | class GreetingEndpoint extends OpenAPIRoute {
239 | schema = {
240 | request: {
241 | query: z.object({
242 | name: z.string().min(1).describe("Name to greet"),
243 | }),
244 | },
245 | responses: {
246 | "200": {
247 | description: 'Greeting message',
248 | content: contentJson(z.object({
249 | greeting: z.string(),
250 | })),
251 | },
252 | },
253 | };
254 |
255 | async handle(c: AppContext) {
256 | const data = await this.getValidatedData();
257 | const name = data.query.name;
258 | return { greeting: `Hello, ${name}! Welcome to Chanfana.` };
259 | }
260 | }
261 |
262 | const app = new Hono<{ Bindings: Env }>();
263 | const openapi = fromHono(app);
264 | openapi.get('/greet', GreetingEndpoint);
265 |
266 | export default app;
267 | ```
268 |
269 | This example demonstrates the basic structure of an `OpenAPIRoute`, defining a schema for query parameters and responses, and implementing the endpoint logic in the `handle` method.
270 |
271 | ---
272 |
273 | In the next sections, we will explore request validation and response definition in more detail, along with the various parameter types Chanfana provides. Let's start with [**Request Validation in Detail**](./request-validation.md).
274 |
--------------------------------------------------------------------------------
/docs/endpoints/request-validation.md:
--------------------------------------------------------------------------------
1 | # Request Validation
2 |
3 | Chanfana's automatic request validation is a key feature that ensures your API receives and processes only valid data. This section dives deep into how request validation works for different parts of an HTTP request: body, query parameters, path parameters, and headers.
4 |
5 | ## Validating Request Body
6 |
7 | Request body validation is crucial for `POST`, `PUT`, and `PATCH` requests where clients send data to your API in the request body. Chanfana primarily supports JSON request bodies and uses Zod schemas to define their structure.
8 |
9 | ### Using `contentJson` for JSON Bodies
10 |
11 | The `contentJson` helper function simplifies defining JSON request bodies in your `schema.request.body`. It automatically sets the `content-type` to `application/json` and wraps your Zod schema appropriately for OpenAPI.
12 |
13 | **Example: Validating a User Creation Body**
14 |
15 | ```typescript
16 | import { OpenAPIRoute, contentJson } from 'chanfana';
17 | import { z } from 'zod';
18 | import { type Context } from 'hono';
19 |
20 | class CreateUserEndpoint extends OpenAPIRoute {
21 | schema = {
22 | request: {
23 | body: contentJson(z.object({
24 | username: z.string().min(3).max(20),
25 | password: z.string().min(8),
26 | email: z.string().email(),
27 | fullName: z.string().optional(),
28 | age: z.number().int().positive().optional(),
29 | })),
30 | },
31 | responses: {
32 | // ... responses
33 | },
34 | };
35 |
36 | async handle(c: Context) {
37 | const data = await this.getValidatedData();
38 | const userDetails = data.body; // Type-safe access to validated body
39 |
40 | // ... logic to create a user ...
41 | return { message: 'User created successfully' };
42 | }
43 | }
44 | ```
45 |
46 | In this example:
47 |
48 | * We use `contentJson` to wrap a Zod object schema that defines the expected structure of the JSON request body.
49 | * The schema specifies fields like `username`, `password`, `email`, `fullName`, and `age` with their respective types and validation rules (e.g., `min`, `max`, `email`, `int`, `positive`, `optional`).
50 | * In the `handle` method, `this.getValidatedData().body` will be automatically typed as:
51 |
52 | ```typescript
53 | {
54 | username: string;
55 | password: string;
56 | email: string;
57 | fullName?: string | undefined;
58 | age?: number | undefined;
59 | }
60 | ```
61 |
62 | ### Zod Schemas for Body
63 |
64 | You can use the full power of Zod to define complex validation rules for your request bodies. This includes:
65 |
66 | * **Data Types:** `z.string()`, `z.number()`, `z.boolean()`, `z.date()`, `z.array()`, `z.object()`, `z.enum()`, etc.
67 | * **String Validations:** `min()`, `max()`, `email()`, `url()`, `uuid()`, `regex()`, etc.
68 | * **Number Validations:** `int()`, `positive()`, `negative()`, `min()`, `max()`, etc.
69 | * **Array Validations:** `min()`, `max()`, `nonempty()`, `unique()`, etc.
70 | * **Object Validations:** `required()`, `optional()`, `partial()`, `strict()`, `refine()`, etc.
71 | * **Transformations:** `transform()`, `preprocess()`, etc.
72 | * **Effects:** `refinement()`, `superRefine()`, etc.
73 |
74 | Refer to the [Zod documentation](https://zod.dev/) for a comprehensive list of validation methods and features.
75 |
76 | ### Body Type Inference
77 |
78 | Chanfana leverages TypeScript's type inference capabilities. When you use `getValidatedData().body`, TypeScript automatically infers the type of `data.body` based on the Zod schema you defined in `schema.request.body`. This provides excellent type safety and autocompletion in your code editor.
79 |
80 | ## Validating Query Parameters
81 |
82 | Query parameters are key-value pairs appended to the URL after the `?` symbol (e.g., `/items?page=1&pageSize=20`). Chanfana validates query parameters using Zod schemas defined in `schema.request.query`.
83 |
84 | ### Defining Query Parameter Schema with Zod
85 |
86 | Use `z.object({})` within `schema.request.query` to define the expected query parameters and their validation rules.
87 |
88 | **Example: Filtering Resources with Query Parameters**
89 |
90 | ```typescript
91 | import { OpenAPIRoute } from 'chanfana';
92 | import { z } from 'zod';
93 | import { type Context } from 'hono';
94 |
95 | class ListProductsEndpoint extends OpenAPIRoute {
96 | schema = {
97 | request: {
98 | query: z.object({
99 | category: z.string().optional().describe("Filter by product category"),
100 | minPrice: z.number().min(0).optional().describe("Filter products with minimum price"),
101 | maxPrice: z.number().min(0).optional().describe("Filter products with maximum price"),
102 | sortBy: z.enum(['price', 'name', 'date']).default('name').describe("Sort products by field"),
103 | sortOrder: z.enum(['asc', 'desc']).default('asc').describe("Sort order"),
104 | page: z.number().int().min(1).default(1).describe("Page number for pagination"),
105 | pageSize: z.number().int().min(1).max(100).default(20).describe("Number of items per page"),
106 | }),
107 | },
108 | responses: {
109 | // ... responses
110 | },
111 | };
112 |
113 | async handle(c: Context) {
114 | const data = await this.getValidatedData();
115 | const queryParams = data.query; // Type-safe access to validated query parameters
116 |
117 | // ... logic to fetch and filter products based on queryParams ...
118 | return { message: 'Product list retrieved' };
119 | }
120 | }
121 | ```
122 |
123 | In this example:
124 |
125 | * We define a Zod object schema for `schema.request.query` with various query parameters like `category`, `minPrice`, `maxPrice`, `sortBy`, `sortOrder`, `page`, and `pageSize`.
126 | * Each parameter is defined with its type, validation rules (e.g., `optional()`, `min()`, `enum()`, `default()`), and a `describe()` method to add descriptions for OpenAPI documentation.
127 | * `this.getValidatedData().query` will be typed according to the schema, providing type-safe access to validated query parameters.
128 |
129 | ### Query Parameter Type Inference
130 |
131 | Similar to request bodies, Chanfana infers the types of query parameters based on your Zod schema. `data.query` will be an object with properties corresponding to your query parameter names, and their types will match the Zod schema definitions.
132 |
133 | ## Validating Path Parameters
134 |
135 | Path parameters are dynamic segments in the URL path, denoted by colons (e.g., `/users/:userId`). Chanfana validates path parameters using Zod schemas defined in `schema.request.params`.
136 |
137 | ### Defining Path Parameter Schema with Zod
138 |
139 | Use `z.object({})` within `schema.request.params` to define the expected path parameters and their validation rules.
140 |
141 | **Example: Retrieving a Resource by ID**
142 |
143 | ```typescript
144 | import { OpenAPIRoute } from 'chanfana';
145 | import { z } from 'zod';
146 | import { type Context } from 'hono';
147 |
148 | class GetProductEndpoint extends OpenAPIRoute {
149 | schema = {
150 | request: {
151 | params: z.object({
152 | productId: z.string().uuid().describe("Unique ID of the product"),
153 | }),
154 | },
155 | responses: {
156 | // ... responses
157 | },
158 | };
159 |
160 | async handle(c: Context) {
161 | const data = await this.getValidatedData();
162 | const productId = data.params.productId; // Type-safe access to validated path parameter
163 |
164 | // ... logic to fetch product details based on productId ...
165 | return { message: `Product ${productId} details retrieved` };
166 | }
167 | }
168 | ```
169 |
170 | In this example:
171 |
172 | * We define a Zod object schema for `schema.request.params` with a single path parameter `productId`.
173 | * The `productId` is defined as a `z.string().uuid()` to ensure it's a valid UUID.
174 | * `this.getValidatedData().params` will be typed as:
175 |
176 | ```typescript
177 | {
178 | productId: string;
179 | }
180 | ```
181 |
182 | ### Path Parameter Type Inference
183 |
184 | Type inference works similarly for path parameters. `data.params` will be an object with properties corresponding to your path parameter names, and their types will be inferred from your Zod schema.
185 |
186 | ## Validating Headers
187 |
188 | Headers are metadata sent with HTTP requests. Chanfana allows you to validate specific headers using Zod schemas defined in `schema.request.headers`.
189 |
190 | ### Defining Header Schema with Zod
191 |
192 | Use `z.object({})` within `schema.request.headers` to define the headers you want to validate and their rules.
193 |
194 | **Example: API Key Authentication via Headers**
195 |
196 | ```typescript
197 | import { OpenAPIRoute } from 'chanfana';
198 | import { z } from 'zod';
199 | import { type Context } from 'hono';
200 |
201 | class AuthenticatedEndpoint extends OpenAPIRoute {
202 | schema = {
203 | request: {
204 | headers: z.object({
205 | 'X-API-Key': z.string().describe("API Key for authentication"),
206 | }),
207 | },
208 | responses: {
209 | // ... responses
210 | },
211 | };
212 |
213 | async handle(c: Context) {
214 | const data = await this.getValidatedData();
215 | const apiKey = data.headers['X-API-Key']; // Type-safe access to validated header
216 |
217 | // ... logic to authenticate user based on apiKey ...
218 | return { message: 'Authenticated request' };
219 | }
220 | }
221 | ```
222 |
223 | In this example:
224 |
225 | * We define a Zod object schema for `schema.request.headers` to validate the `X-API-Key` header.
226 | * `this.getValidatedData().headers` will be typed as:
227 |
228 | ```typescript
229 | {
230 | 'X-API-Key': string;
231 | }
232 | ```
233 |
234 | ### Header Parameter Type Inference
235 |
236 | Type inference also applies to headers. `data.headers` will be an object with properties corresponding to your header names (in lowercase), and their types will be inferred from your Zod schema.
237 |
238 | ---
239 |
240 | By leveraging Chanfana's request validation capabilities, you can build APIs that are more secure, reliable, and easier to maintain. Validation ensures that your API logic only processes valid data, reducing errors and improving the overall API experience.
241 |
242 | Next, let's move on to [**Crafting Response Definitions**](./response-definition.md) to learn how to define the responses your API endpoints will return.
243 |
--------------------------------------------------------------------------------
/docs/endpoints/response-definition.md:
--------------------------------------------------------------------------------
1 | # Response Definitions
2 |
3 | Defining clear and comprehensive response definitions is as important as request validation for building well-documented and predictable APIs. Chanfana makes it easy to define your API responses within the `schema.responses` property of your `OpenAPIRoute` classes. This section will guide you through crafting effective response definitions.
4 |
5 | ## Structuring the `responses` Schema
6 |
7 | The `responses` property in your `OpenAPIRoute.schema` is an object that defines all possible HTTP responses your endpoint can return. It's structured as a dictionary where:
8 |
9 | * **Keys are HTTP Status Codes:** These are strings representing HTTP status codes (e.g., `"200"`, `"201"`, `"400"`, `"404"`, `"500"`). You should define responses for all relevant status codes your endpoint might return, including success and error scenarios.
10 | * **Values are Response Definitions:** Each value is an object that defines the details of the response for the corresponding status code.
11 |
12 | ## Defining Response Status Codes
13 |
14 | You should define responses for all relevant HTTP status codes that your endpoint might return. Common categories include:
15 |
16 | ### Success Responses (2xx)
17 |
18 | These status codes indicate that the request was successfully processed. Common success status codes include:
19 |
20 | * **`"200"` (OK):** Standard response for successful GET, PUT, PATCH, and DELETE requests. Typically used when returning data or confirming a successful operation.
21 | * **`"201"` (Created):** Response for successful POST requests that result in the creation of a new resource. Often includes details of the newly created resource in the response body and a `Location` header pointing to the resource's URL.
22 | * **`"204"` (No Content):** Response for successful requests that don't return any content in the response body, such as successful DELETE operations or updates where no data needs to be returned.
23 |
24 | **Example: Success Responses**
25 |
26 | ```typescript
27 | import { OpenAPIRoute, contentJson } from 'chanfana';
28 | import { z } from 'zod';
29 | import { type Context } from 'hono';
30 |
31 | class CreateResourceEndpoint extends OpenAPIRoute {
32 | schema = {
33 | // ... request schema ...
34 | responses: {
35 | "201": {
36 | description: 'Resource created successfully',
37 | content: contentJson(z.object({
38 | id: z.string(),
39 | createdAt: z.string().datetime(),
40 | })),
41 | headers: z.object({
42 | 'Location': z.string().url().describe('URL of the newly created resource'),
43 | }),
44 | },
45 | "400": { description: 'Bad Request' }, // Example error response
46 | },
47 | };
48 |
49 | async handle(c: Context) {
50 | // ... logic to create resource ...
51 | const newResourceId = 'resource-123';
52 | const resource = { id: newResourceId, createdAt: new Date().toISOString() };
53 | return new Response(JSON.stringify(resource), {
54 | status: 201,
55 | headers: { 'Location': `/resources/${newResourceId}` },
56 | });
57 | }
58 | }
59 | ```
60 |
61 | ### Error Responses (4xx, 5xx)
62 |
63 | These status codes indicate that an error occurred during request processing. It's crucial to define error responses to provide clients with information about what went wrong and how to fix it. Common error status code categories include:
64 |
65 | * **4xx Client Errors:** Indicate errors caused by the client's request (e.g., invalid input, unauthorized access).
66 | * **`"400"` (Bad Request):** Generic client error, often used for validation failures.
67 | * **`"401"` (Unauthorized):** Indicates missing or invalid authentication credentials.
68 | * **`"403"` (Forbidden):** Indicates that the client is authenticated but doesn't have permission to access the resource.
69 | * **`"404"` (Not Found):** Indicates that the requested resource could not be found.
70 | * **`"409"` (Conflict):** Indicates a conflict with the current state of the resource (e.g., trying to create a resource that already exists).
71 | * **`"422"` (Unprocessable Entity):** Used for validation errors when the server understands the request entity but is unable to process it (more semantically correct than 400 for validation errors in some contexts).
72 |
73 | * **5xx Server Errors:** Indicate errors on the server side.
74 | * **`"500"` (Internal Server Error):** Generic server error, should be avoided in detailed API responses but can be used as a fallback.
75 | * **`"503"` (Service Unavailable):** Indicates that the server is temporarily unavailable (e.g., due to overload or maintenance).
76 |
77 | **Example: Error Responses**
78 |
79 | ```typescript
80 | import { OpenAPIRoute, contentJson, InputValidationException, NotFoundException } from 'chanfana';
81 | import { z } from 'zod';
82 | import { type Context } from 'hono';
83 |
84 | class GetItemEndpoint extends OpenAPIRoute {
85 | schema = {
86 | request: {
87 | params: z.object({ itemId: z.string() }),
88 | },
89 | responses: {
90 | "200": {
91 | description: 'Item details retrieved',
92 | content: contentJson(z.object({
93 | id: z.string(),
94 | name: z.string(),
95 | })),
96 | },
97 | ...InputValidationException.schema(),
98 | ...NotFoundException.schema(),
99 | "500": {
100 | description: 'Internal Server Error',
101 | content: contentJson(z.object({
102 | error: z.string(),
103 | })),
104 | },
105 | },
106 | };
107 |
108 | getItemFromDatabase(itemId: string) {
109 | // ... your database lookup logic ...
110 | // Simulate item not found for certain IDs
111 | if (itemId === 'item-not-found') return null;
112 | return { id: itemId, name: `Item ${itemId}` };
113 | }
114 |
115 | async handle(c: Context) {
116 | const data = await this.getValidatedData();
117 | const itemId = data.params.itemId;
118 |
119 | // Simulate item retrieval (replace with actual logic)
120 | const item = this.getItemFromDatabase(itemId); // Assume this function might return null
121 |
122 | if (!item) {
123 | throw new NotFoundException(`Item with ID '${itemId}' not found`);
124 | }
125 |
126 | return { ...item };
127 | }
128 | }
129 | ```
130 |
131 | In this example, we define responses for:
132 |
133 | * `"200"` (OK) for successful item retrieval.
134 | * `"400"` (Bad Request) using the schema from `InputValidationException` (for validation errors).
135 | * `"404"` (Not Found) using the schema from `NotFoundException` (when the item is not found).
136 | * `"500"` (Internal Server Error) for generic server-side errors.
137 |
138 | ## Defining Response Bodies
139 |
140 | For each response status code, you can define a response body using the `content` property. Similar to request bodies, you'll often use `contentJson` for JSON response bodies.
141 |
142 | ### Using `contentJson` for JSON Responses
143 |
144 | `contentJson` simplifies defining JSON response bodies. It sets the `content-type` to `application/json` and wraps your Zod schema for OpenAPI.
145 |
146 | **Example: Defining Response Body Schema with Zod**
147 |
148 | In the examples above, we've already seen how to use `contentJson` to define response bodies. Here's a recap:
149 |
150 | ```typescript
151 | responses: {
152 | "200": {
153 | description: 'Successful response with user data',
154 | content: contentJson(z.object({
155 | id: z.string(),
156 | username: z.string(),
157 | email: z.string(),
158 | })),
159 | },
160 | // ... other responses ...
161 | }
162 | ```
163 |
164 | ### Zod Schemas for Response Bodies
165 |
166 | You can use the same Zod schema capabilities for response bodies as you do for request bodies. Define the structure and data types of your response payloads using Zod's rich set of validation and schema definition methods.
167 |
168 | ## Example Responses
169 |
170 | Let's look at a complete example that defines both request and response schemas for a simple endpoint:
171 |
172 | ```typescript
173 | import { Hono, type Context } from 'hono';
174 | import { fromHono, OpenAPIRoute, contentJson, InputValidationException, NotFoundException } from 'chanfana';
175 | import { z } from 'zod';
176 |
177 | export type Env = {
178 | // Example bindings, use your own
179 | DB: D1Database
180 | BUCKET: R2Bucket
181 | }
182 | export type AppContext = Context<{ Bindings: Env }>
183 |
184 | class ProductEndpoint extends OpenAPIRoute {
185 | schema = {
186 | request: {
187 | params: z.object({
188 | productId: z.string().uuid().describe("Unique ID of the product"),
189 | }),
190 | },
191 | responses: {
192 | "200": {
193 | description: 'Product details retrieved',
194 | content: contentJson(z.object({
195 | id: z.string(),
196 | name: z.string(),
197 | description: z.string().optional(),
198 | price: z.number().positive(),
199 | imageUrl: z.string().url().optional(),
200 | })),
201 | },
202 | ...InputValidationException.schema(),
203 | ...NotFoundException.schema(),
204 | "500": {
205 | description: 'Internal Server Error',
206 | content: contentJson(z.object({
207 | error: z.string(),
208 | })),
209 | },
210 | },
211 | };
212 |
213 | getProductFromDatabase(productId: string) {
214 | // ... your database lookup logic ...
215 | // Simulate product data
216 | return {
217 | id: productId,
218 | name: `Awesome Product ${productId}`,
219 | description: 'This is a simulated product for demonstration.',
220 | price: 99.99,
221 | imageUrl: 'https://example.com/product-image.jpg',
222 | };
223 | }
224 |
225 | async handle(c: AppContext) {
226 | const data = await this.getValidatedData();
227 | const productId = data.params.productId;
228 |
229 | // Simulate fetching product data (replace with actual logic)
230 | const product = this.getProductFromDatabase(productId);
231 |
232 | if (!product) {
233 | throw new NotFoundException(`Product with ID '${productId}' not found`);
234 | }
235 |
236 | return { ...product };
237 | }
238 | }
239 |
240 | const app = new Hono<{ Bindings: Env }>();
241 | const openapi = fromHono(app);
242 | openapi.get('/products/:productId', ProductEndpoint);
243 |
244 | export default app;
245 | ```
246 |
247 | This comprehensive example demonstrates how to define both success and error responses with detailed schemas for the response bodies, making your API documentation clear and your API behavior predictable for clients.
248 |
249 | ---
250 |
251 | Next, we will explore [**Leveraging Auto Endpoints for CRUD Operations**](./auto/base.md) to see how Chanfana simplifies common API patterns.
252 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | This guide will walk you through the initial steps to get Chanfana up and running. We'll cover installation, setting up basic examples with both Hono and itty-router, and exploring the automatically generated OpenAPI documentation.
4 |
5 | ## Prerequisites
6 |
7 | Before you begin, ensure you have the following installed:
8 |
9 | * **Node.js** (version 18 or later recommended) and **npm** (Node Package Manager) or **yarn**.
10 | * **A text editor or IDE** (like VS Code) for writing code.
11 |
12 | ## Installation
13 |
14 | Installing Chanfana is straightforward using npm or yarn:
15 |
16 | **Using npm:**
17 |
18 | ```bash
19 | npm install chanfana --save
20 | ```
21 |
22 | **Using yarn:**
23 |
24 | ```bash
25 | yarn add chanfana
26 | ```
27 |
28 | ## Quick Start with Hono
29 |
30 | Let's create a simple "Hello, World!" API endpoint using Hono and Chanfana.
31 |
32 | ### Creating Your First Endpoint (Hono)
33 |
34 | 1. **Create a new project directory:**
35 |
36 | ```bash
37 | mkdir chanfana-hono-example
38 | cd chanfana-hono-example
39 | npm init -y # or yarn init -y
40 | ```
41 |
42 | 2. **Install dependencies:**
43 |
44 | ```bash
45 | npm install hono chanfana zod --save # or yarn add hono chanfana zod
46 | ```
47 |
48 | 3. **Create a file named `index.ts` (or `src/index.ts` if you are setting up a more structured project) and add the following code:**
49 |
50 | ```typescript
51 | import { Hono, type Context } from 'hono';
52 | import { fromHono, OpenAPIRoute } from 'chanfana';
53 | import { z } from 'zod';
54 |
55 | export type Env = {
56 | // Example bindings, use your own
57 | DB: D1Database
58 | BUCKET: R2Bucket
59 | }
60 | export type AppContext = Context<{ Bindings: Env }>
61 |
62 | // Define a simple endpoint class
63 | class HelloEndpoint extends OpenAPIRoute {
64 | schema = {
65 | responses: {
66 | 200: {
67 | description: 'Successful response',
68 | content: {
69 | 'application/json': {
70 | schema: z.object({ message: z.string() }),
71 | },
72 | },
73 | },
74 | },
75 | };
76 |
77 | async handle(c: AppContext) {
78 | return { message: 'Hello, Chanfana!' };
79 | }
80 | }
81 |
82 | // Create a Hono app
83 | const app = new Hono<{ Bindings: Env }>();
84 |
85 | // Initialize Chanfana for Hono
86 | const openapi = fromHono(app);
87 |
88 | // Register the endpoint
89 | openapi.get('/hello', HelloEndpoint);
90 |
91 | // Export the Hono app (for Cloudflare Workers or other runtimes)
92 | export default app;
93 | ```
94 |
95 | ### Running the Example (Hono)
96 |
97 | 1. **Run your application.** The command to run your application will depend on your environment. For a simple Node.js environment, you can use `tsx` or `node`:
98 |
99 | ```bash
100 | npx tsx index.ts # or node index.js if you compiled to JS
101 | ```
102 |
103 | If you are using Cloudflare Workers, you would typically use `wrangler dev` or `wrangler publish`.
104 |
105 | 2. **Access your API.** By default, Hono applications listen on port 3000. You can access your API endpoint in your browser or using `curl`:
106 |
107 | ```bash
108 | curl http://localhost:3000/hello
109 | ```
110 |
111 | You should see the JSON response:
112 |
113 | ```json
114 | {"message": "Hello, Chanfana!"}
115 | ```
116 |
117 | ### Exploring the OpenAPI Documentation (Hono)
118 |
119 | 1. **Navigate to the documentation URL.** Open your browser and go to the `/docs` URL you configured in the `fromHono` options (in this example, `http://localhost:3000/docs`).
120 |
121 | 2. **Explore the Swagger UI.** You should see the Swagger UI interface, automatically generated from your endpoint schema. You can explore your API's endpoints, schemas, and even try out API calls directly from the documentation.
122 |
123 | You can also access the raw OpenAPI JSON schema at `/openapi.json` (e.g., `http://localhost:3000/openapi.json`).
124 |
125 | ## Quick Start with Itty Router
126 |
127 | Now, let's do the same with itty-router.
128 |
129 | ### Creating Your First Endpoint (Itty Router)
130 |
131 | 1. **Create a new project directory:**
132 |
133 | ```bash
134 | mkdir chanfana-itty-router-example
135 | cd chanfana-itty-router-example
136 | npm init -y # or yarn init -y
137 | ```
138 |
139 | 2. **Install dependencies:**
140 |
141 | ```bash
142 | npm install itty-router chanfana zod --save # or yarn add itty-router chanfana zod
143 | ```
144 |
145 | 3. **Create a file named `index.ts` (or `src/index.ts`) and add the following code:**
146 |
147 | ```typescript
148 | import { Router } from 'itty-router';
149 | import { fromIttyRouter, OpenAPIRoute } from 'chanfana';
150 | import { z } from 'zod';
151 |
152 | // Define a simple endpoint class
153 | class HelloEndpoint extends OpenAPIRoute {
154 | schema = {
155 | responses: {
156 | 200: {
157 | description: 'Successful response',
158 | content: {
159 | 'application/json': {
160 | schema: z.object({ message: z.string() }),
161 | },
162 | },
163 | },
164 | },
165 | };
166 |
167 | async handle(request: Request, env, ctx) {
168 | return { message: 'Hello, Chanfana for itty-router!' };
169 | }
170 | }
171 |
172 | // Create an itty-router router
173 | const router = Router();
174 |
175 | // Initialize Chanfana for itty-router
176 | const openapi = fromIttyRouter(router);
177 |
178 | // Register the endpoint
179 | openapi.get('/hello', HelloEndpoint);
180 |
181 | // Add a default handler for itty-router (required)
182 | router.all('*', () => new Response("Not Found.", { status: 404 }));
183 |
184 | // Export the router's fetch handler (for Cloudflare Workers or other runtimes)
185 | export const fetch = router.handle;
186 | ```
187 |
188 | ### Running the Example (Itty Router)
189 |
190 | 1. **Run your application.** Similar to Hono, the command depends on your environment. For Node.js:
191 |
192 | ```bash
193 | npx tsx index.ts # or node index.js if compiled
194 | ```
195 |
196 | For Cloudflare Workers, use `wrangler dev` or `wrangler publish`.
197 |
198 | 2. **Access your API.** itty-router also defaults to port 3000. Access the endpoint:
199 |
200 | ```bash
201 | curl http://localhost:3000/hello
202 | ```
203 |
204 | You should see:
205 |
206 | ```json
207 | {"message": "Hello, Chanfana for itty-router!"}
208 | ```
209 |
210 | ### Exploring the OpenAPI Documentation (Itty Router)
211 |
212 | 1. **Navigate to the documentation URL.** Open your browser to `/docs` (e.g., `http://localhost:3000/docs`).
213 |
214 | 2. **Explore the Swagger UI.** You'll see the Swagger UI, now documenting your itty-router API endpoint.
215 |
216 | ## Using the Template
217 |
218 | For an even faster start, Chanfana provides a template that sets up a Cloudflare Worker project with OpenAPI documentation out of the box.
219 |
220 | **Create a new project using the template:**
221 |
222 | ```bash
223 | npm create cloudflare@latest -- --type openapi
224 | ```
225 |
226 | Follow the prompts to set up your project. This template includes Chanfana, Hono, and a basic endpoint structure, ready for you to expand upon.
227 |
228 | ---
229 |
230 | Congratulations! You've successfully set up Chanfana with both Hono and itty-router and explored the automatically generated OpenAPI documentation.
231 |
232 | Next, we'll dive deeper into the [**Core Concepts**](./core-concepts.md) of Chanfana to understand how it works and how to leverage its full potential.
233 |
--------------------------------------------------------------------------------
/docs/images/logo-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudflare/chanfana/main/docs/images/logo-icon.png
--------------------------------------------------------------------------------
/docs/images/logo-square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudflare/chanfana/main/docs/images/logo-square.png
--------------------------------------------------------------------------------
/docs/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudflare/chanfana/main/docs/images/logo.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "chanfana"
7 | image: images/logo-icon.png
8 | tagline: OpenAPI 3 and 3.1 schema generator and validator for Hono, itty-router and more!
9 | actions:
10 | - theme: brand
11 | text: Getting Started
12 | link: /getting-started
13 | - theme: alt
14 | text: Auto Endpoints
15 | link: /endpoints/auto/base
16 |
17 | features:
18 | - title: ✨ OpenAPI Schema Generation
19 | details: Automatically generate OpenAPI v3 & v3.1 compliant schemas from your TypeScript API endpoint definitions.
20 | link: /getting-started
21 | - title: ✅ Automatic Request Validation
22 | details: Enforce API contracts by automatically validating incoming requests against your defined schemas.
23 | link: /endpoints/request-validation
24 | - title: 🚀 Class-Based Endpoints
25 | details: Organize your API logic in a clean and structured way using class-based endpoints, promoting code reusability.
26 | link: /endpoints/defining-endpoints
27 | - title: 📦 Auto CRUD Endpoints
28 | details: Auto generate endpoints for common CRUD operations, reducing boilerplate.
29 | link: /endpoints/auto/base
30 | - title: ⌨️ TypeScript Type Inference
31 | details: Automatic type inference for request parameters, providing a type-safe and developer-friendly experience.
32 | link: /getting-started
33 | - title: 🔌 Router Adapters
34 | details: Seamlessly integrate Chanfana with popular routers like Hono and itty-router.
35 | link: /router-adapters
36 | ---
37 |
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 | # Welcome to Chanfana
2 |
3 | 
4 |
5 | Chanfana is a powerful and lightweight TypeScript library designed to effortlessly bring the benefits of OpenAPI to your web APIs. Built with modern JavaScript runtimes in mind, especially Cloudflare Workers, Chanfana provides schema generation and validation for popular routers like [Hono](https://github.com/honojs/hono) and [itty-router](https://github.com/kwhitley/itty-router), and is adaptable to many more.
6 |
7 | ## What is Chanfana?
8 |
9 | Chanfana, previously known as `itty-router-openapi`, is more than just an OpenAPI generator. It's a comprehensive toolkit that allows you to:
10 |
11 | * **Define your API contracts using TypeScript classes and Zod schemas.** Write your API logic and schema in one place, ensuring type safety and reducing boilerplate.
12 | * **Automatically generate OpenAPI v3 and v3.1 compliant schemas.** No more manual schema writing! Chanfana infers your API structure directly from your code.
13 | * **Enforce request validation.** Protect your API by automatically validating incoming requests against your defined schemas.
14 | * **Serve interactive API documentation.** Chanfana seamlessly integrates with Swagger UI and ReDoc to provide beautiful, interactive documentation for your API consumers.
15 | * **Extend your existing router applications.** Integrate Chanfana into your Hono or itty-router projects without rewriting your existing routes.
16 |
17 | Chanfana is built to be both powerful and lightweight, making it ideal for serverless environments like Cloudflare Workers, but it runs perfectly well in any JavaScript runtime.
18 |
19 | ## Key Features at a Glance
20 |
21 | * **OpenAPI v3 & v3.1 Schema Generation:** Supports the latest OpenAPI specifications.
22 | * **TypeScript First:** Fully written in TypeScript, providing excellent type safety and developer experience.
23 | * **Class-Based Endpoints:** Organize your API logic using clean, reusable classes.
24 | * **Automatic Type Inference:** Leverage TypeScript's power for automatic inference of request parameters (query, path, headers, body).
25 | * **Extensible Router Support:** Designed to work seamlessly with Hono, itty-router, and adaptable to other routers.
26 | * **Built-in Validation:** Automatic request validation based on your OpenAPI schemas.
27 | * **Interactive Documentation:** Effortless integration with Swagger UI and ReDoc for API documentation.
28 | * **Lightweight and Performant:** Optimized for serverless environments and fast runtimes.
29 | * **Production Ready:** Used in production at Cloudflare and powering public APIs like [Radar 2.0](https://developers.cloudflare.com/radar/).
30 |
31 | ## Why Choose Chanfana?
32 |
33 | In today's API-driven world, having a well-defined and documented API is crucial. Chanfana simplifies this process by:
34 |
35 | * **Reducing Development Time:** Automatic schema generation eliminates the tedious task of manually writing OpenAPI specifications.
36 | * **Improving API Quality:** Schema validation ensures that your API behaves as expected and reduces integration issues.
37 | * **Enhancing Developer Experience:** TypeScript and class-based endpoints provide a structured and enjoyable development workflow.
38 | * **Facilitating Collaboration:** OpenAPI documentation makes it easy for teams to understand and work with your APIs.
39 | * **Boosting Confidence:** Production readiness and usage in large-scale projects give you confidence in Chanfana's reliability.
40 |
41 | ## Who is Chanfana For?
42 |
43 | Chanfana is designed for developers who want to build robust, well-documented, and maintainable APIs, especially if you are:
44 |
45 | * **Building APIs with Hono or itty-router.** Chanfana provides first-class support for these popular routers.
46 | * **Developing serverless APIs on Cloudflare Workers or similar platforms.** Chanfana's lightweight nature is perfect for serverless environments.
47 | * **Seeking to adopt OpenAPI for your APIs.** Chanfana makes it easy to generate and utilize OpenAPI specifications.
48 | * **Looking for a TypeScript-first API development experience.** Chanfana leverages TypeScript to its fullest potential.
49 | * **Wanting to automate API documentation and validation.** Chanfana handles these tasks so you can focus on your API logic.
50 |
51 | Whether you are building a small personal project or a large-scale enterprise API, Chanfana can help you create better APIs, faster.
52 |
53 | ---
54 |
55 | Ready to get started? Let's move on to the [**Getting Started**](./getting-started.md) guide to set up your first Chanfana API!
56 |
--------------------------------------------------------------------------------
/docs/openapi-configuration-customization.md:
--------------------------------------------------------------------------------
1 | # OpenAPI Configuration and Customization
2 |
3 | Chanfana offers various options to configure and customize the generation of your OpenAPI document. These configurations are primarily set through the `RouterOptions` object when you initialize Chanfana using `fromHono` or `fromIttyRouter`. This section will explore the available configuration options and how to use them to tailor your OpenAPI specification.
4 |
5 | ## Configuring OpenAPI Document Generation
6 |
7 | The primary way to configure OpenAPI document generation in Chanfana is through the `RouterOptions` object, which you pass as the second argument to `fromHono` or `fromIttyRouter`.
8 |
9 | **Example: Configuring Router Options**
10 |
11 | ```typescript
12 | import { Hono } from 'hono';
13 | import { fromHono } from 'chanfana';
14 |
15 | const app = new Hono();
16 |
17 | const openapi = fromHono(app, {
18 | base: '/api/v1', // Base path for all API routes
19 | schema: {
20 | info: {
21 | title: 'My Awesome API',
22 | version: '2.0.0',
23 | description: 'This is the documentation for my awesome API.',
24 | termsOfService: 'https://example.com/terms/',
25 | contact: {
26 | name: 'API Support',
27 | url: 'https://example.com/support',
28 | email: 'support@example.com',
29 | },
30 | license: {
31 | name: 'Apache 2.0',
32 | url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
33 | },
34 | },
35 | servers: [
36 | { url: 'https://api.example.com/api/v1', description: 'Production server' },
37 | { url: 'http://localhost:3000/api/v1', description: 'Development server' },
38 | ],
39 | tags: [
40 | { name: 'users', description: 'Operations related to users' },
41 | { name: 'products', description: 'Operations related to products' },
42 | ],
43 | },
44 | docs_url: '/api/v1/docs',
45 | redoc_url: '/api/v1/redocs',
46 | openapi_url: '/api/v1/openapi.json',
47 | openapiVersion: '3.1', // or '3' for OpenAPI v3.0.3
48 | generateOperationIds: true,
49 | raiseUnknownParameters: false,
50 | });
51 |
52 | // ... register your endpoints using 'openapi' ...
53 | ```
54 |
55 | ## `RouterOptions`: Controlling OpenAPI Behavior
56 |
57 | The `RouterOptions` object accepts the following properties to customize Chanfana's behavior:
58 |
59 | ### `base`: Setting the Base Path for API Routes
60 |
61 | * **Type:** `string`
62 | * **Default:** `undefined`
63 |
64 | The `base` option allows you to set a base path for all API routes managed by Chanfana. If provided, this base path will be prepended to all route paths when generating the OpenAPI document. This is useful when your API is served under a specific path prefix (e.g., `/api/v1`).
65 |
66 | **Example:**
67 |
68 | ```typescript
69 | fromHono(app, { base: '/api/v1' });
70 |
71 | openapi.get('/users', UserListEndpoint); // OpenAPI path will be /api/v1/users
72 | openapi.get('/users/:userId', UserGetEndpoint); // OpenAPI path will be /api/v1/users/{userId}
73 | ```
74 |
75 | ### `schema`: Customizing OpenAPI Document Information
76 |
77 | * **Type:** `Partial`
78 | * **Default:** `{ info: { title: 'OpenAPI', version: '1.0.0' } }`
79 |
80 | The `schema` option allows you to provide a partial OpenAPI object configuration to customize the root-level properties of your generated OpenAPI document. This is where you can set metadata like API title, version, description, contact information, license, servers, and tags.
81 |
82 | **Properties you can customize within `schema`:**
83 |
84 | * **`info`:** (OpenAPI `Info` Object) Provides metadata about the API.
85 | * `title`: API title (required).
86 | * `version`: API version (required).
87 | * `description`: API description.
88 | * `termsOfService`: Terms of service URL.
89 | * `contact`: Contact information for the API.
90 | * `name`: Contact name.
91 | * `url`: Contact URL.
92 | * `email`: Contact email.
93 | * `license`: License information for the API.
94 | * `name`: License name.
95 | * `url`: License URL.
96 | * **`servers`:** (Array of OpenAPI `Server` Objects) Defines the API servers.
97 | * `url`: Server URL (required).
98 | * `description`: Server description (optional).
99 | * `variables`: Server variables (optional).
100 | * **`tags`:** (Array of OpenAPI `Tag` Objects) Defines tags for organizing operations in the OpenAPI document.
101 | * `name`: Tag name (required).
102 | * `description`: Tag description (optional).
103 | * `externalDocs`: External documentation for the tag (optional).
104 | * **`externalDocs`:** (OpenAPI `ExternalDocumentation` Object) Provides external documentation for the API as a whole.
105 | * `description`: Description of external documentation.
106 | * `url`: URL for external documentation (required).
107 |
108 | Refer to the [OpenAPI Specification](https://spec.openapis.org/oas/v3.1.0.html#oasObject) for details on these properties and their structure.
109 |
110 | ### `docs_url`, `redoc_url`, `openapi_url`: Configuring Documentation Endpoints
111 |
112 | * **Type:** `string | null`
113 | * **Default:**
114 | * `docs_url`: `"/docs"`
115 | * `redoc_url`: `"/redocs"`
116 | * `openapi_url`: `"/openapi.json"`
117 |
118 | These options control the URLs at which Chanfana serves the OpenAPI documentation and schema:
119 |
120 | * **`docs_url`:** URL path to serve Swagger UI. Set to `null` to disable Swagger UI documentation.
121 | * **`redoc_url`:** URL path to serve ReDoc UI. Set to `null` to disable ReDoc UI documentation.
122 | * **`openapi_url`:** URL path to serve the raw OpenAPI JSON schema. Set to `null` to disable serving the OpenAPI schema.
123 |
124 | Chanfana automatically creates routes in your router to serve these documentation UIs and the OpenAPI schema at the specified URLs.
125 |
126 | ### `openapiVersion`: Selecting OpenAPI Version (3 or 3.1)
127 |
128 | * **Type:** `"3" | "3.1"`
129 | * **Default:** `"3.1"`
130 |
131 | The `openapiVersion` option allows you to choose between generating OpenAPI v3.0.3 or v3.1.0 compliant schemas.
132 |
133 | * `"3"`: Generates OpenAPI v3.0.3 specification.
134 | * `"3.1"`: Generates OpenAPI v3.1.0 specification (default).
135 |
136 | Choose the version that best suits your needs and the tools you are using to consume your OpenAPI document. OpenAPI 3.1 is the latest version and offers some advantages, but OpenAPI 3.0.3 is still widely supported.
137 |
138 | ### `generateOperationIds`: Controlling Operation ID Generation
139 |
140 | * **Type:** `boolean`
141 | * **Default:** `true`
142 |
143 | The `generateOperationIds` option controls whether Chanfana should automatically generate `operationId` values for your OpenAPI operations.
144 |
145 | * `true`: (Default) Chanfana automatically generates `operationId` values based on the HTTP method and route path (e.g., `get_users_userId`).
146 | * `false`: Chanfana will **not** automatically generate `operationId` values. In this case, you **must** provide `operationId` explicitly in your endpoint's `schema` definition. If you don't provide `operationId` when `generateOperationIds` is `false`, Chanfana will throw an error.
147 |
148 | `operationId` values are used to uniquely identify operations in your OpenAPI document and are often used by code generation tools and API clients.
149 |
150 | ### `raiseUnknownParameters`: Strict Parameter Validation
151 |
152 | * **Type:** `boolean`
153 | * **Default:** `true`
154 |
155 | The `raiseUnknownParameters` option controls whether Chanfana should perform strict validation of request parameters (query, path, headers, body).
156 |
157 | * `true`: (Default) Strict validation is enabled. If the incoming request contains parameters that are **not** defined in your schema, Chanfana will consider it a validation error and return a `400 Bad Request` response. This is generally recommended for API robustness and security.
158 | * `false`: Strict validation is disabled. Chanfana will only validate the parameters that are defined in your schema and ignore any unknown parameters in the request. This can be useful for backward compatibility or when you want to allow clients to send extra parameters that your API might not explicitly handle.
159 |
160 | ## Customizing OpenAPI Schema Output
161 |
162 | While `RouterOptions` allows you to configure the overall OpenAPI document, you can also customize the schema output for individual endpoints and parameters using Zod's OpenAPI metadata features and Chanfana's parameter types (e.g., `Str`, `Num`, `Enumeration`).
163 |
164 | * **`describe()`:** Use Zod's `describe()` method to add descriptions to your schema fields, which will be included in the OpenAPI documentation.
165 | * **`openapi()`:** Use Zod's `openapi()` method to provide OpenAPI-specific metadata, such as examples, formats, and other schema extensions.
166 | * **Chanfana Parameter Type Options:** Options like `description`, `example`, `format`, `required`, and `default` in Chanfana's parameter types (`Str`, `Num`, `Enumeration`, etc.) are directly translated into OpenAPI schema properties.
167 |
168 | Refer to the [Zod-to-OpenAPI documentation](https://github.com/asteasolutions/zod-to-openapi) and [Zod documentation](https://zod.dev/) for more details on schema customization options.
169 |
170 | ## Serving OpenAPI Documentation (Swagger UI, ReDoc)
171 |
172 | Chanfana makes it easy to serve interactive API documentation using Swagger UI and ReDoc. By default, if you provide `docs_url` and `openapi_url` (or `redoc_url` and `openapi_url`) in `RouterOptions`, Chanfana automatically sets up routes to serve these documentation UIs.
173 |
174 | * **Swagger UI:** Served at the URL specified in `docs_url` (default: `/docs`). Provides an interactive, visual interface for exploring and testing your API.
175 | * **ReDoc:** Served at the URL specified in `redoc_url` (default: `/redocs`). Provides a clean, three-panel documentation layout, often preferred for its readability.
176 |
177 | Both Swagger UI and ReDoc are served as static HTML pages that fetch your OpenAPI schema from the `openapi_url` (default: `/openapi.json`) and render the documentation dynamically.
178 |
179 | You can customize the URLs for these documentation endpoints using the `docs_url`, `redoc_url`, and `openapi_url` options in `RouterOptions`, or disable serving specific documentation UIs by setting their corresponding URL option to `null`.
180 |
181 | ---
182 |
183 | By understanding and utilizing these OpenAPI configuration and customization options, you can fine-tune Chanfana to generate OpenAPI documents that accurately represent your API, meet your documentation requirements, and enhance the developer experience for your API consumers.
184 |
--------------------------------------------------------------------------------
/docs/router-adapters.md:
--------------------------------------------------------------------------------
1 | # Adapters: Integrating with Routers
2 |
3 | Chanfana is designed to be router-agnostic, allowing you to integrate it with various JavaScript web routers. Adapters are the bridge that connects Chanfana's OpenAPI functionality to specific router implementations. Currently, Chanfana provides adapters for [Hono](https://github.com/honojs/hono) and [itty-router](https://github.com/kwhitley/itty-router), two popular choices for modern JavaScript runtimes, especially Cloudflare Workers.
4 |
5 | ## Introduction to Adapters
6 |
7 | Adapters in Chanfana serve the following key purposes:
8 |
9 | * **Router Integration:** They provide specific functions and classes to seamlessly integrate Chanfana's OpenAPI schema generation, validation, and documentation features into the routing mechanism of a particular router library.
10 | * **Request Handling Abstraction:** Adapters abstract away the router-specific details of request and response handling, allowing Chanfana's core logic to remain router-independent.
11 | * **Middleware Compatibility:** They ensure compatibility with the middleware ecosystem of the target router, allowing you to use existing middleware alongside Chanfana's features.
12 |
13 | Chanfana provides two main adapters:
14 |
15 | * **Hono Adapter (`fromHono`):** For integrating with Hono applications.
16 | * **Itty Router Adapter (`fromIttyRouter`):** For integrating with itty-router applications.
17 |
18 | ## Hono Adapter (`fromHono`)
19 |
20 | The `fromHono` adapter is used to extend your Hono applications with Chanfana's OpenAPI capabilities. It provides the `fromHono` function and the `HonoOpenAPIRouterType` type.
21 |
22 | ### Setting up Chanfana with Hono
23 |
24 | To integrate Chanfana into your Hono application, you use the `fromHono` function.
25 |
26 | **Example: Basic Hono Integration**
27 |
28 | ```typescript
29 | import { Hono, type Context } from 'hono';
30 | import { fromHono, OpenAPIRoute } from 'chanfana';
31 | import { z } from 'zod';
32 |
33 | export type Env = {
34 | // Example bindings
35 | DB: D1Database
36 | BUCKET: R2Bucket
37 | }
38 | export type AppContext = Context<{ Bindings: Env }>
39 |
40 | class MyEndpoint extends OpenAPIRoute {
41 | schema = {
42 | responses: {
43 | "200": { description: 'Success' },
44 | },
45 | };
46 | async handle(c: AppContext) {
47 | return { message: 'Hello from Hono!' };
48 | }
49 | }
50 |
51 | const app = new Hono<{ Bindings: Env }>();
52 |
53 | // Initialize Chanfana for Hono using fromHono
54 | const openapi = fromHono(app);
55 |
56 | // Register your OpenAPIRoute endpoints using the openapi instance
57 | openapi.get('/hello', MyEndpoint);
58 |
59 | export default app;
60 | ```
61 |
62 | **Explanation:**
63 |
64 | 1. **Import `fromHono`:** Import the `fromHono` function from `chanfana/adapters/hono`.
65 | 2. **Create a Hono App:** Create a standard Hono application instance using `new Hono()`.
66 | 3. **Initialize Chanfana with `fromHono`:** Call `fromHono(app, options)` to initialize Chanfana for your Hono app.
67 | * The first argument is your Hono application instance (`app`).
68 | * The second argument is an optional `RouterOptions` object to configure Chanfana (e.g., `openapi_url`, `docs_url`).
69 | 4. **Use `openapi` to Register Routes:** Use the `openapi` instance (returned by `fromHono`) to register your `OpenAPIRoute` classes for different HTTP methods (`get`, `post`, `put`, `delete`, `patch`, `all`, `on`, `route`). These methods work similarly to Hono's routing methods but are extended with Chanfana's OpenAPI features.
70 |
71 | ### Extending Existing Hono Applications
72 |
73 | `fromHono` is designed to be non-intrusive and can be easily integrated into existing Hono applications without requiring major code changes. You can gradually add OpenAPI documentation and validation to your existing routes by converting your route handlers to `OpenAPIRoute` classes and registering them using the `openapi` instance.
74 |
75 | **Example: Extending an Existing Hono App**
76 |
77 | ```typescript
78 | import { Hono, type Context } from 'hono';
79 | import { fromHono, OpenAPIRoute } from 'chanfana';
80 |
81 | export type Env = {
82 | // Example bindings, use your own
83 | DB: D1Database
84 | BUCKET: R2Bucket
85 | }
86 | export type AppContext = Context<{ Bindings: Env }>
87 |
88 | const app = new Hono<{ Bindings: Env }>();
89 |
90 | // Existing Hono route (without OpenAPI)
91 | app.get('/legacy-route', (c) => c.text('This is a legacy route'));
92 |
93 | // Initialize Chanfana
94 | const openapi = fromHono(app);
95 |
96 | // New OpenAPI-documented route
97 | class NewEndpoint extends OpenAPIRoute {
98 | schema = {
99 | responses: {
100 | "200": { description: 'Success' },
101 | },
102 | };
103 | async handle(c: AppContext) {
104 | return { message: 'This is a new OpenAPI route!' };
105 | }
106 | }
107 | openapi.get('/new-route', NewEndpoint);
108 |
109 | export default app;
110 | ```
111 |
112 | In this example, we have an existing Hono route `/legacy-route` that is not managed by Chanfana. We then initialize Chanfana using `fromHono` and register a new route `/new-route` using `OpenAPIRoute` and `openapi.get()`. Both routes will coexist in the same Hono application. Only the `/new-route` will have OpenAPI documentation and validation.
113 |
114 | ### `HonoOpenAPIRouterType`
115 |
116 | The `fromHono` function returns an object of type `HonoOpenAPIRouterType`. This type is an intersection of `Hono` and `OpenAPIRouterType`, extending the standard Hono application instance with Chanfana's OpenAPI routing methods and properties.
117 |
118 | **Key extensions provided by `HonoOpenAPIRouterType`:**
119 |
120 | * **OpenAPI Routing Methods:** `get()`, `post()`, `put()`, `delete()`, `patch()`, `all()`, `on()`, `route()` methods are extended to handle `OpenAPIRoute` classes and automatically register them for OpenAPI documentation and validation.
121 | * **`original` Property:** Provides access to the original Hono application instance.
122 | * **`options` Property:** Provides access to the `RouterOptions` passed to `fromHono`.
123 | * **`registry` Property:** Provides access to the OpenAPI registry used by Chanfana to collect schema definitions.
124 | * **`schema()` Method:** Returns the generated OpenAPI schema as a JavaScript object.
125 |
126 | ### Example with Hono
127 |
128 | Refer to the [Quick Start with Hono](./getting-started.md#quick-start-with-hono) section for a complete example of setting up Chanfana with Hono and creating a basic endpoint.
129 |
130 | ## Itty Router Adapter (`fromIttyRouter`)
131 |
132 | The `fromIttyRouter` adapter is used to integrate Chanfana with [itty-router](https://github.com/kwhitley/itty-router) applications. It provides the `fromIttyRouter` function and the `IttyRouterOpenAPIRouterType` type.
133 |
134 | ### Setting up Chanfana with Itty Router
135 |
136 | To integrate Chanfana into your itty-router application, you use the `fromIttyRouter` function.
137 |
138 | **Example: Basic Itty Router Integration**
139 |
140 | ```typescript
141 | import { Router } from 'itty-router';
142 | import { fromIttyRouter, OpenAPIRoute } from 'chanfana';
143 | import { z } from 'zod';
144 |
145 | class MyEndpoint extends OpenAPIRoute {
146 | schema = {
147 | responses: {
148 | "200": { description: 'Success' },
149 | },
150 | };
151 | async handle(request: Request, env, ctx) {
152 | return { message: 'Hello from itty-router!' };
153 | }
154 | }
155 |
156 | const router = Router();
157 |
158 | // Initialize Chanfana for itty-router using fromIttyRouter
159 | const openapi = fromIttyRouter(router);
160 |
161 | // Register your OpenAPIRoute endpoints using the openapi instance
162 | openapi.get('/hello', MyEndpoint);
163 |
164 | // Add a default handler for itty-router (required)
165 | router.all('*', () => new Response("Not Found.", { status: 404 }));
166 |
167 | export const fetch = router.handle; // Export the fetch handler
168 | ```
169 |
170 | **Explanation:**
171 |
172 | 1. **Import `fromIttyRouter`:** Import the `fromIttyRouter` function from `chanfana/adapters/ittyRouter`.
173 | 2. **Create an Itty Router Instance:** Create an itty-router instance using `Router()`.
174 | 3. **Initialize Chanfana with `fromIttyRouter`:** Call `fromIttyRouter(router, options)` to initialize Chanfana for your itty-router instance.
175 | * The first argument is your itty-router instance (`router`).
176 | * The second argument is the optional `RouterOptions` object.
177 | 4. **Use `openapi` to Register Routes:** Use the `openapi` instance to register your `OpenAPIRoute` classes for different HTTP methods (`get`, `post`, `put`, `delete`, `patch`, `all`). These methods extend itty-router's routing methods with Chanfana's OpenAPI features.
178 | 5. **Add Default Handler:** Itty-router requires a default handler to be registered using `router.all('*', ...)`. This is necessary for itty-router to function correctly.
179 | 6. **Export `fetch` Handler:** Export the `router.handle` function as `fetch`. This is the standard way to export an itty-router application for Cloudflare Workers or other Fetch API environments.
180 |
181 | ### Extending Existing Itty Router Applications
182 |
183 | Similar to Hono, `fromIttyRouter` can be integrated into existing itty-router applications without major refactoring. You can gradually add OpenAPI documentation and validation to your routes.
184 |
185 | ### `IttyRouterOpenAPIRouterType`
186 |
187 | The `fromIttyRouter` function returns an object of type `IttyRouterOpenAPIRouterType`. This type extends the original itty-router instance with Chanfana's OpenAPI routing methods and properties, similar to `HonoOpenAPIRouterType`.
188 |
189 | ### Example with Itty Router
190 |
191 | Refer to the [Quick Start with Itty Router](./getting-started.md#quick-start-with-itty-router) section for a complete example of setting up Chanfana with itty-router and creating a basic endpoint.
192 |
193 | ## Choosing the Right Adapter
194 |
195 | Choose the adapter that corresponds to the web router you are using in your project:
196 |
197 | * Use `fromHono` for Hono applications.
198 | * Use `fromIttyRouter` for itty-router applications.
199 |
200 | If you are using a different router, you might need to create a custom adapter. Chanfana's architecture is designed to be extensible, and creating a new adapter is possible, although it might require a deeper understanding of Chanfana's internals and the target router's API.
201 |
--------------------------------------------------------------------------------
/docs/troubleshooting-and-faq.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting and FAQ
2 |
3 | This section provides solutions to common issues you might encounter while using Chanfana, frequently asked questions, debugging tips, and resources for getting help and support.
4 |
5 | ## Common Issues and Solutions
6 |
7 | **1. "TypeError: Router.get is not a function" or similar errors when using `fromHono` or `fromIttyRouter`:**
8 |
9 | * **Cause:** You might be calling `fromHono` or `fromIttyRouter` on an object that is not a valid Hono or itty-router router instance.
10 | * **Solution:** Ensure that you are passing a valid router instance to `fromHono` or `fromIttyRouter`. Double-check your router initialization code:
11 |
12 | ```typescript
13 | // Hono example:
14 | import { Hono } from 'hono';
15 | const app = new Hono(); // Correct: Initialize Hono router
16 | const openapi = fromHono(app, {/* options */});
17 |
18 | // Itty-router example:
19 | import { Router } from 'itty-router';
20 | const router = Router(); // Correct: Initialize itty-router
21 | const openapi = fromIttyRouter(router, {/* options */});
22 | ```
23 |
24 | **2. OpenAPI documentation is not showing up at `/docs` or `/openapi.json`:**
25 |
26 | * **Cause 1:** You haven't registered any routes with Chanfana's `openapi` instance.
27 | * **Solution:** Make sure you are registering your `OpenAPIRoute` classes using methods like `openapi.get()`, `openapi.post()`, etc., and **not** directly on the original router instance (`app.get()` in Hono or `router.get()` in itty-router after initialization with Chanfana).
28 |
29 | ```typescript
30 | // Correct: Register route with Chanfana's openapi instance
31 | openapi.get('/my-endpoint', MyEndpointClass);
32 |
33 | // Incorrect: Registering directly on the Hono app instance (OpenAPI not enabled for this route)
34 | app.get('/another-endpoint', () => new Response("Hello"));
35 | ```
36 |
37 | * **Cause 2:** You have set `docs_url`, `redoc_url`, or `openapi_url` to `null` in `RouterOptions`.
38 | * **Solution:** Check your `RouterOptions` and ensure that `docs_url`, `redoc_url`, and `openapi_url` are set to valid URL paths (strings) if you want to enable documentation UIs and schema endpoints.
39 |
40 | * **Cause 3:** Your application is not running or is not accessible at the expected address and port.
41 | * **Solution:** Verify that your application is running correctly and is accessible in your browser or using `curl` at the URL where you expect to see the documentation.
42 |
43 | **3. Request validation errors are not being handled as expected:**
44 |
45 | * **Cause 1:** You are not defining request schemas in your `OpenAPIRoute` classes.
46 | * **Solution:** Ensure that you have defined request schemas (e.g., `schema.request.body`, `schema.request.query`, etc.) in your `OpenAPIRoute` classes for the endpoints where you want request validation to be performed.
47 |
48 | * **Cause 2:** You are catching and handling `ZodError` exceptions manually in your `handle` method, potentially overriding Chanfana's default error handling.
49 | * **Solution:** In most cases, you should **not** manually catch `ZodError` exceptions within your `handle` method. Let Chanfana automatically handle validation errors and return `400 Bad Request` responses. Only catch exceptions if you need to perform custom error handling logic for specific error types other than validation errors.
50 |
51 | **4. "TypeError: Cannot read properties of undefined (reading 'schema')" or similar errors related to `_meta`:**
52 |
53 | * **Cause:** You are using auto endpoints (`CreateEndpoint`, `ReadEndpoint`, etc.) without properly defining the `_meta` property in your endpoint class.
54 | * **Solution:** When using auto endpoints, you **must** define the `_meta` property and assign a valid `Meta` object to it. Ensure that your `Meta` object includes the `model` property with `schema`, `primaryKeys`, and `tableName` (if applicable).
55 |
56 | ```typescript
57 | class MyCreateEndpoint extends CreateEndpoint {
58 | _meta = { // Correct: Define _meta property
59 | model: {
60 | schema: MyDataModel,
61 | primaryKeys: ['id'],
62 | tableName: 'my_table',
63 | },
64 | };
65 | { /* ... */ };
66 | // ...
67 | }
68 | ```
69 |
70 | **5. OpenAPI schema is missing descriptions or examples:**
71 |
72 | * **Cause:** You have not provided descriptions or examples in your Zod schemas or Chanfana parameter types.
73 | * **Solution:** Use Zod's `describe()` method and Chanfana parameter type options like `description` and `example` to add metadata to your schemas. This metadata is used to generate more informative OpenAPI documentation.
74 |
75 | ```typescript
76 | const nameSchema = Str({ description: 'User name', example: 'John Doe' }); // Add description and example
77 | const ageSchema = Int({ description: 'User age' }); // Add description
78 | ```
79 |
80 | **6. D1 endpoints are not working, "Binding 'DB' is not defined in worker" error:**
81 |
82 | * **Cause:** You have not correctly configured the D1 database binding in your `wrangler.toml` file, or the binding name in your code (`dbName = 'DB'`) does not match the binding name in `wrangler.toml`.
83 | * **Solution:**
84 | * Verify your `wrangler.toml` file and ensure that you have a `[[d1_databases]]` section with a `binding` name (e.g., `binding = "DB"`).
85 | * Make sure that the `dbName` property in your D1 endpoint class matches the `binding` name in `wrangler.toml` (case-sensitive).
86 | * Ensure that you have deployed your Cloudflare Worker or are running it in a local environment where the D1 binding is correctly set up.
87 |
88 | ## Frequently Asked Questions (FAQ)
89 |
90 | **Q: Can I use Chanfana with routers other than Hono and itty-router?**
91 |
92 | **A:** Chanfana is designed to be router-agnostic in principle. While it provides official adapters for Hono and itty-router, you can potentially create custom adapters for other routers. However, creating a custom adapter might require a deeper understanding of Chanfana's internals and the API of the target router.
93 |
94 | **Q: Does Chanfana support OpenAPI 3.1?**
95 |
96 | **A:** Yes, Chanfana fully supports OpenAPI 3.1 (which is the default) and also OpenAPI 3.0.3. You can select the OpenAPI version using the `openapiVersion` option in `RouterOptions`.
97 |
98 | **Q: Can I customize the generated OpenAPI document beyond the `RouterOptions`?**
99 |
100 | **A:** Yes, you can customize the OpenAPI schema output extensively using Zod's `describe()` and `openapi()` methods, as well as Chanfana's parameter type options. For more advanced customizations or modifications to the generated OpenAPI document structure, you might need to extend or modify Chanfana's core classes (which is generally not recommended unless you have a deep understanding of the library).
101 |
102 | **Q: Is Chanfana suitable for production APIs?**
103 |
104 | **A:** Yes, Chanfana is considered stable and production-ready. It is used in production at Cloudflare and powers public APIs like [Radar 2.0](https://developers.cloudflare.com/radar/).
105 |
106 | **Q: Does Chanfana support authentication and authorization?**
107 |
108 | **A:** Chanfana itself does not provide built-in authentication or authorization mechanisms. However, it is designed to work seamlessly with middleware and custom logic for implementing authentication and authorization in your API endpoints. Refer to the [Examples and Recipes](./examples-and-recipes.md) section for a basic example of API key authentication.
109 |
110 | **Q: How can I handle different content types (e.g., XML, plain text) in request and responses?**
111 |
112 | **A:** Chanfana primarily focuses on JSON APIs and provides `contentJson` for simplifying JSON content handling. For handling other content types like XML or plain text, you might need to:
113 |
114 | * Manually define OpenAPI `content` objects in your schema without using `contentJson`.
115 | * Use Zod schemas that are appropriate for the data format (e.g., `z.string()` for plain text).
116 | * Handle request parsing and response serialization for non-JSON content types within your endpoint `handle` methods using router-specific or Fetch API methods.
117 |
118 | *(Future versions of Chanfana might introduce more utilities for handling different content types.)*
119 |
120 | ## Debugging Tips
121 |
122 | * **Check Browser Console:** When using Swagger UI or ReDoc, inspect the browser's developer console for any JavaScript errors or warnings that might indicate issues with the documentation rendering or OpenAPI schema.
123 | * **Validate OpenAPI Schema:** Use online OpenAPI validators (e.g., Swagger Editor, ReDoc online validator) to validate your generated `openapi.json` schema file for any structural or syntax errors.
124 | * **Verbose Logging:** Add `console.log` statements in your endpoint `handle` methods and middleware to trace the request flow, data validation, and error handling logic.
125 | * **Simplify and Isolate:** If you encounter complex issues, try to simplify your endpoint definitions and isolate the problematic part of your code to narrow down the source of the error.
126 | * **Example Projects:** Refer to the example projects and code snippets in the documentation and Chanfana's repository for working examples and best practices.
127 |
128 | ## Getting Help and Support
129 |
130 | * **Chanfana GitHub Repository:** [https://github.com/cloudflare/chanfana/](https://github.com/cloudflare/chanfana/) - Check the repository for issues, discussions, and updates.
131 | * **Cloudflare Developer Community:** [https://community.cloudflare.com/](https://community.cloudflare.com/) - Ask questions and seek help from the Cloudflare developer community.
132 | * **Radar Discord Channel:** [https://discord.com/channels/595317990191398933/1035553707116478495](https://discord.com/channels/595317990191398933/1035553707116478495) - Join the Radar Discord channel for discussions and support related to Chanfana and Radar API development.
133 | * **Report Issues:** If you encounter bugs or have feature requests, please open an issue in the Chanfana GitHub repository.
134 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chanfana",
3 | "version": "2.8.0",
4 | "description": "OpenAPI 3 and 3.1 schema generator and validator for Hono, itty-router and more!",
5 | "main": "dist/index.js",
6 | "module": "dist/index.mjs",
7 | "types": "dist/index.d.ts",
8 | "files": [
9 | "dist",
10 | "LICENSE",
11 | "README.md"
12 | ],
13 | "scripts": {
14 | "prepare": "husky",
15 | "build": "rm -rf dist/ && tsup src/index.ts --format cjs,esm --dts --config tsconfig.json --external Hono",
16 | "lint": "npx @biomejs/biome check src/ tests/ || (npx @biomejs/biome check --write src/ tests/; exit 1)",
17 | "test": "vitest run --root tests",
18 | "docs:deploy": "npm run docs:build && wrangler pages deploy .vitepress/dist/ --project-name chanfana --branch main",
19 | "docs:dev": "vitepress dev",
20 | "docs:build": "vitepress build && cp .vitepress/_redirects .vitepress/dist/",
21 | "docs:preview": "vitepress preview"
22 | },
23 | "keywords": [
24 | "cloudflare",
25 | "worker",
26 | "workers",
27 | "serverless",
28 | "cloudflare workers",
29 | "router",
30 | "openapi",
31 | "swagger",
32 | "openapi generator",
33 | "cf",
34 | "optional",
35 | "middleware",
36 | "parameters",
37 | "typescript",
38 | "npm",
39 | "package",
40 | "cjs",
41 | "esm",
42 | "umd",
43 | "typed"
44 | ],
45 | "author": "Gabriel Massadas (https://github.com/g4brym)",
46 | "license": "MIT",
47 | "homepage": "https://chanfana.pages.dev",
48 | "repository": {
49 | "type": "git",
50 | "url": "https://github.com/cloudflare/chanfana.git"
51 | },
52 | "bugs": {
53 | "url": "https://github.com/cloudflare/chanfana/issues"
54 | },
55 | "devDependencies": {
56 | "@biomejs/biome": "1.9.4",
57 | "@cloudflare/vitest-pool-workers": "^0.6.0",
58 | "@cloudflare/workers-types": "4.20250109.0",
59 | "@types/js-yaml": "^4.0.9",
60 | "@types/node": "22.10.5",
61 | "@types/service-worker-mock": "^2.0.1",
62 | "hono": "4.6.16",
63 | "husky": "9.1.7",
64 | "itty-router": "5.0.18",
65 | "tsup": "8.3.5",
66 | "typescript": "5.7.3",
67 | "vitepress": "^1.6.3",
68 | "vitest": "2.1.8",
69 | "vitest-openapi": "^1.0.3",
70 | "wrangler": "3.101.0"
71 | },
72 | "dependencies": {
73 | "@asteasolutions/zod-to-openapi": "^7.2.0",
74 | "js-yaml": "^4.1.0",
75 | "openapi3-ts": "^4.4.0",
76 | "zod": "^3.23.8"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/adapters/hono.ts:
--------------------------------------------------------------------------------
1 | import type { Hono, Input } from "hono";
2 | import type {
3 | BlankInput,
4 | Env,
5 | H,
6 | HandlerResponse,
7 | MergePath,
8 | MergeSchemaPath,
9 | Schema,
10 | ToSchema,
11 | TypedResponse,
12 | } from "hono/types";
13 | import { OpenAPIHandler, type OpenAPIRouterType } from "../openapi";
14 | import type { OpenAPIRoute } from "../route";
15 | import type { RouterOptions } from "../types";
16 |
17 | type MergeTypedResponse = T extends Promise
18 | ? T2 extends TypedResponse
19 | ? T2
20 | : TypedResponse
21 | : T extends TypedResponse
22 | ? T
23 | : TypedResponse;
24 |
25 | const HIJACKED_METHODS = new Set(["basePath", "on", "route", "delete", "get", "patch", "post", "put", "all"]);
26 |
27 | export type HonoOpenAPIRouterType<
28 | E extends Env = Env,
29 | S extends Schema = {},
30 | BasePath extends string = "/",
31 | > = OpenAPIRouterType> & {
32 | on(method: string, path: string, endpoint: typeof OpenAPIRoute): Hono["on"];
33 | on(method: string, path: string, router: Hono): Hono["on"];
34 |
35 | route(
36 | path: SubPath,
37 | app: HonoOpenAPIRouterType,
38 | ): HonoOpenAPIRouterType> | S, BasePath>;
39 |
40 | all