Resource Generation
Generate full-stack CRUD resources for desktop apps. One command creates Go models, services, React pages, and injects code into 10 locations.
Generate a Resource
The same grit generate resource command works for both web and desktop projects. Grit auto-detects the project type.
Files Created
Five new files are generated:
internal/models/product.goGORM model struct with ID, fields, timestamps, soft delete
internal/service/product.goService with List, ListAll, GetByID, Create, Update, Delete
frontend/src/routes/_layout/products.index.tsxList route with search, pagination, PDF/Excel export, edit/delete
frontend/src/routes/_layout/products.new.tsxCreate form route with field-type-based inputs
frontend/src/routes/_layout/products.$id.edit.tsxEdit form route with pre-filled fields
Automatic Injections
In addition to new files, code is injected into 10 locations in existing files using grit: markers:
| File | Marker | What |
|---|---|---|
| db.go | // grit:models | Model in AutoMigrate |
| main.go | // grit:service-init | Service initialization |
| main.go | /* grit:app-args */ | Service passed to NewApp |
| app.go | // grit:fields | Service field on App struct |
| app.go | /* grit:constructor-params */ | Constructor parameter |
| app.go | /* grit:constructor-assign */ | Field assignment |
| app.go | // grit:methods | 7 bound methods (CRUD + export) |
| types.go | // grit:input-types | Input struct |
| cmd/studio/main.go | // grit:studio-models | Model in Studio |
| sidebar.tsx | // grit:nav-icons + nav | Nav icon + item |
Supported Field Types
| Type | Go Type | Form Input |
|---|---|---|
| string | string | Text input |
| text | string | Textarea |
| richtext | string | Textarea |
| int | int | Number input |
| uint | uint | Number input |
| float | float64 | Number input |
| bool | bool | Toggle switch |
| date | time.Time | Date picker |
| datetime | time.Time | DateTime picker |
| slug | string | Auto-generated from source field |
| belongs_to | uint | Number input (foreign key) |
Example: Article with Slug
The slug field type auto-generates a URL-friendly slug from the source field (title) via a BeforeCreate GORM hook. Slug fields are excluded from forms and input structs.
Remove a Resource
To remove a previously generated resource, deleting files and reversing all injections:
This deletes the model, service, and route files, and removes all injected code from existing files. The grit: markers remain intact for future generation.
How Route Files Work (TanStack Router)
Grit Desktop uses TanStack Router with file-based routing. Each generated resource creates three route files that are automatically discovered by the TanStack Router Vite plugin — no centralized route registry needed.
routes/_layout/products.index.tsxRoute path: /_layout/products/
The list route. Exports Route via createFileRoute. Uses useQuery to fetch data and renders a DataTable with search, pagination, and export buttons.
routes/_layout/products.new.tsxRoute path: /_layout/products/new
The create form route. Uses useNavigate() for navigation after successful creation. No params needed.
routes/_layout/products.$id.edit.tsxRoute path: /_layout/products/$id/edit
The edit form route. Uses Route.useParams() to get the typed $id parameter. Fetches the existing record and pre-fills the form.
Route File Structure
Every generated route file follows this pattern:
import { createFileRoute } from "@tanstack/react-router";export const Route = createFileRoute("/_layout/products/")({component: ProductsPage,});function ProductsPage() {// ... list page with DataTable, search, pagination}
For the edit route, Route.useParams() provides type-safe access to the $id parameter:
import { createFileRoute, useNavigate } from "@tanstack/react-router";export const Route = createFileRoute("/_layout/products/$id/edit")({component: EditProductPage,});function EditProductPage() {const { id } = Route.useParams();const navigate = useNavigate();// Fetch product by ID, pre-fill form// On save: navigate({ to: "/products" })}
Key differences from React Router:
- No centralized routes — route files are auto-discovered by the Vite plugin. Adding a resource just creates files.
- Type-safe params —
Route.useParams()returns typed params scoped to the current route, not a genericuseParams(). - Object-based navigate — use
navigate({ to: "/products" })instead ofnavigate("/products"). - Hash history — desktop apps use
createHashHistory()so routing works from disk without a web server.