Project setup
Scaffold a Railbase app, understand the layout, and run the dev loop.
Updated
Railbase isn't only something you run — it's something you build on. You define your data model in Go, and Railbase gives you a REST + realtime API, auth, an admin console, and a typed client for free. This section is the developer track.
Scaffold a project
railbase init myapp --template basic \
--railbase-source /path/to/railbase # pre-release: pin the local core checkout
cd myapp
go mod tidy
Important
Pre-release: github.com/railbase/railbase isn't published yet, so
--railbase-source (or RAILBASE_LOCAL_PATH) is required. It writes
both replace directives into the generated go.mod — one for the core
and one for the (also-unpublished) github.com/railbase/vault it depends on,
assuming a vault/ checkout next to the railbase one. replace directives
are not inherited from dependencies, so if your vault checkout lives
elsewhere and go mod tidy fails with "github.com/railbase/vault@v0.0.0 …
404", point the second replace at the right path:
replace github.com/railbase/vault => /path/to/vault
railbase init generates a complete, buildable project:
myapp/
├── cmd/myapp/main.go # entry point (your binary)
├── schema/main.go # your data model (Go DSL)
├── pb_hooks/example.pb.js# server-side JS hooks
├── railbase.yaml # project config
├── webembed/ # optional embedded SPA
├── Makefile
├── go.mod
└── pb_data/ # vault + .secret (created on init)
The templates layer on each other: auth-starter = basic + auth, fullstack
= auth-starter + a web frontend.
Tip
The project directory and Go module both default to the <name> you pass.
Override the module path with --module github.com/you/myapp while keeping the
directory name.
The entry point
cmd/myapp/main.go is a thin main that hands control to Railbase and gives you
a hook to extend the app before the server starts:
package main
import (
_ "myapp/schema" // blank import: its init() registers your collections
"github.com/go-chi/chi/v5"
"github.com/railbase/railbase/pkg/railbase"
"github.com/railbase/railbase/pkg/railbase/cli"
"github.com/railbase/railbase/pkg/railbase/hooks"
)
func main() {
cli.ExecuteWith(func(app *railbase.App) {
// Runs after the app is built, before Run() starts. Safe here:
// Go hooks (the registry is lazy).
app.GoHooks().OnRecordBeforeCreate("posts",
func(c *hooks.Context, ev *hooks.RecordEvent) error {
// ... validate, mutate ev.Record, etc.
return nil // or an error / hooks.ErrReject → 400
})
// Jobs / JobsStore / Realtime / EventBus are wired during
// App.Run and are nil at this point — register against them
// from OnBeforeServe, which fires after every subsystem is up:
app.OnBeforeServe(func(r chi.Router) {
app.Jobs().Register("report.generate", generateReport)
app.EventBus().Subscribe("record.changed", 256, onRecordChanged)
r.Get("/api/myapp/stats", statsHandler(app))
})
})
}
Useful app seams: OnBeforeServe(func(chi.Router)), GoHooks(),
ServeStaticFS(path, fs), Jobs(), JobsStore(), Realtime(),
EventBus(), Pool(). Everything except GoHooks/OnBeforeServe/
ServeStaticFS returns nil before Run — touch those only from inside
an OnBeforeServe callback (or later, e.g. in hooks and handlers).
First run
go build ./cmd/myapp
./myapp migrate diff initial_schema # generate the first migration from your DSL
./myapp migrate up # apply it
./myapp serve # http://localhost:8095 (admin at /_/)
The scaffolded railbase.yaml ships with runtime.dev: true, so these commands
unlock the vault with the development key out of the box — no RAILBASE_VAULT_PASSWORD
needed. (Remove that line and set a real password before deploying; see
Installation.) If you ever clear it and
forget, migrate/serve exit with "no vault password configured" — re-add it or
prefix the command with RAILBASE_DEV=true.
Create an admin with ./myapp admin create you@example.com and sign in. See
Defining your schema next.
The dev loop
For active development, railbase dev runs the backend and a frontend dev server
under one Ctrl-C, waits for /readyz, and (optionally) regenerates the TypeScript
SDK on schema changes:
railbase dev --web ./web --web-cmd "npm run dev" \
--watch-schema ./schema --sdk-out web/src/client
Logs are interleaved and prefixed [api] / [web].