File-based Routing
Every file under apiDir is a route. The first directory level is the HTTP method, and the file path is the URL; there is no central file where routes are registered.
src/api/
index.ts generated by align
get/
index.ts generated by align
health.ts GET /health
user/
[id].ts GET /user/:id
post/
index.ts generated by align
user/
login.ts POST /user/login[id]denotes a route parameter, written as:idat runtime.- Handler files must
export default. types/handler.d.tscurrently provides type projections for four methods:get,post,patchanddelete. The runtime itself only expands the keys of a method directory, so directories for other methods are still registered.
Generated Barrel Files
Each method directory has an index.*, and the API root has another one that aggregates all methods:
// src/api/get/index.ts
// @generated by tealina — do not edit.
// Rebuild it with: tealina align
export default {
health: import('./health.js'),
'user/:id': import('./user/[id].js'),
}// src/api/index.ts
export default {
get: import('./get/index.js'),
post: import('./post/index.js'),
}The keys are logical paths: no leading slash. @tealina/server prepends / when registering routes.
These index.* files are generated artifacts; never edit them by hand. The // @generated by tealina — do not edit. line at the top of the file is the marker. After adding, renaming or deleting a handler, run align once.
Commands
create
tealina create post/user # writes src/api/post/user.ts
tealina create user/login # defaults to post when no method is given
tealina create post/user/:id # writes [id].tscreate also updates the barrel files touched by this run. A route segment may be written as :id or [id]; on disk it is normalized to [id].
delete
tealina delete post/user/login
tealina delete post/user/login --with-testDeletes the handler and rewrites the barrel files. --with-test also deletes the generated integration test file.
align
Rebuilds every generated index.* and the fallback types/api.d.ts from the files actually present on disk. After moving or renaming files by hand, this is the command you must run.
tealina align
tealina align --check # compare only, write nothing; exit code 1 on differences
tealina align --check --json # write a JSON object to stdoutThe shape of --json:
{
"ok": false,
"changed": [{ "action": "update", "file": "src/api/get/index.ts" }]
}--check is suitable for CI; it runs the same generation logic as a normal run and only stops before writing.
Excluding Files
Patterns in exclude are relative to apiDir and matched with POSIX path rules. align already skips index files and dotfiles; this option is for other files you want to keep next to the routes without turning them into endpoints:
export default defineConfig({
apiDir: 'src/api',
apiBaseURL: '/api/v1',
typesDir: 'types',
exclude: ['_shared/**', '**/*.test.ts'],
})URL Prefix
apiBaseURL is the prefix under which the routes answer; the CLI never infers it from directory names. In the scaffold it comes from API_BASE_URL in version.js, and at least four places read it:
tealina.config.ts, which passes it togdoc;src/mount.ts, which decides where routes are mounted;src/doc.ts, which decides the URL of the documentation JSON file;- the frontend's
api/client.ts, which decides request URLs and the Vite proxy.
When changing the prefix, change version.js; do not change all four places separately.
Exit Codes
align and gdoc exit 0 on success; --check exits 1 when a generated artifact is stale, without writing files. Error messages go to stderr, and the --json object goes to stdout, so it can safely be piped into other tools.