16 - Put It All Together
📋 Jump to Takeaways🎁 You've collected fifteen lessons' worth of pieces. What does a real, deployable Go service look like when all of them snap together into one project?
You've learned project structure, configuration, logging, routing, middleware, error handling, databases, templates, embedding, graceful shutdown, profiling, and deployment. Now let's see the complete bookmarks project, every file, wired together, ready to run.
This lesson has no new concepts. It's the finished product. Use it as a reference when something doesn't work, or clone it to start fresh.
Project Overview
A bookmarks app with two interfaces:
- JSON API:
GET,POST,DELETEon/api/bookmarksfor programmatic access - HTML UI: a server-rendered page at
/where you can add and delete bookmarks in the browser
Both share the same data layer. One store, two presentations.
Directory Structure
bookmarks/
├── main.go # Entry point: config, routes, graceful shutdown
├── config.go # Environment variables + flag overrides
├── model.go # Bookmark struct
├── store.go # Database layer (database/sql + lib/pq)
├── handler.go # JSON API handlers
├── handler_html.go # HTML handlers (list, create, delete)
├── middleware.go # Logger, Recovery, RequestID, CORS, Chain
├── templates/
│ ├── layout.html # Base HTML layout
│ └── list.html # Bookmarks page with form
├── static/
│ └── style.css # Minimal styles
├── go.mod
├── go.sum
├── .env # Local environment variables
├── Makefile # Build, run, test, lint commands
├── Dockerfile # Multi-stage build
├── docker-compose.yml # Postgres + API
└── .dockerignoreLesson-by-Lesson Recap
Every lesson added one piece. This is the assembled result:
| File | Lesson(s) | What it does |
|---|---|---|
main.go |
Project Structure, Graceful Shutdown | Loads config, opens the database, creates the store, parses templates, registers routes, wraps the mux in Chain, and starts the server with graceful shutdown |
config.go |
Configuration | Reads PORT, DATABASE_URL, and LOG_LEVEL from environment variables with sensible defaults. Flags can override port and log level |
model.go |
Project Structure | Defines the Bookmark struct, shared by the store, API handlers, and templates |
store.go |
Database | Opens a *sql.DB with pool settings, auto-creates the table on startup, and provides Create, GetByID, List, and Delete. All methods take context.Context |
handler.go |
HTTP Routing, Error Handling | JSON API: writeJSON/writeError helpers plus list, create, get, and delete handlers. Each is a closure that captures the store |
handler_html.go |
Templates | HTML handlers: list renders the template, create reads r.FormValue and redirects (Post/Redirect/Get), delete takes the ID from the path and redirects |
middleware.go |
Middleware, Structured Logging | Provides RequestID, Recovery, CORS, Logger, and the Chain function that wraps them in order |
templates/ |
Templates | Go's html/template with a layout + page pattern. The list page has an add form and per-item delete buttons |
Makefile, Dockerfile |
Deployment | Build, run, test, and lint commands plus a multi-stage container image |
Running the Final Project
With Docker Compose (recommended)
docker compose upThis starts Postgres and the API. Open http://localhost:8080.
Without Docker
Start Postgres locally, then:
# Set environment variables
export DATABASE_URL="postgres://user:pass@localhost:5432/bookmarks?sslmode=disable"
export PORT=8080
# Build and run
make runOr use the .env file:
make run # Makefile loads .env automaticallyTest the API
# Create a bookmark
curl -X POST http://localhost:8080/api/bookmarks \
-H "Content-Type: application/json" \
-d '{"url": "https://go.dev", "title": "Go"}'
# List all bookmarks
curl http://localhost:8080/api/bookmarks
# Delete a bookmark
curl -X DELETE http://localhost:8080/api/bookmarks/1Or just open http://localhost:8080 in your browser and use the form.
Key Takeaways
- A production Go service is a handful of files, each with a clear responsibility
- The standard library handles routing, middleware, templates, database access, and graceful shutdown, no frameworks needed
- Separate your JSON API from your HTML handlers. They share the store but serve different clients
- Use
docker composeto run the full stack locally with one command - The complete source files are in the examples below. Copy them to start your own project