Skip to main content

Configuration

BurgerAPI uses five clear configuration layers.

LayerWherePurpose
Buildburger.build.tsDirs, prefixes, debug. Read by burger-api build, inspect, and doctor (CLI only)
Appnew Burger({...})Runtime options: dirs, prefixes, validation, prebuilt routes
App docssrc/openapi.config.tsOpenAPI metadata, docs UI, docs auth
Pluginssrc/plugins.tsRegister plugins
Routeapi/**/config.tsPlain data exposed as ctx.config; core only reads responseValidation

burger.build.ts is not runtime configuration. burger-api dev and burger-api start load the options in src/index.ts (new Burger({...})); burger-api build, inspect, and doctor read burger.build.ts to find route directories. The scaffold keeps the two in sync (apiDir: "./src/api", apiPrefix: "/api" in both), and the convention defaults below agree out of the box, so a default project works without touching either file. If you change one, change the other: burger-api doctor warns when they disagree.

Application entry​

src/index.ts
import { Burger } from "burger-api";

const burger = new Burger({
apiDir: "./src/api",
apiPrefix: "/api",
});

burger.serve(4000);

OpenAPI metadata belongs in src/openapi.config.ts, which burger-api create scaffolds. It takes title, description, version, servers, contact, license, docsAuth, provider, path, and more. See OpenAPI for the full field list.

src/openapi.config.ts
import type { OpenAPIConfig } from "burger-api";

export default {
title: "My Burger API",
version: "1.0.0",
description: "An API built with BurgerAPI",
servers: [{ url: "https://api.example.com", description: "Production" }],
} satisfies OpenAPIConfig;

new Burger({ title, version, description }) still works as a fallback for apps that do not use an openapi.config.ts file.

How directories resolve​

apiDir, pageDir, and wsDir are looked up in this order until one exists:

  1. Absolute path, used as-is.
  2. Relative to the project root, for example ./src/api.
  3. Relative to the entry file's directory, when the CLI is loading or scanning the app (burger-api dev, build, inspect, doctor). For the default entry src/index.ts, that directory is src/, so apiDir: "api" finds src/api.

Both forms work with the default scaffold layout:

apiDir: "./src/api", // relative to the project root
// or
apiDir: "api", // found under src/ via the entry-file fallback

The project root always wins when both locations exist. Running the app with plain bun src/index.ts (no CLI) skips the entry-file fallback.

If a directory cannot be resolved, the error names the exact paths that were tried:

Routes directory "backend" does not exist. Tried "./backend" (project root)
and "./src/backend" (src/). Check the apiDir option in src/index.ts.

Build file​

burger.build.ts
export default {
apiDir: "./src/api",
pageDir: "./src/pages",
apiPrefix: "/api",
pagePrefix: "/",
debug: false,
};

Generated by burger-api create. Used by the CLI for discovery and build.

Plugins and providers​

src/plugins.ts
import type { PluginRegistrar } from "burger-api";

export default (burger: PluginRegistrar) => {
burger.usePlugin(/* ecosystem plugin */);
};
src/providers.ts
import type { ProviderRegistrar } from "burger-api";

export default (burger: ProviderRegistrar) => {
burger.provide("db", db);
};

Route config​

A route's config.ts default export is plain data, available to hooks and plugins as ctx.config. Core itself reads exactly one key: responseValidation ("off" | "dev" | "enforce"), which overrides the app-wide mode for that route. Keys such as auth, cache, and timeout do nothing unless a plugin or hook reads them. See Validation Configuration for the response mode.

src/api/public/health/config.ts
export default {
auth: false,
};
src/api/admin/config.ts
export default {
auth: { required: true, roles: ["admin"] },
timeout: 5000,
responseValidation: "enforce",
};

Per-method overrides​

The default export applies to every method. Add an uppercase method export to override it for that method only. The override is shallow-merged over the default, and method keys win:

src/api/items/config.ts
export default { auth: false };

export const POST = { auth: { required: true } };

Here GET stays public while POST requires a user. ctx.config for a request is the merged object for its method, so plugins and hooks keep reading ctx.config.auth unchanged. A config.ts with only a default export behaves exactly as before.

Type your keys by augmenting RouteConfig, so ctx.config is typed wherever a hook or plugin reads it:

src/types.ts
declare module "burger-api" {
interface RouteConfig {
auth?: boolean | { required?: boolean; roles?: string[] };
cache?: { maxAge: number };
timeout?: number;
}
}

config.ts applies per route directory: there is no inheritance between folders.

Global hooks​

Annotate global hooks with GlobalHooks so TypeScript checks the hook signature and infers ctx:

src/hooks.ts
import type { GlobalHooks } from "burger-api";

export const onRequest: GlobalHooks["onRequest"] = [/* ... */];

export const onError: GlobalHooks["onError"] = (error, ctx) => {
console.error(error);
};

Environment variables​

BurgerAPI does not ship its own .env loader. Bun loads .env natively. Optional validation can live in an ecosystem plugin.

Types for this feature​

Every configuration layer has a type.

The types you use (all from burger-api):

  • ServerOptions: the new Burger({...}) options
  • OpenAPIConfig: the src/openapi.config.ts default export
  • BuildConfig: the burger.build.ts export
  • RouteConfig: the config.ts options (augment it to type your keys)
  • GlobalHooks: the src/hooks.ts named exports
  • RouteDefinition: a route with handlers, schema, and openapi metadata
  • RouteSchema: a route's full schema map
  • HTTPMethod: the allowed method names
  • BurgerContext: the request object (for typing handlers and hooks)

✅ Correct, typed configuration:

src/index.ts
import { Burger } from "burger-api";
import type { RouteDefinition } from "burger-api";

const routes: RouteDefinition[] = [
{
path: "/users",
handlers: { GET: (ctx) => Response.json({ ok: true }) },
},
];

const burger = new Burger({
apiRoutes: routes,
});

When apiRoutes is provided, it replaces filesystem discovery: apiDir is ignored and no route files are scanned at runtime. This is how burger-api build produces portable bundles.

❌ Wrong, a typo in an option name or a method key:

new Burger({ apiDri: "./src/api" }); // ❌ Property 'apiDri' does not exist

const bad: RouteDefinition = {
path: "/x",
handlers: { get: () => new Response() }, // ❌ lowercase 'get' is not an HTTP method
};

For typed ctx.services and custom context values, use the augmentation pattern. See the TypeScript overview.

Check your code: bun run typecheck.