# An app, step by step

> The smallest complete app: a schema, a server, a page with a list of orders, and a sign in.

This page builds an app with clients and their orders. Each step is a file of the app repo. A new app starts from a template with the same files, so this is also a tour of what an agent finds on its first day.

## 1. The schema

`schema.ts` is the single source of truth for data. It declares the collections, their fields and their rules. The types of every record come from it, with no code generation.

```ts
import { auth, base, defineSchema, number, relation, select, text } from 'locosdk/schema'

const signedIn = '@request.auth.id != ""'
const rules = { listRule: signedIn, viewRule: signedIn, createRule: signedIn, updateRule: signedIn, deleteRule: signedIn }

export const schema = defineSchema({
    users: auth({
        fields: { name: text(), role: select(['admin', 'agent']) },
        listRule: 'id = @request.auth.id',
        viewRule: 'id = @request.auth.id',
        updateRule: 'id = @request.auth.id',
    }),
    clients: base({ fields: { name: text({ required: true, presentable: true }), city: text() }, ...rules }),
    orders: base({
        fields: {
            client_ref: relation('clients', { required: true }),
            label: text({ required: true }),
            status: select(['open', 'done']),
            amount: number(),
        },
        ...rules,
    }),
})
```

A rule left out is `null`: only a superuser passes it. More: [Schema as code](https://golocomotive.fr/docs/developers/schema.md), [Rules and filters](https://golocomotive.fr/docs/developers/rules.md).

## 2. The server

`server.ts` makes the app and serves it. `createApp` opens `data/app.db`, brings its tables in line with the schema, and mounts the PocketBase API, the pages and `GET /api/health`.

```ts
import { createApp } from 'locosdk/server'
import config from './loco.config.ts'
import { schema } from './schema.ts'
import index from './src/index.html'

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

app.serve()
```

`serve()` listens on `PORT`, else 8090. Hooks, routes and crons go between the two lines: [Server: hooks, routes, crons](https://golocomotive.fr/docs/developers/server.md).

## 3. The identity

`loco.config.ts` names the app. It is plain data: the portal's settings rewrite the `app` block in place.

```ts
import type { LocoConfig } from 'locosdk/src/config/config.ts'

export default {
    app: { name: 'Orders', color: '#0b7285', logo: null, lang: 'en' },
} satisfies LocoConfig
```

The theme derives every shade from `color`. `lang` is `en` or `fr`: the language a visitor starts in.

## 4. The data layer

`src/data.ts` holds the app's one client, its observable database and its session.

```ts
import { createClient, createDb } from 'locosdk/client'
import { notify, Session } from 'locosdk/ui'
import { schema } from '../schema.ts'

export const client = createClient(location.origin, { schema })
export const db = createDb(client, { notify })
export const session = new Session(client)
```

`db.orders` is typed from the schema. A name outside the schema fails the typecheck. More: [Client and data layer](https://golocomotive.fr/docs/developers/client.md).

## 5. A collection on screen

`defineCollectionUI` describes a collection once. The grid, the record card, the form and the export all read it.

```tsx
import { defineCollectionUI } from 'locosdk/ui'
import { db } from '../../data.ts'

export const clients = defineCollectionUI(db.clients, {
    title: 'Clients',
    label: (c) => c.name,
    sort: 'name',
    form: { fields: ['name', 'city'] },
    columns: [
        { id: 'name', label: 'Name', value: (c) => c.name },
        { id: 'city', label: 'City', value: (c) => c.city },
    ],
})
```

## 6. The page

`src/main.tsx` mounts the shell. `AppRouter` gives the sign in page, the guard, the navbar and a not found page.

```tsx
import 'locosdk/ui/setup'
import { createRoot } from 'react-dom/client'
import { AppRoot, AppRouter, LiveGrid } from 'locosdk/ui'
import { clients } from './collections/clients/index.tsx'
import { client, session } from './data.ts'
import config from '../loco.config.ts'

createRoot(document.body.appendChild(document.createElement('div'))).render(
    <AppRoot app={config.app} client={client}>
        <AppRouter
            session={session}
            languages={['en', 'fr']}
            pages={[{ path: 'clients', label: 'Clients', icon: 'group', element: <LiveGrid ui={clients} deletable exportMenu={{ filename: 'clients', formats: ['csv'] }} /> }]}
        />
    </AppRoot>,
)
```

`locosdk/ui/setup` is always the first import: it installs the globals `X` (Mantine), `H` (Mantine hooks) and `I` (icons).

## 7. Run it

```sh
bun install
bun --hot server.ts
# in a second terminal, once the app booted
bunx golo superuser upsert admin@example.com
```

The first boot creates `data/app.db`. `superuser upsert` reads the password on stdin. `golo` is the bin of `locosdk`: `bunx golo` runs it from the app's own install. Open the app on port 8090, sign in, and the Clients grid pages, sorts, searches and exports through the server.

## 8. Check it

```sh
golo gate
```

`golo gate` runs the typecheck, the tests and the lint in parallel and exits 1 on red. It also fails when the code changed and `CHANGELOG.md` has no line for it: [Changelog and releases](https://golocomotive.fr/docs/developers/releases.md).
