Contacts: everything at once
Every client Grit can scaffold, against one API and one set of types. Useful when you know you want more than one client, and useful for looking at what the other tiers give you before choosing one.
The app is a contact book: contacts that belong to groups, each with a photo. Two resources, two commands, and everything that follows is the same in every tier. What differs is what you run and where it ends up, which is what this page is about.
Before you start
- Go 1.21 or newer — The API and the desktop shell.
- Node 20 or newer, with pnpm — Four JavaScript applications.
- The Wails CLI — For the desktop app. The rest works without it.
- Expo Go, or a simulator — For the phone app.
And Grit itself: go install github.com/MUKE-coder/grit/v3/cmd/grit@latest.
1. Create the project
$grit new contacts --full --theme emerald --db sqlite$cd contacts
Postgres is the default. --db sqlite above means the project runs with no database server at all, which is the shortest path to seeing it work; drop the flag when you want Postgres, and docker compose up -d brings one up along with Redis, MinIO and a mail catcher.
Next.js or TanStack
The command above builds the frontend with Next.js. The same tier with TanStack Router and Vite instead is one flag:
$grit new contacts --full --vite --theme emerald --db sqlite
Everything after this point is identical. The resources, the API, the admin screens and the deployment are the same; what changes is the router and the build tool, and the admin panel is written in whichever dialect that app speaks.
Pick a theme
--theme sets the sign-in layout, the dashboard tokens, the fonts and the brand colours, and it applies to both frontends. Eight ship with Grit:
| Theme | What it looks like |
|---|---|
| atlas | Split-screen sign-in, Inter. The default: sharp and neutral, for a team tool. |
| aurora | Centered sign-in, Geist. Pastel and friendly, for consumer software. |
| pulse | Split-screen with a carousel, Onest and DM Serif. Warm and bold, for a brand. |
| coral | A sign-in modal over the page. Soft, close up. |
| amber | A boxed sign-in card. Warm neutrals. |
| sky | A banner above the form. Open and light. |
| mono | A showcase panel beside the form. Monochrome and typographic. |
| emerald | A quote beside the form. Green and calm: the one the commands above use. |
Nothing is baked in. THEME and VITE_THEME in .env both carry the name, so changing it there repaints the app without re-scaffolding.
| Directory | What is in it |
|---|---|
| apps/api | The Go API |
| apps/web | The public Next.js site |
| apps/admin | The admin panel |
| apps/desktop | The Wails desktop app |
| apps/expo | The Expo mobile client |
| apps/docs | A documentation site for the project |
2. Describe the data
A group first, because a contact points at one. Each command writes the Go model, the migration, the service, the handler, the routes, the Zod schema, the TypeScript types and an admin screen, and registers all of it.
$grit generate resource Group \$ --fields "name:string,description:text"
Then the contact, with a photo and a group to belong to. The belongs_to:Group field is what makes the admin screen render a picker rather than a text box asking for an id.
$grit generate resource Contact \$ --fields "name:string,email:email,phone:tel,photo:file:image,group:belongs_to:Group"
3. Build the database
$grit migrate$grit seed
grit migrate creates the tables from the models, and grit seed fills them: an administrator you can sign in as, a few users, and an API key. The administrator is admin@example.com with the password admin123. Change it before anybody else can reach the machine.
4. Run it
$grit start
Brings up the API, the web app, the admin panel and the desktop app when Wails is installed. The Expo client is its own command, grit start expo, because Metro wants a terminal of its own.
| Where | What |
|---|---|
| http://localhost:8080/api/v1 | The API |
| http://localhost:3000 | The public site |
| http://localhost:3001 | The admin panel |
| A native window | The desktop app, when Wails is installed |
| Expo Go | The phone app, after grit start expo |
5. What you have
Two resources, and for each of them: a table with sorting, filtering, search, pagination, bulk edit and CSV import and export; a form that validates on both sides from one schema; and REST endpoints under /api/v1/groups and /api/v1/contacts with the same rules applied.
The photo field is a real upload: the browser asks the API for a presigned URL and sends the file straight to storage, so the file never passes through the API process. With STORAGE_DRIVER=local that storage is a directory on disk, which is the default when there is no MinIO to talk to.
6. Deploy it
# The API, and the web apps behind it$grit deploy --host user@server.com --domain contacts.example.com# Then each client, on its own schedule$cd apps/desktop && wails build$cd apps/expo && npx eas build --platform all
The server side deploys once. The desktop and mobile apps are artefacts with their own release cycles: people update them when they choose to, which is the reason to keep the API backward compatible rather than assuming every client is current.
Before any of that, read the go-live checklist: it is the list of things that are fine in development and not in production, starting with the seeded password above and the secrets in .env.
