16 - Put It All Together

📋 Jump to Takeaways

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.

The Project

A bookmarks app with two interfaces:

  • JSON APIGET, POST, DELETE on /api/bookmarks for 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
└── .dockerignore

How It Connects

The flow through the codebase:

  1. main.go loads config, opens the database, creates the store, parses templates, registers all routes, wraps the mux in middleware, and starts the server with graceful shutdown.

  2. config.go reads PORT, DATABASE_URL, and LOG_LEVEL from environment variables with sensible defaults. Flags can override port and log level.

  3. model.go defines the Bookmark struct — shared by the store, API handlers, and templates.

  4. store.go opens a *sql.DB connection with pool settings, auto-creates the table on startup, and provides Create, GetByID, List, and Delete methods. All methods take context.Context for cancellation.

  5. handler.go has the JSON API: writeJSON/writeError helpers, and handlers for list, create, get, and delete. Each handler is a closure that captures the store.

  6. handler_html.go has the HTML handlers: list renders the template, create reads r.FormValue and redirects (Post/Redirect/Get), delete takes the ID from the path and redirects.

  7. middleware.go provides RequestID, Recovery, CORS, Logger, and the Chain function that wraps them in order.

  8. templates/ uses Go's html/template with a layout + page pattern. The list page includes an add form and per-item delete buttons.

Running It

docker compose up

This 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 run

Or use the .env file:

make run   # Makefile loads .env automatically

Test 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/1

Or just open http://localhost:8080 in your browser and use the form.

What Each Lesson Contributed

File Lesson
main.go Project Structure, Graceful Shutdown
config.go Configuration
middleware.go Middleware, Structured Logging
handler.go HTTP Routing, Error Handling
handler_html.go Templates
store.go Database
model.go Project Structure
templates/ Templates
Makefile, Dockerfile Deployment

Every lesson added a piece. This is the assembled result.

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 compose to run the full stack locally with one command
  • The complete source files are in the examples below — copy them to start your own project

📝 Ready to test your knowledge?

Answer the quiz below to mark this lesson complete.

© 2026 ByteLearn.dev. Free courses for developers. · Privacy