The pieces every Go API starter ends up with, a minimal standard library skeleton you can copy, and a starter that comes with Postgres, tests and deployment to AWS or GCP already wired.
Every Go backend starter converges on the same parts: an entry point, configuration, logging, graceful shutdown, a database with migrations, tests and a way to ship it. The HTTP router is the smallest of these decisions, and the database, test and deployment wiring is where a new project spends its first week and where hand-copied boilerplate drifts between services.
This guide lists what a Go boilerplate should include, gives a standard library skeleton you can start from, and shows the Encore (a tool that works alongside your framework) starter that comes with Postgres, tests and deployment to your own AWS or GCP account.
The Go team's guide to organizing a Go module recommends starting with packages at the module root and adding cmd/ for multiple commands and internal/ for packages that other modules must not import. Deep trees of pkg/, domain/ and adapters/ created on day one mostly hold one file each, and a newcomer, or a coding agent, has to open five folders to follow a single request.
Read configuration from the environment once at startup, as described in the twelve-factor app, and fail fast when a required value is missing. Secrets do not belong in a committed .env file, which is the first thing an AI coding agent with repository access reads.
Starting with Go 1.22, net/http routes by method and path pattern, so a small API needs no router dependency. The skeleton below logs with log/slog, stops accepting requests on SIGTERM with signal.NotifyContext and drains in-flight requests before exiting, which is what container platforms expect during a rolling deploy:
package main
import (
"context"
"encoding/json"
"errors"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
log := slog.New(slog.NewJSONHandler(os.Stdout, nil))
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
log.Info("get user", "id", r.PathValue("id"))
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"id": r.PathValue("id")})
})
srv := &http.Server{
Addr: ":" + getenv("PORT", "8080"),
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Error("server failed", "err", err)
os.Exit(1)
}
}()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Error("shutdown", "err", err)
}
}
func getenv(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
The skeleton handles the process lifecycle but not the parts that take longer: a Postgres instance for local development, a migration runner, a test database that is reset between runs, a Dockerfile, a CI workflow, and the cloud resources, networking and permissions the service needs in production. Each of those is another file to keep in sync across every service built from the same template.
Run tests against a real Postgres, since mocked database layers pass while the SQL is wrong and the query that fails in production is the one the mock accepted. In CI, run go test ./..., go vet ./... and govulncheck, which reports only vulnerabilities in code paths your module actually calls. Pin the Go version with the go and toolchain lines in go.mod, so local builds and CI compile with the same release.
Run in your terminal to get started locally.
Create an app from the URL shortener template, which is a small Go API with a Postgres database:
Encore is a tool that works alongside your framework for TypeScript and Go, with a platform that provisions and deploys into your own AWS or GCP account. The starter is ordinary Go: the url service declares its endpoints with //encore:api comments and its database in code, and encore run starts Postgres locally, applies the migrations in url/migrations and serves the API with a local development dashboard for calling endpoints and reading traces.

The core of the starter's url/url.go:
package url
import (
"context"
"encore.dev/storage/sqldb"
)
type URL struct {
ID string // short-form URL id
URL string // complete URL, in long form
}
//encore:api public method=GET path=/url/:id
func Get(ctx context.Context, id string) (*URL, error) {
u := &URL{ID: id}
err := db.QueryRow(ctx, `
SELECT original_url FROM url
WHERE id = $1
`, id).Scan(&u.URL)
return u, err
}
var db = sqldb.NewDatabase("url", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
The template also includes url/url_test.go, which shortens a URL and reads it back. encore test compiles the app and runs go test with all the usual flags against a test database, as covered in the testing docs.
A second service is a new package that calls the first like a function, and Encore turns the call into an API call between services. This redirect service sends visitors to the long URL:
// redirect/redirect.go
package redirect
import (
"net/http"
"encore.app/url"
"encore.dev"
)
//encore:api public raw method=GET path=/r/:id
func Redirect(w http.ResponseWriter, req *http.Request) {
id := encore.CurrentRequest().PathParams.Get("id")
u, err := url.Get(req.Context(), id)
if err != nil {
http.NotFound(w, req)
return
}
http.Redirect(w, req, u.URL, http.StatusFound)
}
The parts the standard library skeleton left open come from the same code on deploy. Encore builds the services, provisions Postgres as RDS on AWS or Cloud SQL on GCP, runs the services on Fargate, EKS, Cloud Run or GKE, and provisions an IAM role for each service, with a deploy on every push once the GitHub integration is connected. Adding Pub/Sub, cron jobs or object storage later is a declaration in the service that uses it rather than a new section of the boilerplate. For a coding agent, the starter is small enough to read in one pass, and the infrastructure it needs is in the same files as the endpoints.
Best fit: Go teams starting a new API who want Postgres, tests, service-to-service calls and deployment to their own AWS or GCP account working from the first commit.
A module layout with a cmd entry point or service packages, configuration from the environment, structured logging with log/slog, graceful shutdown on SIGTERM, a database with versioned migrations, tests that run against a real database, a Dockerfile or build step, and a CI workflow that runs tests and govulncheck.
The Go team documents module layouts at go.dev/doc/modules/layout, which recommends starting simple with packages at the module root and adding cmd and internal directories when a module has several commands or private packages. The widely shared golang-standards/project-layout repository is a community convention, not an official standard.
Start from the smallest starter that already runs against the database you will use in production, since the time goes into wiring the database, tests and deployment, not into the HTTP router. Generators that produce many folders up front tend to leave empty layers you maintain without using.
Running encore app create my-app --example=url-shortener gives you a url service with Shorten and Get endpoints declared with //encore:api, a Postgres database declared with sqldb.NewDatabase and its migrations, and a test that runs with encore test. encore run starts it locally with Postgres.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.