The Minimalist's Guide to Building a Production-Ready TODO App with Go and PostgreSQL
Share this article
The Art of Minimalism in Backend Development
In an era of bloated tech stacks, developer Erik Schluntz recently challenged conventions by building a fully functional TODO application using only PostgreSQL and Go—with zero JavaScript frameworks. His experiment proves that sometimes the simplest solutions are the most powerful, especially when leveraging PostgreSQL's advanced features and Go's pragmatic design.
The Two-Table Architecture
Schluntz's schema epitomizes simplicity:
CREATE TABLE users (id TEXT PRIMARY KEY);
CREATE TABLE tasks (
id TEXT PRIMARY KEY,
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
completed BOOLEAN NOT NULL,
position DOUBLE PRECISION NOT NULL GENERATED ALWAYS AS (...) STORED
);
The magic lies in PostgreSQL's generated columns for fractional indexing. Unlike traditional integer-based sorting, this approach allows infinite insertions without costly renumbering operations. Tasks get positioned via midpoint calculations between existing items—a nod to collaborative editing algorithms.
Type-Safe SQL with sqlc
Rather than wrestling with ORMs, Schluntz adopted sqlc to generate type-safe Go code from SQL queries:
// sqlc-generated method
func (q *Queries) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error) {
row := q.db.QueryRowContext(ctx, createTask, arg.ID, arg.UserID, arg.Title)
var task Task
err := row.Scan(&task.ID, &task.UserID, &task.Title, &task.Completed, &task.Position)
return task, err
}
This eliminated manual struct mapping while maintaining explicit control over queries—a balance rarely struck in ORM-heavy workflows.
Deployment Simplicity with Fly.io
The entire stack deploys as two services on Fly.io:
1. A PostgreSQL cluster
2. The Go binary handling HTTP routing
Fly's built-in secrets management and global load balancing transformed deployment from a multi-hour ordeal to a fly launch command. No Dockerfiles, no Kubernetes manifests—just immediate production readiness.
Why This Approach Resonates
- Database as Engine: PostgreSQL's generated columns and cascading deletes handled logic traditionally dumped into application code.
- Go's Pragmatism: Native HTTP/SQL support reduced dependencies to just
sqlcandpq. - Cloud-Native Minimalism: Fly.io abstracted infrastructure without imposing framework bloat.
Schluntz completed the project in hours—not days—proving that skipping trendy frameworks can accelerate delivery. As he notes: "Sometimes the ‘boring’ stack is the most revolutionary."
For developers drowning in over-engineering, this experiment is a masterclass in leveraging mature technologies to their fullest. The result isn’t just a TODO app—it’s a manifesto for intentional simplicity.
Source: Erik Schluntz's blog post