Building a Full App: End-to-End Example
This guide walks through building a bookstore API from scratch. By the end, you’ll have:
- Cookie-based authentication with signup
- Organization-scoped multi-tenancy
- Two related resources:
authorsandbooks(with a foreign key) - Full CRUD endpoints with generated tests
- Tenancy isolation tests proving data can’t leak between organizations
- An OpenAPI spec, docs UI, and TypeScript client
This mirrors the patterns used in ShipQ’s own end-to-end test suite.
Step 0: Prerequisites
Section titled “Step 0: Prerequisites”Make sure you have:
- Go 1.25+ installed
- ShipQ CLI built from source (Installation)
- A database engine available (Postgres, MySQL, or SQLite)
For this walkthrough, we’ll assume Postgres. If you don’t have Postgres, ShipQ will fall back to SQLite automatically.
Step 1: Initialize the Project
Section titled “Step 1: Initialize the Project”mkdir bookstore && cd bookstoreshipq initThis creates:
go.modwith your module nameshipq.iniwith[db]and[typescript]sections.gitignoreconfigured to exclude.shipq/
Step 2: Set Up the Database
Section titled “Step 2: Set Up the Database”# For Postgres:export DATABASE_URL="postgres://localhost:5432/bookstore_dev?sslmode=disable"shipq db setup
# Or just let ShipQ auto-detect (falls back to SQLite if no DB server found):# shipq db setupVerify that shipq.ini now has a database_url:
[db]database_url = postgres://localhost:5432/bookstore_dev?sslmode=disable
[typescript]framework = reacthttp_output = .Step 3: Generate Authentication
Section titled “Step 3: Generate Authentication”shipq authgo mod tidyThis generates:
- Migrations for
organizations,accounts, andsessions - Login/logout/me handlers in
api/auth/ - Auth middleware that protects routes by default
- Tests in
api/auth/spec/
Now add the signup endpoint:
shipq signupgo mod tidyVerify the auth system works:
go test ./api/auth/spec/... -v -count=1You should see all auth tests passing — login, logout, session management, and signup.
Step 4: Configure Multi-Tenancy
Section titled “Step 4: Configure Multi-Tenancy”Open shipq.ini and add the scope setting under [db]:
[db]database_url = postgres://localhost:5432/bookstore_dev?sslmode=disablescope = organization_id
[auth]protect_by_default = true
[typescript]framework = reacthttp_output = .Setting scope = organization_id tells ShipQ to:
- Auto-inject
organization_id:references:organizationsinto every new migration - Generate queries that filter by
organization_id - Generate handlers that extract
organization_idfrom the authenticated user’s context - Generate tenancy isolation tests
Step 5: Create the Authors Table
Section titled “Step 5: Create the Authors Table”shipq migrate new authors name:string bio:textBecause scope = organization_id is set, ShipQ automatically injects organization_id:references:organizations into the migration. The effective column list is:
name:string bio:text organization_id:references:organizationsYou don’t have to type the scope column — it’s injected by the compiler.
Step 6: Create the Books Table
Section titled “Step 6: Create the Books Table”shipq migrate new books title:string isbn:string published:bool author_id:references:authorsAgain, organization_id is auto-injected. This migration creates a books table with:
title(string)isbn(string)published(bool)author_id(foreign key →authors)organization_id(foreign key →organizations, auto-injected)- Plus the standard
id,public_id,created_at,updated_at,deleted_atcolumns
Step 7: Apply All Migrations
Section titled “Step 7: Apply All Migrations”shipq migrate upThis runs the schema compiler, which:
- Discovers all migration files (auth tables + authors + books)
- Builds a canonical
MigrationPlan - Writes
shipq/db/migrate/schema.json - Generates typed schema bindings in
shipq/db/schema/schema.go - Applies the schema to both dev and test databases
After this step, you have typed table and column references available:
schema.Authors.Name(),schema.Authors.Bio(), etc.schema.Books.Title(),schema.Books.AuthorId(), etc.
Step 8: Generate CRUD Endpoints
Section titled “Step 8: Generate CRUD Endpoints”Generate all CRUD operations for both resources:
shipq resource authors allshipq resource books allgo mod tidyFor each resource, this generates:
| File | Method | Route |
|---|---|---|
api/authors/create.go | POST | /authors |
api/authors/get_one.go | GET | /authors/:id |
api/authors/list.go | GET | /authors |
api/authors/update.go | PATCH | /authors/:id |
api/authors/soft_delete.go | DELETE | /authors/:id |
api/books/create.go | POST | /books |
api/books/get_one.go | GET | /books/:id |
api/books/list.go | GET | /books |
api/books/update.go | PATCH | /books/:id |
api/books/soft_delete.go | DELETE | /books/:id |
Plus register.go, query definitions, and tests for each.
Since protect_by_default = true (from shipq auth), all routes require authentication. The generated tests include both authenticated CRUD tests and 401 rejection tests.
Since scope = organization_id is set, all routes are tenant-scoped. The generated tests include tenancy isolation tests.
Step 9: Run the Full Test Suite
Section titled “Step 9: Run the Full Test Suite”go test ./... -v -count=1This runs every generated test, including:
- Auth tests: login, logout, signup, session management
- Authors CRUD tests: create, read, update, delete with valid auth
- Books CRUD tests: create, read, update, delete with valid auth
- 401 tests: verifying unauthenticated requests are rejected
- Tenancy isolation tests: verifying Organization A’s data is invisible to Organization B
The tenancy tests follow this pattern:
- Create User A in Organization A
- Create User B in Organization B
- User A creates a resource (e.g., an author)
- User B tries to read that resource → gets 404 (not 200)
- User B lists resources → gets an empty list (not User A’s data)
If all tests pass, your data isolation is correct by construction.
Step 10: Start the Server
Section titled “Step 10: Start the Server”go run ./cmd/server# or:shipq start serverYour API is now running! In dev mode, visit:
GET /docs— Interactive API documentation with all 10+ endpointsGET /openapi— Raw OpenAPI 3.1 JSON spec
Step 11: Test It Manually
Section titled “Step 11: Test It Manually”Sign up a new user
Section titled “Sign up a new user”curl -c cookies.txt -X POST http://localhost:8080/auth/signup \ -H "Content-Type: application/json" \The response sets a signed session cookie. The -c cookies.txt flag saves it for subsequent requests.
Log in
Section titled “Log in”curl -c cookies.txt -X POST http://localhost:8080/auth/login \ -H "Content-Type: application/json" \Create an author
Section titled “Create an author”curl -b cookies.txt -X POST http://localhost:8080/authors \ -H "Content-Type: application/json" \ -d '{"name": "J.R.R. Tolkien", "bio": "Author of The Lord of the Rings"}'Create a book
Section titled “Create a book”curl -b cookies.txt -X POST http://localhost:8080/books \ -H "Content-Type: application/json" \ -d '{"title": "The Hobbit", "isbn": "978-0547928227", "published": true, "author_id": "<author-public-id>"}'List books
Section titled “List books”curl -b cookies.txt http://localhost:8080/booksWhat You’ve Built
Section titled “What You’ve Built”Let’s take stock of everything ShipQ generated from just a handful of commands:
Commands you typed
Section titled “Commands you typed”shipq initshipq db setupshipq authshipq signupshipq migrate new authors name:string bio:textshipq migrate new books title:string isbn:string published:bool author_id:references:authorsshipq migrate upshipq resource authors allshipq resource books allshipq handler compileWhat was generated
Section titled “What was generated”- ✅ Database schema: 5 tables (organizations, accounts, sessions, authors, books) with proper foreign keys, indexes, and soft-delete support
- ✅ Cookie-based authentication: signup, login, logout, session management
- ✅ 10 CRUD endpoints: full create/read/update/delete/list for both authors and books
- ✅ Multi-tenancy: every query scoped to
organization_id, enforced at the SQL level - ✅ Typed query runners: compile-time type-safe database access for all operations
- ✅ Comprehensive tests: auth tests, CRUD tests, 401 tests, tenancy isolation tests
- ✅ OpenAPI 3.1 spec: auto-generated from handler metadata
- ✅ API docs UI: interactive documentation at
/docs - ✅ Admin UI: for manual testing and exploration
- ✅ TypeScript HTTP client: fully typed, ready for your frontend
- ✅ React hooks: data-fetching hooks for every endpoint
- ✅ Self-contained Go project: no runtime dependency on ShipQ
Going Further
Section titled “Going Further”From here, you can:
- Add file uploads:
shipq files→ S3-compatible managed file storage - Add background jobs:
shipq workers→ Redis job queue + Centrifugo WebSocket channels - Add OAuth:
shipq auth googleorshipq auth githubfor social login - Add email verification:
shipq emailfor email verification and password reset (requires workers) - Write custom queries: add query definitions in
querydefs/using the PortSQL DSL - Write custom handlers: add handler packages in
api/and register them with the handler registry - Deploy:
shipq dockerto generate production Dockerfiles
Iteration Cheat Sheet
Section titled “Iteration Cheat Sheet”As you develop, here’s the command to run when things change:
| What changed | Command |
|---|---|
| New or edited migration | shipq migrate up |
| New or edited query definition | shipq db compile |
| New or edited handler | shipq handler compile |
| New table + full CRUD | shipq migrate new <table> ... → shipq resource <table> all |
| Channel definitions changed | shipq workers compile |
| Everything (nuclear option) | shipq migrate reset → regenerate resources → shipq handler compile |