# scaffold

> Write an API module, a REST resource per table and an OpenAPI route for Hono or Next.js, from the tables gen found.

Source: https://bettersupabase.com/docs/cli/scaffold

`scaffold api` writes the files that serve your tables as REST
[resources](/docs/specs/resources) and serve the OpenAPI document for them,
for Hono or Next.js. Run [`gen`](/docs/cli/gen) first: the tables come from
the metadata module it writes.

```bash
pnpm better-supabase scaffold api
pnpm better-supabase scaffold api --framework hono --tables customers,tags
```

## Options [#options]

| 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 [#from-gen]

With `scaffold.api` set, [`gen`](/docs/cli/gen#tasks) runs scaffold as a
task after it writes the metadata, so a new table reaches the API without
another command:

```ts title="better-supabase.config.ts"
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 [#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:

```ts title="src/lib/api.ts"
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](/docs/specs)).

### Next.js [#nextjs]

A catch-all route serves every resource through `bs.resources`, and a
second route serves the document with
[`specResponse`](/docs/specs#serve-the-document):

```ts title="src/app/api/[...path]/route.ts"
import { basePath, resources } from "../../../lib/api";
import { bs } from "../../../lib/supabase/server";

export const { GET, POST, PATCH, PUT, DELETE } = bs.resources(resources, {
  basePath,
});
```

```ts title="src/app/api/openapi.json/route.ts"
import { specResponse } from "better-supabase/spec";

import { api } from "../../../lib/api";

const { document } = api.openapi();

export const GET = (request: Request) => specResponse(document, request);
```

### Hono [#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`:

```ts title="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 [#next-steps]

The command prints what is left to do:

* With Hono, mount the routes in `server.ts`.
* When `specs.entry` is not the new `lib/api.ts`, set
  `specs: { entry: "src/lib/api.ts" }` in `better-supabase.config.ts`, so
  [`spec emit`](/docs/cli/spec) reads the model.
* Describe the resources in `lib/api.ts`. A later `scaffold api` only
  rewrites `api.generated.ts`, so run it again with a new `--tables` list
  to add or remove a table.