Complete API reference

The public framework surface in one place. Follow the linked guides for examples and behavior in context.

updated in 0.9.4
On this page

Package entry points

EntryExports and purpose
goribuLink, NavLink, navigate, Form, FileUpload, upload, UploadError, Stylesheet, ClientEntry, and UI/metadata types.
goribu/serverdefineConfig, low-level createApp, and request, response, config, data, and middleware types.
goribu/clientSide-effect browser bootstrap. Import once from the client entry.
goribu/vitegoribu() Vite plugin for development and build integration.
  • Runtime boundary: Keep server runtime imports out of browser-reachable components.
  • Type-only imports: Type-only imports do not create runtime dependencies.

Route exports

ExportContract
defaultReact page component. Receives the props passed to res.render().
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS(req, res) => ResponseResult, optionally async.
metaMetadata object or synchronous ({ props, url }) => metadata.
prerenderOptional boolean. False requires Worker rendering; true requires successful static output.
uploadsObject of upload field policies. Requires R2 and a POST, PUT, or PATCH handler in the same route. See Uploads.
  • Implicit methods: A page has an implicit GET when no explicit GET exists. Goribu derives HEAD and OPTIONS when needed.
  • Special routes: _404, _500, and _middleware have special roles under routes.
  • Document location: The optional app-wide Document lives in src/setup/_document with a supported JavaScript or TypeScript extension.
  • Middleware export: A middleware default export is one function or an array. Return nothing to continue or a response result to stop.

Request

Handlers and middleware share one request object.

MemberType or result
methodHTTP method string.
pathURL pathname string.
urlAbsolute URL of the handled route; on client navigation, the target page's URL.
paramsNamed dynamic segments, each a string.
queryURLSearchParams.
headersHeaders.
envWorker environment and bindings.
cookiesCookie jar described below.
d1D1 query facade described below.
postgresPostgres.js client with Hyperdrive caching disabled, for writes and fresh reads.
postgresCachedPostgres.js client with Hyperdrive caching enabled, for reads that may be about a minute stale.
kvKV facade described below.
cacheJSON cache described below.
r2R2 bucket facade described below.
body()Promise of a plain object: parsed JSON, or the fields of either form encoding. Other content types, including text, give {}; use rawBody() for those.
rawBody()Promise of Uint8Array bytes, sharing the cached read with body().
waitUntil(promise)Register background work with the Worker execution context. See background work.

Middleware may attach its own fields to req, such as req.user, for later middleware and the handler; declare them for TypeScript on AppRequestFields. See Request handlers.

  • JSON bodies: JSON bodies must have an object root.
  • Form bodies: URL-encoded and multipart forms return field objects. Uploads are File values, repeated names become arrays, and missing fields are absent.
  • Upload fields: Fields declared in the route's uploads become verified R2Upload values or are absent. See Uploads.
  • Text bodies: Text returns a string.
  • Unsupported bodies: Empty or unsupported bodies return an empty object.
  • Parse failures: Malformed supported bodies produce 400. See Request handlers.
src/routes/name.ts
import type { Handler } from "goribu/server";

export const POST: Handler = async (req, res) => {
  const { name } = await req.body();
  if (typeof name !== "string" || !name.trim()) {
    return res.invalid({ name: "Enter your name." });
  }
  return res.json({ name: name.trim() });
};
src/routes/name.js
export const POST = async (req, res) => {
  const { name } = await req.body();
  if (typeof name !== "string" || !name.trim()) {
    return res.invalid({ name: "Enter your name." });
  }
  return res.json({ name: name.trim() });
};

Response

Return one terminal result from every handler. status() and headers() configure the builder and return it for chaining.

MethodContract
status(code)Set the HTTP status. Default is 200, except helper-specific defaults.
headers(object)Add header strings or arrays of strings.
render(props = {})Render this route's page with serializable object props.
json(data)JSON response.
text(body)Plain text response.
stream(body)Bytes from a ReadableStream, ArrayBuffer, typed array, or Blob; application/octet-stream unless you set content-type.
invalid(errors, status = 422)Field-error object or string mapped to root.
redirect(location)Redirect with default status 303. Explicit allowed statuses: 301, 302, 303, 307, 308.
notFound(props = {})Render the 404 page with optional props.
  • Return values: Handlers cannot return raw Response objects or JSX. Use stream() for binary or streamed bodies.
  • Streamed routes: A streamed route is never prerendered. Client navigation to it loads the document.
  • Bodyless statuses: Bodyless statuses reject a body.
  • Form outcomes: Enhanced Form outcomes are JSON, invalid, redirect, and same-page render. See Forms.

Cookies

MethodContract
req.cookies.get(name)Incoming string value or null.
req.cookies.set(name, value, options?)Queue a Set-Cookie header.
req.cookies.delete(name, { path?, domain? }?)Queue deletion using matching scope.
  • Cookie options: domain, path, expires (Date), maxAge (seconds), httpOnly, secure, and sameSite.
  • SameSite values: sameSite accepts Lax, Strict, or None, also lowercase.
  • Security defaults: Cookie security options have no implicit defaults.
  • Cookie encoding: Encode structured or Unicode data before setting a cookie.
APIExtra props or arguments
LinkNormal anchor props, replace = false, prefetch = true.
NavLinkLink props plus end = false and a string or ({ isActive }) => className callback. Goribu owns aria-current.
navigate(target, { replace? }?)Returns Promise<void>. Requires a browser and an HTTP(S) destination.
  • Active matching: NavLink matches pathname boundaries, ignoring query and hash. Root is exact-only.
  • Native links: Link enhancement preserves native behavior for external links, downloads, modifier clicks, and same-document anchors. See Links & navigation.
  • Static navigation: Production static navigation and prefetch reuse saved build-time data without invoking the Worker.
  • Current static URL: Clicking the exact current URL scrolls a confirmed static page without fetching or rerendering.
  • Current dynamic URL: Clicking the exact current URL of a dynamic page fetches fresh data.
  • Current-URL history: Neither static nor dynamic navigation to the exact current URL adds a history entry.
Exact active link
import { NavLink } from "goribu";

export default function SettingsLink() {
  return (
    <NavLink
      href="/settings"
      end
      className={({ isActive }) => isActive ? "active" : undefined}
    >
      Settings
    </NavLink>
  );
}
Exact active link
import { NavLink } from "goribu";

export default function SettingsLink() {
  return (
    <NavLink
      href="/settings"
      end
      className={({ isActive }) => isActive ? "active" : undefined}
    >
      Settings
    </NavLink>
  );
}

Form

Import Form from goribu. It accepts ordinary form props except that Goribu owns encoding and defines its method/action behavior.

PropContract
methodRequired: get, post, put, patch, or delete, in upper or lower case.
actionDefaults to the current URL when submitted.
childrenReact children or ({ pending, errors, data }) => children.
replaceReplace history during enhanced navigation; defaults false.
resetReset controls after successful JSON or same-page render; defaults false.
onSubmitRuns first and may cancel with preventDefault().
onResultRuns after state/render commit and before redirect navigation.
refReceives the HTML form; extended-method forms expose it after hydration.
  • Initial Form state: { pending: false, uploading: false, errors: null, data: null }.
  • Uploading state: uploading is true while any FileUpload in the form is still uploading. Submitting waits for those uploads to settle.
  • Form errors: errors is an object of message strings.
  • Result callback: onResult receives { ok: true, data?, response? } or { ok: false, errors, data?, response? }.
  • Encoding ownership: Do not pass encType to Form.
  • Hydration: GET/POST work natively. PUT/PATCH/DELETE require hydration.
  • Submitter overrides: A submitter's formMethod supports only GET/POST. Declare extended methods on Form itself. See Forms.

Metadata

meta is a RouteMetadata object or synchronous function receiving { props, url }, where url is a URL object.

FieldShape
titleString. Missing title produces an empty managed title.
description, canonical, robotsStrings.
metaArray of { name, content } or { property, content }.
linksArray of descriptors with required rel and href.
  • Optional link fields: as, type, media, sizes, hrefLang, crossOrigin, referrerPolicy, fetchPriority, imageSrcSet, imageSizes, and color are all strings.
  • Rejected metadata: Unknown fields, event handlers, async results, and duplicate canonical/description representations are rejected.

Document and browser entry

DocumentProps contains { children, head, nonce? }. Keep children under #root and head inside the document head.

  • Stylesheet: Stylesheet() renders the current graph's styles and takes no public props.
  • Client entry: ClientEntry({ src, nonce? }) renders the browser entry. src is the development fallback; production resolves the built asset.
  • Custom Document: Keep one Stylesheet and one ClientEntry in a custom Document.
  • Browser bootstrap: Import goribu/client once to hydrate and install navigation and Forms.
  • Browser environment: Only import.meta.env.PUBLIC_* values are browser-visible. See Rendering.

D1

Use the req.d1 tagged template in handlers or middleware. Values become parameters. A statement is lazy until a terminal runs.

APIResult or purpose
.get()Promise of first row or null.
.all()Promise of row array.
.value()Promise of first column of first row or null.
.run()Promise of { changes, lastRowId }.
req.d1.atomic(statements)Atomic batch of unexecuted statements; entries have rows, changes, and lastRowId.
req.d1.unsafe(text, params?)Statement from application-controlled SQL structure and explicit values.
req.d1.freshQuery facade that starts each tagged terminal at the primary.
req.d1.publicQuery facade without the visitor's bookmark.
.sessionSession object for integrations; raw binding when replication is disabled.
.bindingPlain Cloudflare D1Database binding.
  • Tagged D1 values: Strings, numbers, booleans, null, ArrayBuffer, ArrayBufferView, and non-empty arrays of those scalars are supported.
  • Unsafe parameters: unsafe() parameters are a flat scalar array.
  • Rejected D1 values: Undefined, dates, objects, nested/empty arrays, functions, symbols, and bigints are rejected.
  • Default consistency: Default D1 queries use the visitor session when replication is enabled.
  • Facade restrictions: Fresh/public cannot be chained. Atomic belongs to the default facade.
  • Batch session: A batch rebinds passed statements to its own session. See D1.
Typed row results
// Inside a handler; D1 and the posts table must exist.
type Post = { id: number; title: string };

const posts = await req.d1<Post>`
  SELECT id, title FROM posts ORDER BY id DESC
`.all();
Typed row results
const posts = await req.d1`
  SELECT id, title FROM posts ORDER BY id DESC
`.all();

Postgres

With database: { type: "postgres" }, handlers and middleware get two Postgres.js clients. Goribu doesn't wrap them, so the Postgres.js API applies as written. Use them as tagged templates; values become parameters and each query resolves to a row array.

APIResult or purpose
req.postgresClient with Hyperdrive caching off. The default for writes, transactions, and fresh reads.
req.postgresCachedClient with Hyperdrive caching on. Results may be up to about 75 seconds old, and writes don't clear them.
req.postgres.begin(callback)Transaction. Commits when the callback finishes and rolls back if it throws.
req.postgres(identifier), req.postgres(rows)Postgres.js helpers for dynamic table or column names and bulk inserts.
req.postgres.array(values)Array parameter for booleans, Dates, bytea, or bigints. String and number arrays need no wrapper.
req.env.POSTGRES.connectionStringHyperdrive connection string with caching off, for libraries with their own driver.
req.env.POSTGRES_CACHED.connectionStringHyperdrive connection string with caching on.
  • Connection strings: POSTGRES_URL is required in the env files; optional POSTGRES_MIGRATE_URL replaces it for migrations. Deploy reads both from .env.production only and doesn't upload them as secrets. req.env.POSTGRES_URL exists only in development.
  • Compatibility date: Postgres apps need 2024-09-23 or later. Goribu adds the nodejs_compat flag.
  • Development: pnpm dev connects both clients directly to the database in .env, so cached reads are always fresh locally.
  • Cached client restrictions: Never write or open a transaction on req.postgresCached. Queries that call NOW(), CURRENT_DATE, or RANDOM() are never cached.
  • Connections: Clients are created per request; never call .end(). A request runs up to three req.postgres and two req.postgresCached queries at once; more wait their turn.
  • No session state: Each query may use a different connection. Use SET only inside a transaction; temporary tables, session locks, and LISTEN/NOTIFY don't carry over.
  • Array results: int8[] and numeric[] return strings. Arrays of custom enum types return a single string, such as '{a,b}'.
  • Errors: Errors carry Postgres's code, such as 23505 for a unique violation. Branch on the code, not the message.
  • Query time: A query through Hyperdrive runs for at most 60 seconds. Migrations connect directly and aren't limited. See Database: Postgres.
Typed row results
// Inside a handler; Postgres and the posts table must exist.
type Post = { id: number; title: string };

const posts = await req.postgres<Post[]>`
  SELECT id, title FROM posts ORDER BY id DESC
`;
Typed row results
const posts = await req.postgres`
  SELECT id, title FROM posts ORDER BY id DESC
`;

KV

APIOptions
get(key, options?)type: text/json/arrayBuffer/stream; cacheTtl; metadata: boolean.
put(key, value, options?)expiration, expirationTtl, JSON-serializable metadata.
delete(key)No options.
list(options?)prefix, limit, cursor.
  • Default KV reads: Reads return text or null. JSON is parsed only when requested.
  • Metadata result: Metadata reads return { value, metadata, cacheStatus }. Missing value and metadata are null.
  • List result: Lists return keys, list_complete, and an optional cursor.
  • KV completion: Await all operations.
  • KV keys: Keys for get/put/delete are non-empty strings.
  • JSON writes: JSON writes need explicit stringification.
  • Unsupported KV calls: There is no bulk-get array, string shorthand for options, or separate getWithMetadata method.
  • KV consistency: KV is eventually consistent, including cached misses. See KV store.

Cache

req.cache needs no binding or configuration. The GoribuCache type is exported from goribu/server.

APIResult or options
get<Value = unknown>(key)Promise of parsed JSON or null on a miss. The type argument does not validate stored data.
set(key, value, options?)Promise of undefined. JSON serialization; optional positive safe-integer ttl in seconds, default 60.
delete(...keys)Promise of undefined. Attempts deletion of the supplied keys in the current data center.
  • Cache keys: Literal strings starting with /, scoped by origin and execution location.
  • Cache failures: Invalid inputs reject. Platform failures warn and become misses or skipped operations.
  • Pre-rendering: Pre-rendering bypasses the cache.
  • Background writes: Use req.waitUntil explicitly. See Cache for freshness, Smart Placement, and deployment support.

R2

req.r2 is the main bucket declared in r2, and req.r2.private the optional private bucket. The GoribuR2 and GoribuR2Bucket types are exported from goribu/server.

APIResult or purpose
get, put, head, delete, listNative R2 Workers API methods with the same arguments and results.
createMultipartUpload, resumeMultipartUploadNative multipart uploads.
url(key)Permanent public URL string on r2.domain. Main bucket only, and only when it has a domain.
signedUrl(key, options?)Promise of an expiring link. Options: expiresIn seconds (default 900, up to 604,800), download filename, immutable boolean.
req.r2.privateThe private bucket, with the same methods and signedUrl, but no url.
req.env.R2, req.env.R2_PRIVATERaw Cloudflare bucket bindings, for libraries that expect them.
  • Content type: put with a File or typed Blob sets its content type unless httpMetadata already names one.
  • Missing buckets: Using req.r2 without r2, or req.r2.private without r2.private, throws.
  • Development URLs: Under pnpm dev, url(key) returns a local /_goribu/r2/public/ path.
  • Keys: url() and signedUrl() reject empty keys and keys with . or .. path segments.
  • Signed links: A link works for anyone who has it until it expires; check access before creating one. immutable: true reuses one URL for half the lifetime, so use it only for contents that never change under the same key. Prerendering rejects signed links.
  • Signing secret: Signed links and upload references use GORIBU_SECRET. Changing it invalidates existing ones. See R2 files & uploads.
Public and private links
// Inside a handler; r2 must declare a domain and a private bucket.
const avatarUrl = req.r2.url(user.avatarKey);

const downloadUrl = await req.r2.private.signedUrl(invoice.fileKey, {
  expiresIn: 900,
  download: "invoice.pdf",
});
Public and private links
// Inside a handler; r2 must declare a domain and a private bucket.
const avatarUrl = req.r2.url(user.avatarKey);

const downloadUrl = await req.r2.private.signedUrl(invoice.fileKey, {
  expiresIn: 900,
  download: "invoice.pdf",
});

Uploads

A route exports uploads to accept files into R2. The browser uploads each file first, then the form submits a signed reference that the handler receives as an R2Upload.

APIContract
uploads route exportObject mapping field names to UploadPolicy objects.
FileUpload{ name, children? }. A file field inside Form that uploads on selection. children is an optional (state) => children render function.
upload(file, { action, name, signal? })Promise of a signed reference string for the field name declared at action. Send it to that route's handler as the field value.
UploadErrorError with a ready-to-show message and a code: type, size, forbidden, network, expired, or server.
Policy fieldContract
acceptRequired non-empty array of MIME types, such as image/png or image/*. Parameters and */* are rejected.
maxBytesRequired positive integer. At most 100,000,000, or 5,000,000,000 with direct: true.
privateBoolean. Stores the field in r2.private, which must be configured.
directBoolean. The browser uploads straight to R2 through a short-lived pre-signed URL.
key({ req, file, ext }) => string, called after middleware. Defaults to uploads/<random id><ext>.
  • R2Upload: { key, name, type, size, url? }. url is present only for the public main bucket.
  • FileUpload render state: file, status (idle, uploading, done, or error), errorMessage, previewUrl, showPicker(), and dropProps for a drop target.
  • One file per field: Use separate field names for several files. FileUpload outside a Form throws; use upload() there.
  • New keys only: Uploads never overwrite an existing object, so a fixed key fails the second time.
  • Time limits: An upload must start within 60 seconds and the form must be submitted within an hour.
  • Rendering: A route with uploads always runs on the Worker. To keep a page static, put uploads and the handler in a separate action route.
  • Direct uploads: Production needs the Worker secrets R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY; deploy asks for them in a terminal. Development uploads through the local Worker without keys.
  • File contents: The browser reports the type and Goribu doesn't inspect contents. Abandoned uploads stay in the bucket. See R2 files & uploads.
src/routes/profile/avatar.ts
import type { Handler, R2Upload, UploadPolicy } from "goribu/server";

export const uploads = {
  avatar: {
    accept: ["image/jpeg", "image/png"],
    maxBytes: 5_000_000,
  },
} satisfies Record<string, UploadPolicy>;

export const POST: Handler = async (req, res) => {
  const { avatar } = await req.body() as { avatar?: R2Upload };
  if (!avatar) {
    return res.invalid({ avatar: "Choose an image." });
  }
  return res.json({ key: avatar.key });
};
src/routes/profile/avatar.js
export const uploads = {
  avatar: {
    accept: ["image/jpeg", "image/png"],
    maxBytes: 5_000_000,
  },
};

export const POST = async (req, res) => {
  const { avatar } = await req.body();
  if (!avatar) {
    return res.invalid({ avatar: "Choose an image." });
  }
  return res.json({ key: avatar.key });
};

Configuration

defineConfig(config) validates and returns a frozen configuration. The fields are name, compatibilityDate, optional compatibilityFlags, database, kv, r2, domain, workersDev, responseHeaders, and wrangler.

Optional top-level domain connects one application hostname during deployment. workersDev: false turns off the workers.dev address and requires domain.

  • D1 declaration: { type: "d1", name, replication? }.
  • Postgres declaration: { type: "postgres" }, or with both hyperdriveId and hyperdriveCachedId for configs you manage. Connection strings go in POSTGRES_URL and the optional POSTGRES_MIGRATE_URL env values, never in the config. See Database: Postgres.
  • KV declaration: { name }.
  • R2 declaration: { name, domain?, private?: { name }, jurisdiction?: "eu" }. private requires domain on the main bucket. See R2 files & uploads.
  • Wrangler block: Native Wrangler fields as JSON, such as ai, services or vars, deeply frozen and added to the generated config. Fields and binding names Goribu owns, unknown fields, and non-JSON values fail the build. See Wrangler bindings.
  • Header callback: Receives { request, env, mode, nonce } and synchronously returns a plain object of strings or string arrays. See Configuration for defaults, allowed names, environment files, and header restrictions.

CLI

Run from the application directory. The app scripts wrap the installed Goribu CLI.

CommandBehavior
pnpm dev [--port <number>] [--host]Local Worker development with Vite. --port listens on exactly that port and fails if it is taken; --host listens on every network interface.
pnpm buildProduction bundles and static output.
pnpm checkRead-only production deployment plan.
pnpm run deployAuthenticate, build, provision, migrate, and upload.
pnpm exec goribu cloudflare-loginLog in to Cloudflare through Goribu's installed Wrangler.
pnpm exec goribu tailStream live logs from the deployed Worker.
pnpm exec goribu wrangler <args>Run Goribu's installed Wrangler with the account deploy uses. A real deploy is refused.
pnpm exec goribu d1 "<sql>"Run SQL against local D1. With --remote, a single SELECT runs directly and other SQL needs the database name typed in a terminal.
pnpm exec goribu local:clear <r2|kv> [--prefix <prefix>]Delete local R2 objects (every declared bucket) or KV keys, all or only keys starting with the prefix. Works while pnpm dev is running and never touches production.
pnpm exec goribu migrate:new <name>Create the next numbered SQL migration. Migrations are forward-only; migrate:down was removed in 0.9.4.
pnpm exec goribu migrateApply pending local migrations.
pnpm exec goribu migrate:statusRead migration status.
pnpm exec goribu migrate:seedRun local seed.sql; cannot target production (D1 only).
pnpm exec goribu --helpPrint usage. -h also works.
pnpm exec goribu --versionPrint the installed version. -v also works.
  • Production targets: Migrate, status, and d1 accept --remote, or --local for the default local target. Seed accepts only --local.
  • Postgres migrations: migrate:new, migrate, and migrate:status work with Postgres. With --remote, they connect to the database in .env.production. There is no Postgres version of d1; use psql "$POSTGRES_URL".
  • Development arguments: Dev accepts only --port and --host.
  • Argument-free commands: Build, check, deploy, tail, and cloudflare-login take no arguments.
  • CLI failures: Failures exit non-zero. Recognized Goribu failures print their repair message without a stack trace.
  • Type checking: pnpm typecheck is the TypeScript starter's separate tsc --noEmit script.
  • Scaffolding: pnpm create goribu@latest <name> creates an app.
  • Scaffolder options: --js, --help, and --goribu-tarball <path> for a matching local framework archive.
  • Scaffolder scope: Scaffolding never installs, authenticates, or deploys for you.

TypeScript

Use import type. Types annotate behavior; they do not validate request input or stored JSON.

EntryPublic types
goribuLinkProps, NavLinkProps, FormProps, FormMethod, FormState, FormResult, FormErrors, DocumentProps, RouteMetadata, Meta, FileUploadProps, FileUploadState, UploadErrorCode.
goribu/serverApplicationConfig, AppRequestFields, DefinedConfig, ResponseHeaders, CookieOptions, Cookies, D1Value, D1WriteResult, D1Statement, D1Queries, D1, KV, GoribuCache, GoribuR2, GoribuR2Bucket, R2Upload, UploadPolicy, GoribuRequest, GoribuResponse, ResponseResult, Handler, Middleware, ServerErrorProps, WorkerContext, WorkerApp, RouteManifestEntry, ClientAsset, AppOptions.
  • Handler types: Handler<Props, Env, Params> and Middleware<Env>.
  • UI types: Meta<Props> and Form<Data>.
  • Data types: req.d1<Row>, req.postgres<Row[]>, and req.kv.get<Value>(key, { type: "json" }). Postgres takes the array type, D1 the row type.
  • Postgres bindings: req.env isn't typed for POSTGRES and POSTGRES_CACHED. Declare them in the handler's Env type argument to read their connectionString.
  • Body validation: Request body values still need narrowing and validation.

Low-level app creation

createApp(options) from goribu/server returns a Worker app with fetch(request, env, ctx): Promise<Response>. Normal applications use goribu() in Vite; the build supplies the route graph and calls this API for them.

  • Required app options: manifest, modules, and errorPages.
  • Optional app options: documentId, middlewareByRoute, mode, clientAssetManifest, clientAssetBase, applicationConfig, and postgres, an (env) => { fresh(), cached() } factory for the two Postgres clients.
  • Compiled inputs: App options describe compiled routes, error pages, assets, and validated config. They are not additional goribu.config.js fields.