Creating the Project
Let’s create the project and start it in watch mode:copper run -watch running while you work; it rebuilds and restarts the application whenever you save a Go file under pkg/, and the Vite dev server hot-reloads your React changes.
Defining the Schema
New projects come with an empty first migration. Let’s add arockets table to it:
migrations/0001_initial.sql
copper run applies pending migrations each time it starts, so restart it (or run copper migrate) to create the table. Later migrations get their own sequentially numbered files; see Migrations for more.
Scaffolding the Feature Package
Each feature in a Copper application lives in its own package underpkg/. Let’s scaffold one for rockets, along with a queries struct for database access:
pkg/rockets/ with models.go, queries.go, and wire.go, and registers the package with the application. Next, define the model:
pkg/rockets/models.go
db tags map database columns to fields, while the json tags shape the props your React page receives.
Writing Queries
Now let’s add two methods to the scaffoldedQueries struct:
pkg/rockets/queries.go
? regardless of your database; Copper rebinds them to the right style ($1 for Postgres) automatically. The qb.Columns helper (from github.com/gocopper/copper/csql/qb) renders the column list from the model’s db: tags, so the query never drifts from the struct; see Query Builder for more.
Adding Routes
Next, scaffold a router for the package:pkg/rockets/router.go and registers it in pkg/app/handler.go. The scaffolded router only receives a logger, but our handlers also need the queries and the Inertia renderer. Add them to NewRouterParams, and Copper’s dependency injection provides them:
pkg/rockets/router.go
pkg/rockets/router.go
ReadForm decodes the request and enforces the valid tags. If validation fails, it flashes the validation error and returns false, and the redirect sends the user back to the form. This POST-then-redirect shape is the standard Inertia flow; see Inertia for the details.
Building the Page
TheComponent: "rockets" above resolves to web/src/pages/rockets.tsx. Let’s create it:
web/src/pages/rockets.tsx
useForm posts the form and re-renders the page with fresh props after the redirect.
Trying It Out
Open http://localhost:5901/rockets, type a name, and add a rocket. The list updates instantly. If you submit an empty name,valid:"required" rejects it and the redirect brings you back.
You’ve now built a full-stack feature, from the database to React, without writing an API.
Next Steps
- Go deeper on Inertia: flash messages, shared props, partial reloads
- Wrap multi-step writes in Transactions
- Expose the same data as a JSON API
- Add Middleware for auth or logging
- Understand the App Lifecycle behind
copper.New()