282 lines
4.5 KiB
Markdown
282 lines
4.5 KiB
Markdown
# 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:
|
|
|
|
```go
|
|
module github.com/arkie/ark-database
|
|
```
|
|
|
|
That module name is used in imports:
|
|
|
|
```go
|
|
import "github.com/arkie/ark-database/internal/config"
|
|
```
|
|
|
|
The executable starts at:
|
|
|
|
```text
|
|
cmd/server/main.go
|
|
```
|
|
|
|
In Go, a runnable program has:
|
|
|
|
```go
|
|
package main
|
|
|
|
func main() {
|
|
// program starts here
|
|
}
|
|
```
|
|
|
|
## 2. Packages
|
|
|
|
Every `.go` file begins with a package name:
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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`:
|
|
|
|
```go
|
|
type Config struct {
|
|
Addr string
|
|
DatabaseURL string
|
|
JWTSecret string
|
|
}
|
|
```
|
|
|
|
JSON field names are controlled by tags:
|
|
|
|
```go
|
|
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)`:
|
|
|
|
```go
|
|
pool, err := db.Connect(ctx, cfg.DatabaseURL)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
```
|
|
|
|
Read this as:
|
|
|
|
1. Try connecting to DB.
|
|
2. If `err` is not nil, stop.
|
|
3. Otherwise use `pool`.
|
|
|
|
## 6. Short variable declaration `:=`
|
|
|
|
This creates a new variable:
|
|
|
|
```go
|
|
cfg := config.Load()
|
|
```
|
|
|
|
This assigns to an existing variable:
|
|
|
|
```go
|
|
cfg = config.Load()
|
|
```
|
|
|
|
Most code here uses `:=` inside functions.
|
|
|
|
## 7. HTTP handlers
|
|
|
|
A Go HTTP handler usually looks like:
|
|
|
|
```go
|
|
func ListCategories(w http.ResponseWriter, r *http.Request) {
|
|
// w writes response
|
|
// r reads request
|
|
}
|
|
```
|
|
|
|
- `w` = response writer.
|
|
- `r` = request.
|
|
|
|
Route registration in `main.go`:
|
|
|
|
```go
|
|
r.Get("/categories", handlers.ListCategories)
|
|
```
|
|
|
|
Because this is inside `r.Route("/api", ...)`, the full URL is:
|
|
|
|
```text
|
|
GET /api/categories
|
|
```
|
|
|
|
## 8. Middleware
|
|
|
|
Middleware wraps a request before it reaches the final handler.
|
|
|
|
Example in `main.go`:
|
|
|
|
```go
|
|
r.Use(middleware.Logger)
|
|
```
|
|
|
|
This logs requests.
|
|
|
|
Admin auth is also middleware:
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
r.Use(func(next http.Handler) http.Handler {
|
|
return handlers.WithPool(next, pool)
|
|
})
|
|
```
|
|
|
|
Handlers retrieve it:
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
err := pool.QueryRow(ctx, `SELECT id FROM admins WHERE email = $1`, email).Scan(&id)
|
|
```
|
|
|
|
Many rows:
|
|
|
|
```go
|
|
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`:
|
|
|
|
```go
|
|
var pubAt *time.Time
|
|
```
|
|
|
|
This allows SQL `NULL` to become Go `nil`.
|
|
|
|
Code checks before using it:
|
|
|
|
```go
|
|
if pubAt != nil {
|
|
s := pubAt.UTC().Format(time.RFC3339)
|
|
dto.PublishedAt = &s
|
|
}
|
|
```
|
|
|
|
## 12. `defer`
|
|
|
|
`defer` runs later when the current function returns.
|
|
|
|
Examples:
|
|
|
|
```go
|
|
defer pool.Close()
|
|
defer rows.Close()
|
|
defer r.Body.Close()
|
|
```
|
|
|
|
Use it for cleanup.
|
|
|
|
## 13. JSON helpers in this project
|
|
|
|
Write JSON response:
|
|
|
|
```go
|
|
writeJSON(w, map[string]any{"ok": true})
|
|
```
|
|
|
|
Read JSON request body:
|
|
|
|
```go
|
|
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:
|
|
|
|
```bash
|
|
gofmt -w .
|
|
go test ./...
|
|
```
|
|
|
|
`go test ./...` also compiles all packages, even if there are no test files.
|
|
|
|
## 15. Recommended reading order
|
|
|
|
1. `README.md`
|
|
2. `cmd/server/main.go`
|
|
3. `internal/config/config.go`
|
|
4. `internal/handlers/public.go`
|
|
5. `internal/handlers/admin.go`
|
|
6. `internal/handlers/wallet_auth.go`
|
|
7. `migrations/001_init.sql`
|
|
|
|
When you see a function you do not understand, search for its definition:
|
|
|
|
```bash
|
|
rg "func FunctionName" .
|
|
```
|