Configuration
BurgerAPI uses five clear configuration layers.
| Layer | Where | Purpose |
|---|---|---|
| Build | burger.build.ts | Dirs, prefixes, debug. Read by burger-api build, inspect, and doctor (CLI only) |
| App | new Burger({...}) | Runtime options: dirs, prefixes, validation, prebuilt routes |
| App docs | src/openapi.config.ts | OpenAPI metadata, docs UI, docs auth |
| Plugins | src/plugins.ts | Register plugins |
| Route | api/**/config.ts | Plain 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
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.
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:
- Absolute path, used as-is.
- Relative to the project root, for example
./src/api. - 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 entrysrc/index.ts, that directory issrc/, soapiDir: "api"findssrc/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
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
import type { PluginRegistrar } from "burger-api";
export default (burger: PluginRegistrar) => {
burger.usePlugin(/* ecosystem plugin */);
};
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.
export default {
auth: false,
};
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:
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:
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:
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: thenew Burger({...})optionsOpenAPIConfig: thesrc/openapi.config.tsdefault exportBuildConfig: theburger.build.tsexportRouteConfig: theconfig.tsoptions (augment it to type your keys)GlobalHooks: thesrc/hooks.tsnamed exportsRouteDefinition: a route with handlers, schema, and openapi metadataRouteSchema: a route's full schema mapHTTPMethod: the allowed method namesBurgerContext: the request object (for typing handlers and hooks)
✅ Correct, typed configuration:
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.