# createApp

> Every option of createApp and every member of the app object it returns.

```ts
import { createApp } from 'locosdk/server'

const app = createApp({ schema, app: config.app, pages: { '/*': index } })
app.serve()
```

How to use it: [Server: hooks, routes, crons](https://golocomotive.fr/docs/developers/server.md).

## Options

| option | default | what |
|---|---|---|
| `schema` | required | the `defineSchema(...)` of `schema.ts` |
| `app` | none | the identity of `loco.config.ts`: names the mails, serves the logo at `/api/_/logo` |
| `dir` | `'data'` | the data folder: `<dir>/app.db`, `<dir>/storage/`, `<dir>/backups/` |
| `migrations` | `[]` | `{ file, up(m) }` items, run once each before the schema sync, in file order |
| `pages` | `{}` | Bun.serve routes of HTML imports, `{ '/*': index }`. `/api/*` never falls through to a page |
| `meta` | local values | `{ appName, appURL, senderName, senderAddress }`: the `{APP_NAME}` and `{APP_URL}` of mails, the sender, the public URL of the MCP server |
| `mail` | sending throws | a transport: `smtpTransport(config)`, or `memoryTransport()` in tests |
| `dev` | `NODE_ENV !== 'production'` | hot reload on the pages, no cap on live codes |
| `rateLimits` | `*:auth` 5 per 3 s | PocketBase's rate limit rules: `{ label, maxRequests, duration }`. A list given here replaces the default, so name `*:auth` again |
| `storage` | `diskStorage('<dir>/storage')` | where file fields live: `diskStorage(path)` or `s3Storage(config)` |
| `backups` | no cron, keep all, on disk | `{ cron, maxKeep, storage }`, `storage` is `'disk'` or an S3 config |
| `trustedProxy` | `{ headers: [], useLeftmostIP: false }` | the headers that carry the real client IP |
| `restart` | `() => process.exit(0)` | how the process comes back after a restore |
| `mcp` | off | the app as an MCP server at `/mcp`: [The MCP server](https://golocomotive.fr/docs/developers/mcp.md) |

## The app object

| member | what |
|---|---|
| `schema` | the live schema, after the sync |
| `db` | the `bun:sqlite` database. `app.db.query(sql).all(...params)`, every value bound. No rules |
| `records` | the trusted records service: `list`, `getOne`, `getFirst`, `create`, `update`, `delete`, `transaction`, `batch`. No rules, full validation, after success hooks fire |
| `mail.send(message)` | send through the transport and the `onMailerSend` chain |
| `meta` | the resolved `meta` |
| `route(method, path, handler, ...middlewares)` | a custom route: [Hooks](https://golocomotive.fr/docs/reference/hooks.md#routes-and-middlewares) |
| `on*` | the hooks: [Hooks](https://golocomotive.fr/docs/reference/hooks.md) |
| `cron(name, spec, fn)` | a schedule, UTC, five fields or `@hourly @daily @weekly @monthly @yearly` |
| `dispatch(request, { auth, body, context })` | an internal request through the router: the route's middlewares and hooks run. What MCP tools call |
| `backups` | `create(name?)`, `list()`, `restore(key)`, `active` |
| `storage` | the file storage |
| `serve({ port? })` | listen on `port`, else `PORT`, else 8090. Port 0 picks a free one. Returns Bun's `Server` |
| `close()` | stop the server and the crons, close the database |

Under `bun --hot`, a second `createApp` on the same `dir` retires the first: its crons stop and its database closes. Bun keeps the socket.

## Boot, in order

1. Retire an earlier app on the same `dir` (a hot reload).
2. Open `<dir>/app.db` (WAL, foreign keys on, a 10 s busy timeout).
3. Run the migrations not yet applied, then sync the schema: additive changes apply, a destructive one stops the boot with every problem listed ([Schema as code](https://golocomotive.fr/docs/developers/schema.md#sync-on-boot)). The sync also checks each auth collection's options and mints a token secret where one is missing.
4. Mount the routes: records, auth, files, `/api/health`, backups, the changelog, the user copy, MCP when it is on, the logo.
5. Start the crons: the built in hourly cleanup, and the backup cron when `backups.cron` is set.
