scaffold
Write an API module, a REST resource per table and an OpenAPI route for Hono or Next.js, from the tables gen found.
scaffold api writes the files that serve your tables as REST
resources and serve the OpenAPI document for them,
for Hono or Next.js. Run gen first: the tables come from
the metadata module it writes.
pnpm better-supabase scaffold api
pnpm better-supabase scaffold api --framework hono --tables customers,tagsOptions
| Option | Default | Effect |
|---|---|---|
--framework <name> | The one framework (hono or next) in package.json | Which files to write. With both or neither installed, pass it |
--tables <list> | Every table and view in the public schema | The tables to serve, comma-separated or repeated |
--name <name> | api | The module name: lib/<name>.ts and lib/<name>.generated.ts |
--base-path <path> | /api | Where the resources are served |
Each option falls back to the same key under scaffold.api in the config
(framework, tables, name, basePath) before its default. Without a
scaffold.api entry, the command prints one for the flags you passed.
From gen
With scaffold.api set, gen runs scaffold as a
task after it writes the metadata, so a new table reaches the API without
another command:
export default defineConfig({
scaffold: { api: { framework: "hono", tables: ["customers", "tags"] } },
});The task rewrites only lib/<name>.generated.ts, never the module you edit
or the routes, and gen --check fails when that file is out of date.
--tables names are checked against the metadata module
(generated.meta.js), and an unknown table fails with the list of known
ones. Without a metadata module, pass --tables; the names are used as
they are. --name is a file name without a folder.
What it writes
Paths are under src/ when the project has one, else under the project
root.
| File | Framework | Written |
|---|---|---|
lib/api.generated.ts | both | On every run |
lib/api.ts | both | Once; yours after that |
app/api/[...path]/route.ts | Next.js | Once; yours after that |
app/api/openapi.json/route.ts | Next.js | Once; yours after that |
api-routes.ts | Hono | Once; yours after that |
lib/api.generated.ts holds tableNames, resources and basePath, and
starts with an @generated header. Each run rewrites it from the options,
and a file at that path without the header stops the command, so it never
overwrites your code. Every other file is written when it doesn't exist and
kept (Kept <path> (yours)) when it does.
lib/api.ts is the API model, the entry spec emit reads:
import { defineApi } from "better-supabase/spec";
import { betterSupabase } from "./supabase";
import { basePath, resources, tableNames } from "./api.generated";
export { basePath, resources, tableNames };
export const api = defineApi(betterSupabase, {
info: { title: "API", version: "1.0.0" },
basePath,
resources,
});Describe the resources, add list queries and custom routes there (see API documents).
Next.js
A catch-all route serves every resource through bs.resources, and a
second route serves the document with
specResponse:
import { basePath, resources } from "../../../lib/api";
import { bs } from "../../../lib/supabase/server";
export const { GET, POST, PATCH, PUT, DELETE } = bs.resources(resources, {
basePath,
});import { specResponse } from "better-supabase/spec";
import { api } from "../../../lib/api";
const { document } = api.openapi();
export const GET = (request: Request) => specResponse(document, request);Hono
api-routes.ts exports apiRoutes(bs): the document at
<basePath>/openapi.json, bs.middleware() on <basePath>/*, and
bs.resource(table) for each table. Mount it in src/server.ts:
import { createHono } from "better-supabase/hono";
import { apiRoutes } from "./api-routes";
import { betterSupabase } from "./lib/supabase";
export const bs = createHono(betterSupabase);
export const app = bs.app().route("/", apiRoutes(bs));Next steps
The command prints what is left to do:
- With Hono, mount the routes in
server.ts. - When
specs.entryis not the newlib/api.ts, setspecs: { entry: "src/lib/api.ts" }inbetter-supabase.config.ts, sospec emitreads the model. - Describe the resources in
lib/api.ts. A laterscaffold apionly rewritesapi.generated.ts, so run it again with a new--tableslist to add or remove a table.
Last updated on