Build Your First App
From zero to running web application. Every step uses standard Go.
Get Congo
Download a prebuilt binary for your platform from the download page. Unpack it and put it on your PATH:
# macOS / Linux
tar -xzf congo-*.tar.gz
mv congo /usr/local/bin/
congo version
Requires Go 1.25+ to build projects. The binary itself has no dependencies.
Create a Project
congo init myapp
cd myapp
congo dev
Open localhost:5000 in your browser — you'll see a welcome page with live reload.
Edit any template and the browser updates automatically.
This creates a new directory with the full framework vendored inside:
myapp/
internal/ Framework source (yours to read and modify)
application/ HTTP server, routing, templates, middleware
database/ ORM, auto-migration, SQLite/LibSQL engines
frontend/ React islands, esbuild, HMR
assistant/ AI chat, streaming, tool calling
platform/ Cloud server management, Docker, SSH
web/
controllers/ Your request handlers
models/ Your data models
views/ Your HTML templates
layouts/ Page layouts
partials/ Reusable components
static/ CSS, JS, images
main.go Entry point
go.mod
Dockerfile
CLAUDE.md AI context (generated by congo claude)
The framework lives in internal/. It's regular Go code.
No hidden magic, no code generation, no reflection tricks. Open any file and read it.
Add a Controller
Controllers handle HTTP requests and expose methods to templates. Create web/controllers/todos.go:
package controllers
import (
"net/http"
"myapp/internal/application"
"myapp/web/models"
)
func Todos() (string, *TodosController) {
return "todos", &TodosController{}
}
type TodosController struct {
application.BaseController
}
func (c *TodosController) Setup(app *application.App) {
c.BaseController.Setup(app)
http.Handle("GET /todos", app.Serve("todos.html", nil))
http.Handle("POST /todos", app.Method(c, "Create", nil))
}
// Value receiver creates a copy — each request gets its own state.
func (c TodosController) Handle(r *http.Request) application.Controller {
c.Request = r
return &c
}
// Public methods are callable from templates: {{todos.All}}
func (c *TodosController) All() []*models.Todo {
items, _ := models.Todos.All()
return items
}
func (c *TodosController) Create(w http.ResponseWriter, r *http.Request) {
todo := &models.Todo{Title: r.FormValue("title")}
models.Todos.Insert(todo)
c.Redirect(w, r, "/todos")
}
Register it in web/main.go:
application.Serve(views,
application.WithController(controllers.Home()),
application.WithController(controllers.Todos()),
)
The factory function returns a name and controller. The name becomes the template namespace —
todos.All calls the All() method.
Add a Model
Models are Go structs. The ORM creates tables, migrates schemas, and provides type-safe CRUD.
Create web/models/todo.go:
package models
import "myapp/internal/database"
type Todo struct {
database.Model
Title string
Done bool
}
var Todos = database.Manage(DB, new(Todo))
That's it. The table is created on startup. Columns are added automatically when you add struct fields.
database.Model provides ID, CreatedAt, and UpdatedAt.
// Insert — generates UUID, sets timestamps
id, err := models.Todos.Insert(&models.Todo{Title: "Ship it"})
// Get by ID
todo, err := models.Todos.Get(id)
// Search with SQL (PascalCase column names)
done, err := models.Todos.Search("WHERE Done = ?", true)
// Update — auto-updates UpdatedAt
todo.Done = true
err = models.Todos.Update(todo)
// Delete
err = models.Todos.Delete(todo)
IDs are always strings (UUIDs). Add indexes with database.WithUniqueIndex or database.WithIndex.
Write a View
Views are standard Go html/template files with HTMX attributes.
Create web/views/todos.html:
{{template "main.html" .}}
{{define "content"}}
<div class="container mx-auto px-8 py-16 max-w-2xl">
<h1 class="text-3xl font-bold mb-8">Todos</h1>
<form hx-post="/todos" hx-target="body" class="flex gap-2 mb-8">
<input name="title" class="input input-bordered flex-1"
placeholder="What needs doing?" required />
<button class="btn btn-primary">Add</button>
</form>
{{range todos.All}}
<div class="flex items-center gap-3 py-2">
<span>{{.Title}}</span>
</div>
{{end}}
</div>
{{end}}
Controller methods like todos.All are called directly in templates.
HTMX handles form submissions and page updates without writing JavaScript.
Write Tests
Congo uses an in-memory SQLite database when no DB_PATH or DB_URL
is set — which means tests get a fresh database automatically. Test models with standard Go tests:
package models_test
import (
"testing"
"myapp/web/models"
)
func TestTodoInsert(t *testing.T) {
id, err := models.Todos.Insert(&models.Todo{Title: "Ship it"})
if err != nil {
t.Fatal(err)
}
todo, err := models.Todos.Get(id)
if err != nil {
t.Fatal(err)
}
if todo.Title != "Ship it" {
t.Errorf("got %q, want %q", todo.Title, "Ship it")
}
}
go test ./web/models/...
External dependencies have mock providers built in — assistant/providers/mock
for AI features and platform/providers/mock for infrastructure.
No external services needed to run your test suite.
AI-Assisted Development
congo claude
This launches Claude Code with a complete framework reference injected into the session. The AI knows the controller pattern, the model API, the template conventions, and the HTMX patterns. It writes code that fits your project because the framework taught it how.
Build and Deploy
# Build a production binary
congo build
# Deploy to your server
congo launch
congo build compiles your app into a single binary.
congo launch builds a Docker image, ships it to your server,
and starts it with health checks and automatic rollback. Your infrastructure is defined in
infra.json — servers, volumes, services, all in one file.
Generational Development
Every Congo binary carries the complete source tree inside it. Run congo source to extract
a buildable copy — the CLI, the framework packages, the scaffold templates, everything.
congo source ./my-framework
cd my-framework
go build -o my-cli ./cmd
This is how Congo is meant to evolve. Take the source, modify it, ship your own version.
Your CLI carries your changes inside it. When someone runs my-cli source,
they get your fork — and can fork it again. Each generation inherits and builds on the last.
Framework Packages
Congo includes five packages. Use what you need, exclude what you don't with flags like --no-frontend or --no-database.
Every package is linked to its source. Read the implementation, understand the abstractions, modify them if you want.
Stay Updated
Get notified about new releases and updates.