# Defining Endpoints

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.

## Understanding the `OpenAPIRoute` Class

`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.

**Key Components of an `OpenAPIRoute` Class:**

*   **`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.

*   **`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).

*   **`getValidatedData<S = any>()` 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.

## Basic Endpoint Structure

Here's the basic structure of an `OpenAPIRoute` class:

```typescript
import { OpenAPIRoute } from 'chanfana';
import { z } from 'zod';
import { type Context } from 'hono';

class MyEndpoint extends OpenAPIRoute {
    schema = {
        // Define your OpenAPI schema here (request and responses)
        request: {
            // ... request schema (optional)
        },
        responses: {
            // ... response schema (required)
        },
    };

    async handle(c: Context) {
        // Implement your endpoint logic here
        // Access validated data using this.getValidatedData()
        // Return a Response, Promise<Response>, or a plain object
    }
}
```

## Defining the `schema`

The `schema` property is where you define the OpenAPI contract for your endpoint. Let's break down its components:

### Request Schema (`request`)

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:

*   **`body`:**  Schema for the request body. Typically used for `POST`, `PUT`, and `PATCH` requests. You'll often use `contentJson` to define JSON request bodies.
*   **`query`:** Schema for query parameters in the URL. Use `z.object({})` to define the structure of query parameters.
*   **`params`:** Schema for path parameters in the URL path. Use `z.object({})` to define the structure of path parameters.
*   **`headers`:** Schema for HTTP headers. Use `z.object({})` to define the structure of headers.

**Example: Request Schema with Body and Query Parameters**

```typescript
import { OpenAPIRoute, contentJson } from 'chanfana';
import { z } from 'zod';
import { type Context } from 'hono';

class ExampleEndpoint extends OpenAPIRoute {
    schema = {
        request: {
            body: contentJson(z.object({
                name: z.string().min(3),
                email: z.email(),
            })),
            query: z.object({
                page: z.number().int().min(1).default(1),
                pageSize: z.number().int().min(1).max(100).default(20),
            }),
        },
        responses: {
            // ... response schema
        },
    };

    async handle(c: Context) {
        const data = await this.getValidatedData<typeof this.schema>();
        // data.body will be of type { name: string, email: string }
        // data.query will be of type { page: number, pageSize: number }
        console.log("Validated Body:", data.body);
        console.log("Validated Query:", data.query);
        return { message: 'Request Validated!' };
    }
}
```

### Response Schema (`responses`)

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.

Each response definition should include:

*   **`description`:** A human-readable description of the response.
*   **`content`:** (Optional) Defines the response body content. You'll often use `contentJson` to define JSON response bodies.

**Example: Response Schema with Success and Error Responses**

```typescript
import { OpenAPIRoute, contentJson, InputValidationException } from 'chanfana';
import { z } from 'zod';
import { type Context } from 'hono';

class AnotherEndpoint extends OpenAPIRoute {
    schema = {
        responses: {
            "200": {
                description: 'Successful operation',
                ...contentJson(z.object({
                    status: z.string().default("success"),
                    data: z.object({ id: z.number() }),
                })),
            },
            ...InputValidationException.schema(),
            "500": {
                description: 'Internal Server Error',
                ...contentJson(z.object({
                    status: z.string().default("error"),
                    message: z.string(),
                })),
            },
        },
    };

    async handle(c: Context) {
        // ... your logic ...
        const success = Math.random() > 0.5;
        if (success) {
            return { status: "success", data: { id: 123 } };
        } else {
            throw new Error("Something went wrong!"); // Example of throwing an error
        }
    }
}
```

## Implementing the `handle` Method

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.

**Inside the `handle` method, you typically:**

1.  **Access Validated Data:** Use `this.getValidatedData<typeof this.schema>()` 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.
2.  **Implement Business Logic:** Perform the operations your endpoint is designed for (e.g., database interactions, calculations, external API calls).
3.  **Return a Response:**
    *   **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).
    *   **Return a Promise that resolves to a `Response`:** If your logic is asynchronous, return a Promise that resolves to a `Response`.
    *   **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.

**Example: `handle` Method Logic**

```typescript
import { OpenAPIRoute, contentJson } from 'chanfana';
import { z } from 'zod';
import { type Context } from 'hono';

class UserEndpoint extends OpenAPIRoute {
    schema = {
        request: {
            params: z.object({
                userId: z.string(),
            }),
        },
        responses: {
            "200": {
                description: 'User details retrieved',
                ...contentJson(z.object({
                    id: z.string(),
                    name: z.string(),
                    email: z.string(),
                })),
            },
            // ... error responses
        },
    };

    async handle(c: Context) {
        const data = await this.getValidatedData<typeof this.schema>();
        const userId = data.params.userId;

        // Simulate fetching user data (replace with actual database/service call)
        const user = {
            id: userId,
            name: `User ${userId}`,
            email: `user${userId}@example.com`,
        };

        return { ...user }; // Return a plain object, Chanfana will convert to JSON
    }
}
```

## Accessing Validated Data with `getValidatedData()`

The `getValidatedData<S = any>()` method is crucial for accessing the validated request data within your `handle` method.

**Key features of `getValidatedData()`:**

*   **Type Safety:**  By using `getValidatedData<typeof this.schema>()`, 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.
*   **Asynchronous Operation:** `getValidatedData()` is an asynchronous method because it performs request validation. You need to `await` its result before accessing the validated data.
*   **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.

**Example: Using `getValidatedData()`**

```typescript
import { type Context } from 'hono';

async handle(c: Context) {
    const data = await this.getValidatedData<typeof this.schema>();
    const userName = data.body.name; // TypeScript knows data.body.name is a string
    const pageNumber = data.query.page; // TypeScript knows data.query.page is a number

    // ... use validated data in your logic ...
}
```

## Example: A Simple Greeting Endpoint

Let's put it all together with a simple greeting endpoint that takes a name as a query parameter and returns a personalized greeting.

```typescript
import { Hono, type Context } from 'hono';
import { fromHono, OpenAPIRoute, contentJson } from 'chanfana';
import { z } from 'zod';

export type Env = {
    // Example bindings, use your own
    DB: D1Database
    BUCKET: R2Bucket
}
export type AppContext = Context<{ Bindings: Env }>

class GreetingEndpoint extends OpenAPIRoute {
    schema = {
        request: {
            query: z.object({
                name: z.string().min(1).describe("Name to greet"),
            }),
        },
        responses: {
            "200": {
                description: 'Greeting message',
                ...contentJson(z.object({
                    greeting: z.string(),
                })),
            },
        },
    };

    async handle(c: AppContext) {
        const data = await this.getValidatedData<typeof this.schema>();
        const name = data.query.name;
        return { greeting: `Hello, ${name}! Welcome to Chanfana.` };
    }
}

const app = new Hono<{ Bindings: Env }>();
const openapi = fromHono(app);
openapi.get('/greet', GreetingEndpoint);

export default app;
```

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.

---

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).
