4.5 KiB
Go Beginner Guide for This Backend
This is not a full Go course. It explains only the Go ideas you need to read this project.
1. How a Go project starts
This repo has a go.mod file:
module github.com/arkie/ark-database
That module name is used in imports:
import "github.com/arkie/ark-database/internal/config"
The executable starts at:
cmd/server/main.go
In Go, a runnable program has:
package main
func main() {
// program starts here
}
2. Packages
Every .go file begins with a package name:
package handlers
Files in the same folder usually share the same package and can call each other directly.
Example: files in internal/handlers/ all say package handlers, so public.go can call writeJSON from util.go.
3. Imports
Go imports are explicit:
import (
"net/http"
"github.com/go-chi/chi/v5"
)
If an import is unused, Go compilation fails. Run gofmt -w . after edits.
4. Structs
A struct is an object/data shape.
Example from internal/config/config.go:
type Config struct {
Addr string
DatabaseURL string
JWTSecret string
}
JSON field names are controlled by tags:
type ResourceDTO struct {
CategoryID int `json:"categoryId"`
}
The Go field is CategoryID, but JSON output is categoryId.
5. Functions with errors
Go commonly returns (value, error):
pool, err := db.Connect(ctx, cfg.DatabaseURL)
if err != nil {
log.Fatal(err)
}
Read this as:
- Try connecting to DB.
- If
erris not nil, stop. - Otherwise use
pool.
6. Short variable declaration :=
This creates a new variable:
cfg := config.Load()
This assigns to an existing variable:
cfg = config.Load()
Most code here uses := inside functions.
7. HTTP handlers
A Go HTTP handler usually looks like:
func ListCategories(w http.ResponseWriter, r *http.Request) {
// w writes response
// r reads request
}
w= response writer.r= request.
Route registration in main.go:
r.Get("/categories", handlers.ListCategories)
Because this is inside r.Route("/api", ...), the full URL is:
GET /api/categories
8. Middleware
Middleware wraps a request before it reaches the final handler.
Example in main.go:
r.Use(middleware.Logger)
This logs requests.
Admin auth is also middleware:
r.Use(handlers.AdminAuth(cfg.JWTSecret))
If token is bad, it returns 401 unauthorized before reaching the admin handler.
9. Context
Context carries request-scoped values and cancellation.
This project stores the DB pool in request context:
r.Use(func(next http.Handler) http.Handler {
return handlers.WithPool(next, pool)
})
Handlers retrieve it:
pool := poolFrom(r)
So most handlers do not receive the DB pool as a direct function parameter.
10. Database queries
This project uses pgx.
One row:
err := pool.QueryRow(ctx, `SELECT id FROM admins WHERE email = $1`, email).Scan(&id)
Many rows:
rows, err := pool.Query(ctx, `SELECT id, title FROM resources`)
defer rows.Close()
for rows.Next() {
rows.Scan(&id, &title)
}
PostgreSQL parameters use $1, $2, etc. This protects from SQL injection when used correctly.
11. Pointers and nil
You will see *string and *time.Time:
var pubAt *time.Time
This allows SQL NULL to become Go nil.
Code checks before using it:
if pubAt != nil {
s := pubAt.UTC().Format(time.RFC3339)
dto.PublishedAt = &s
}
12. defer
defer runs later when the current function returns.
Examples:
defer pool.Close()
defer rows.Close()
defer r.Body.Close()
Use it for cleanup.
13. JSON helpers in this project
Write JSON response:
writeJSON(w, map[string]any{"ok": true})
Read JSON request body:
var req loginReq
if err := jsonDecode(r, &req); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
14. Common edit workflow
After changing .go files:
gofmt -w .
go test ./...
go test ./... also compiles all packages, even if there are no test files.
15. Recommended reading order
README.mdcmd/server/main.gointernal/config/config.gointernal/handlers/public.gointernal/handlers/admin.gointernal/handlers/wallet_auth.gomigrations/001_init.sql
When you see a function you do not understand, search for its definition:
rg "func FunctionName" .