Shared infrastructure module for Go web applications: database, authentication, HTTP server, CLI, and deployment tooling.
Build an app once, run it three ways β web server with Google OAuth, local CLI with SQLite, native desktop with Wails β all through the same HTTP handler.
# Prerequisites: install mise (https://mise.jdx.dev), then:
go install github.com/michaelwinser/appbase/cmd/appbase@latest
mkdir myapp && cd myapp
appbase init # creates app.yaml, app.json
mise install # Go, Node, pnpm, codegen toolscd myapp
mise install # set up toolchain
./dev serve # start the web server
./dev test # run testsβββββββββββββββββββββββββββββββββββββββββββββββ
β Your App β
β Handlers β CLI Commands β Stores β
βββββββββββββββββββββββββββββββββββββββββββββββ€
β appbase β
β DB (SQLite/Firestore) β Auth (Google) β
β Router (chi) β Sessions β
β CLI (Cobra) β Config (YAML) β
β Deploy (Cloud Run) β Store (generic) β
βββββββββββββββββββββββββββββββββββββββββββββββ
You write: domain entities, store CRUD, API handlers, frontend. appbase provides: database connections, OAuth login, session management, server scaffolding, CLI framework, deployment scripts.
Every appbase app runs in three modes automatically:
| Mode | Command | Auth | Database |
|---|---|---|---|
| Web server | ./dev serve |
Google OAuth | SQLite or Firestore |
| Local CLI | myapp list |
Automatic (dev@localhost) | SQLite at ~/.config/myapp/ |
| Remote CLI | myapp --server URL list |
Keychain session | Server's database |
No code changes between modes β identity injection happens at the transport layer.
Every project gets a ./dev script that wraps common tasks:
./dev serve # Start web server (loads secrets from keychain)
./dev build # Build server binary (+ desktop if cmd/desktop/ exists)
./dev test # Run Go tests
./dev codegen # Generate Go server/client + frontend types from openapi.yaml
./dev lint-api # Verify codegen is up to date
./dev deploy # Deploy to Cloud Run
./dev provision email # Full GCP project setup
./dev secret set K V # Store secret in OS keychainThe script sources shared functions from the appbase binary:
eval "$(appbase dev-template)"The recommended pattern uses OpenAPI as the source of truth:
- Define endpoints in
openapi.yaml - Generate Go server interface + client:
./dev codegen - Implement the generated interface in
server.go - Use the generated client in CLI commands and tests
// Generated interface β implement this
type ServerInterface interface {
ListTodos(w http.ResponseWriter, r *http.Request)
CreateTodo(w http.ResponseWriter, r *http.Request)
}
// Your implementation
func (s *TodoServer) ListTodos(w http.ResponseWriter, r *http.Request) {
userID := appbase.UserID(r) // from session/transport
items, _ := s.Store.List(userID)
server.RespondJSON(w, http.StatusOK, items)
}Use store.Collection[T] for automatic SQLite + Firestore support:
type Todo struct {
ID string `store:"id,pk"`
UserID string `store:"user_id,index"`
Title string `store:"title"`
Done bool `store:"done"`
}
coll, _ := store.NewCollection[Todo](db, "todos")
coll.Create(&todo)
coll.Where("user_id", "==", uid).OrderBy("created_at", store.Desc).All()Tables and indexes are auto-created. New fields added to the struct are auto-migrated on existing databases.
// Built-in login page (shows Google sign-in when unauthenticated)
r.Get("/*", app.LoginPage(myHandler))
// In any handler:
userID := appbase.UserID(r)
email := appbase.Email(r)
token := appbase.AccessToken(r) // OAuth token for Google API callsFor apps that need Google API access (Tasks, Calendar, Drive), add scopes and the required GCP API in app.yaml:
auth:
extra_scopes:
- https://www.googleapis.com/auth/tasks
gcp:
apis:
- tasks.googleapis.comThe extra_scopes are requested during OAuth login. The gcp.apis are enabled by ./dev provision.
CLI commands use the generated HTTP client, never direct store access:
httpClient, baseURL, cleanup, _ := appcli.ClientForCommand(cmd, "myapp", app.Handler())
defer cleanup()
client, _ := api.NewClientWithResponses(baseURL, api.WithHTTPClient(httpClient))
resp, _ := client.ListTodosWithResponse(ctx)Flags: --server URL for remote, --local to force local mode, --data PATH for custom data directory.
Set APPBASE_TEST_MODE=true and use X-Test-User header β no OAuth or session setup needed:
func setupTestApp(t *testing.T) http.Handler {
os.Setenv("APPBASE_TEST_MODE", "true")
app, _ := appbase.New(appbase.Config{Name: "myapp", Quiet: true})
// ... register routes ...
return app.Server().Router()
}
h.Run("UC-001", "List todos", func(c *harness.Client) {
c.SetHeader("X-Test-User", "test@example.com")
resp := c.GET("/api/todos")
c.AssertStatus(resp, 200)
})myapp/
βββ .mise.toml # Toolchain (Go, Node, pnpm, codegen)
βββ app.yaml # Config (name, port, auth, environments)
βββ openapi.yaml # API spec (source of truth)
βββ main.go # Server + CLI entry point
βββ internal/app/
β βββ store.go # Entity + store.Collection
β βββ server.go # Implements generated ServerInterface
βββ api/ # Generated (never edit)
β βββ server.gen.go
β βββ client.gen.go
βββ frontend/ # Svelte SPA
β βββ src/lib/api.ts # Typed fetch wrappers
βββ usecases_test.go # API-level tests
βββ dev # Project commands (eval "$(appbase dev-template)")
βββ sandbox # nono sandbox for AI sessions
βββ cmd/desktop/ # Wails desktop entry point (optional)
Uses mise for per-project tool management:
mise install # installs Go, Node, pnpm, oapi-codegen, wailsFor AI-assisted development with Claude Code, the ./sandbox script provides nono-based isolation:
./sandbox claude # Claude Code session with project-only access
appbase sandbox-template # generate a starter sandbox script./dev provision user@example.com # GCP project, APIs, Firestore, OAuth
./dev secret import creds.json # OAuth credentials to keychain
./dev deploy # Cloud Run deploymentProvisioning enables 5 infrastructure APIs automatically. App-specific APIs (Tasks, Calendar, etc.) are declared in app.yaml under gcp.apis and enabled alongside them.
Secrets flow: OS keychain (local) -> GCP Secret Manager (Cloud Run). Never stored as plaintext on disk.
The ./dev script sources shared functions from appbase but your project controls the dispatch. Override any command by adding a case entry before the fallthrough:
#!/bin/sh
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
eval "$(appbase dev-template)"
APP_BINARY_NAME="myapp"
case "${1:-help}" in
# Custom deploy: run appbase deploy, then set up Cloud Scheduler
deploy)
_load_secrets
appbase deploy
setup_scheduler
;;
# Custom provision: run appbase provision, then enable extra APIs
provision)
appbase provision "$2"
gcloud services enable cloudscheduler.googleapis.com --project="$(appbase_project)"
;;
# All other commands use the shared defaults
*) dev_dispatch "$@" ;;
esac
# --- App-specific functions ---
setup_scheduler() {
# Create service account, grant invoker, create scheduler job
# ... app-specific gcloud commands ...
}Key patterns:
_load_secretsβ loads secrets from keychain before deployappbase deployβ the standard Cloud Run deploy (call this first)- Post-deploy steps run after the service is live (scheduler jobs, cache warming, DNS, etc.)
- Pre-provision steps run before or after
appbase provision(extra APIs, custom resources) - App-specific GCP APIs go in
app.yamlundergcp.apisβ provisioned automatically
| Example | Pattern | What it demonstrates |
|---|---|---|
examples/todo/ |
Raw DB | Hand-written routes, direct SQL |
examples/todo-store/ |
store.Collection | Generic persistence, hand-written routes |
examples/todo-api/ |
Full stack | OpenAPI, codegen, Svelte, Wails, Google Tasks sync, CLI |
examples/bookmarks/ |
store.Collection | Richer entity with more fields |
examples/todo-api/ is the reference implementation. Start there.
| Variable | Purpose | Default |
|---|---|---|
PORT |
HTTP server port | 3000 |
STORE_TYPE |
sqlite or firestore |
sqlite |
SQLITE_DB_PATH |
SQLite file path | data/app.db |
GOOGLE_CLIENT_ID |
OAuth client ID | from app.yaml secrets |
GOOGLE_CLIENT_SECRET |
OAuth client secret | from app.yaml secrets |
APPBASE_TEST_MODE |
Enable X-Test-User auth | false |
When working on appbase or an appbase app, these slash commands are available:
| Skill | When to use |
|---|---|
/scaffold-app |
Create a new app from scratch |
/api-first |
Add or modify API endpoints |
/add-entity |
Add a new domain entity |
/review-arch |
Validate against architectural invariants |
/deploy |
Provision and deploy |
/run-tests |
Run and interpret tests |