Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,9 @@ const createItem = create("/item", {

### Open API

Better Call by default generate open api schema for the endpoints and exposes it on `/api/reference` path using scalar. By default, if you're using `zod` it'll be able to generate `body` and `query` schema.
Better Call by default generates an OpenAPI schema for the endpoints and exposes it on the `/api/reference` path using scalar. `body` and `query` schemas are generated automatically for any schema library that implements the [Standard JSON Schema](https://standardschema.dev/json-schema) interface (Zod `>= 4.2`, ArkType `>= 2.1.28`, and others); libraries without it fall back to a generic object schema and can be described explicitly via `metadata.openapi` (see below).

Every method of an endpoint — including `PUT`, `PATCH`, and `DELETE`, and multiple methods on the same path — is documented, and `:param` route segments are rendered as OpenAPI `{param}` path parameters.

```ts
import { createEndpoint, createRouter } from "better-call"
Expand Down Expand Up @@ -844,9 +846,32 @@ const createItem = createEndpoint("/item/:id", {
})
```

#### Authentication

Better Call is auth-agnostic and does **not** assert any security scheme by default. To document authentication in an OpenAPI-standard way, declare your security schemes and a document-level requirement on the router. Per-endpoint requirements can be set via `metadata.openapi.security` and override the document-level default for that operation.

```ts
const router = createRouter({
createItem
}, {
openapi: {
// Exposed under components.securitySchemes
securitySchemes: {
sessionCookie: {
type: "apiKey",
in: "cookie",
name: "better-auth.session_token"
}
},
// Applied to every operation unless overridden per-endpoint
security: [{ sessionCookie: [] }]
}
})
```

#### Configuration

You can configure the open api schema by passing the `openapi` option to the router.
You can configure the OpenAPI schema by passing the `openapi` option to the router.

```ts
const router = createRouter({
Expand All @@ -855,9 +880,21 @@ const router = createRouter({
openapi: {
disabled: false, //default false
path: "/api/reference", //default /api/reference
scalar: {
// OpenAPI Info Object (defaults to { title: "API Reference", version: "1.0.0" })
info: {
title: "My API",
version: "1.0.0",
description: "My API Description"
},
// OpenAPI Server Objects
servers: [{ url: "https://api.example.com" }],
// See "Authentication" above
security: [{ sessionCookie: [] }],
securitySchemes: {
sessionCookie: { type: "apiKey", in: "cookie", name: "better-auth.session_token" }
},
scalar: {
title: "My API",
description: "My API Description",
theme: "dark" //default saturn
}
Expand Down
6 changes: 3 additions & 3 deletions packages/better-call/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "better-call",
"version": "2.0.5",
"name": "@futonic/better-call",
"version": "2.0.6",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/better-auth/better-call.git",
"url": "git+https://github.com/isaacwasserman/better-call.git",
"directory": "packages/better-call"
},
"copyright": "Copyright (C) 2025 Bereket Engida",
Expand Down
11 changes: 10 additions & 1 deletion packages/better-call/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type {
ResolveMethod,
ResolveQuery,
} from "./types";
import { isRequest } from "./utils";
import { getBody, isRequest } from "./utils";
import { runValidation } from "./validator";

export type EndpointContext<
Expand Down Expand Up @@ -235,6 +235,15 @@ export const createInternalContext = async (
const headers = new Headers();
let responseStatus: Status | undefined;

if (
context.body === undefined &&
options.body &&
"request" in context &&
isRequest(context.request)
) {
context.body = await getBody(context.request);
}

const { data, error } = await runValidation(options as any, context as any);
if (error) {
throw new ValidationError(error.message, error.issues);
Expand Down
27 changes: 27 additions & 0 deletions packages/better-call/src/endpoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1128,3 +1128,30 @@ describe("responseHeaders", () => {
expect(result.count).toBe(1);
});
});

describe("body from request", () => {
it("should parse body from the request when body property is not provided", async () => {
const endpoint = createEndpoint(
"/test",
{
method: "POST",
body: z.object({
filename: z.string(),
}),
},
async (ctx) => {
return ctx.body;
},
);

const result = await endpoint({
request: new Request("http://localhost/test", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ filename: "test.txt" }),
}),
} as any);

expect(result).toEqual({ filename: "test.txt" });
});
});
12 changes: 11 additions & 1 deletion packages/better-call/src/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import {
} from "./error";
import type { HasRequiredKeys, Prettify } from "./helper";
import type { Middleware } from "./middleware";
import type { OpenAPIParameter, OpenAPISchemaType } from "./openapi";
import type {
OpenAPIParameter,
OpenAPISchemaType,
OpenAPISecurityRequirement,
} from "./openapi";
import type { StandardSchemaV1 } from "./standard-schema";
import { toResponse } from "./to-response";
import type {
Expand All @@ -36,6 +40,12 @@ export interface EndpointMetadata {
description?: string;
tags?: string[];
operationId?: string;
/**
* Security requirements for this operation. Overrides the document-level
* `security` when set. Each entry maps a security scheme name (declared in
* `components.securitySchemes`) to its required scopes.
*/
security?: OpenAPISecurityRequirement[];
parameters?: OpenAPIParameter[];
requestBody?: {
content: {
Expand Down
18 changes: 12 additions & 6 deletions packages/better-call/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ export type { Prettify } from "./helper";
export type { Middleware, MiddlewareContext } from "./middleware";
// Middleware
export { createMiddleware } from "./middleware";
export type { OpenAPIParameter, OpenAPISchemaType } from "./openapi";
export type {
OpenAPIGeneratorConfig,
OpenAPIParameter,
OpenAPISchemaType,
OpenAPISecurityRequirement,
OpenAPISecurityScheme,
} from "./openapi";

// OpenAPI
export {
Expand All @@ -33,6 +39,11 @@ export {
export type { Router, RouterConfig } from "./router";
// Router
export { createRouter } from "./router";
// Schema
export type { StandardSchemaV1 } from "./standard-schema";
export type { JSONResponse } from "./to-response";
// Response
export { toResponse } from "./to-response";
// Types
export type {
HTTPMethod,
Expand All @@ -42,8 +53,3 @@ export type {
ResolveMetaInput,
ResolveQueryInput,
} from "./types";
// Schema
export type { StandardSchemaV1 } from "./standard-schema";
export type { JSONResponse } from "./to-response";
// Response
export { toResponse } from "./to-response";
Loading