New to Go? Read This First
Version: 1.0 | Written 2026-09-16
You don't know Go yet — this page is the minimum vocabulary to follow the backend lessons without stalling on syntax. Not a full tutorial. Skim it once, then come back whenever a lesson uses a word from here.
The shape of a Go file
package services
import (
"context"
"github.com/medyzen-health/medyzen-backend/internal/order/models"
)
type OrderService struct {
repo Repository
}
func (s *OrderService) GetByID(ctx context.Context, id int64) (*models.Order, error) {
return s.repo.FindByID(ctx, id)
}package— every file belongs to one, matching its folder.internal/order/services/*.gois allpackage services.import— other packages this file uses, by their full module path.type X struct { ... }— a data shape, like a class with only fields, no methods attached directly.func (s *OrderService) GetByID(...)— a method. The(s *OrderService)part is the "receiver": this function is attached toOrderService. Read it as "OrderService has a method called GetByID".(*models.Order, error)— Go functions that can fail return two values: the result, and an error. There are no exceptions to catch — every call you make, you check the second value.
Reading a function signature
func CreateOrder(ctx context.Context, patientID int64, items []CartItem) (*Order, error)Left to right: name, then each parameter as name Type, then what it returns in parentheses. []CartItem means "a slice (list) of CartItem". *Order means "a pointer to an Order" — see below.
context.Context — you'll see it everywhere
Almost every function that does I/O (database, HTTP, Redis) takes a ctx context.Context as its first argument. It carries two things: a deadline/cancellation signal (if the caller gives up or times out, everything downstream stops), and small pieces of request-scoped data (like the logged-in user, via internal/pkg/ctxkeys). You don't need to understand its internals — just always pass the ctx you were given down to the next call, don't create a new empty one.
Pointers (*Order vs Order)
Order is a value — passing it copies the whole struct. *Order is a pointer — passing it shares the same struct in memory. Two rules that cover 90% of what you'll read:
- A method with receiver
(s *OrderService)can modifys's fields;(s OrderService)gets its own copy and any change is thrown away when the method returns. - A function returning
(*Order, error)returnsnil, erron failure —nilis Go's "no pointer here", checked before you touch the result:if err != nil { return err }first, then use the order.
Errors are values, not exceptions
order, err := repo.FindByID(ctx, id)
if err != nil {
return nil, err
}There's no try/catch. Every call that can fail returns an error as its last value, and the very next lines almost always check it. When you read a long function, the if err != nil blocks are the failure branches — skip past them on a first read to see the happy path, then re-read for what can go wrong.
Goroutines and channels (background workers)
go someFunc() starts someFunc running concurrently — it doesn't block the caller. The backend's background workers (Day 5) are started this way at boot and keep running for the life of the process. A chan (channel) is how goroutines signal each other — commonly you'll see done := make(chan struct{}) and later close(done), which is a goroutine saying "I've finished" to whatever is waiting with <-done.
Structs, interfaces, and "handler → service → repository"
- A struct is data:
type Order struct { ID int64; Status string }. - An interface is a contract: a list of method signatures with no implementation.
type Repository interface { FindByID(ctx, id) (*Order, error) }. Any struct that has those methods satisfies the interface automatically — noimplementskeyword. - The backend's layering (Day 1) uses interfaces so a
servicedepends on aRepositoryinterface, not a concrete database struct — a test can swap in a fake that satisfies the same interface.
GORM (the database library)
GORM maps Go structs to Postgres tables. db.Where("id = ?", id).First(&order) reads roughly as SQL. The ? is a placeholder — GORM fills it in safely, which is why raw string concatenation into a query is the thing to avoid (see the SQL-injection trap in Day 2).
What you can skip for now
Generics ([T any]), Go's build tags, and reflection show up in a handful of places (tests, the dev-tagged build) but are not needed to follow the request-handling code. If a lesson hits one, it says so.
A five-minute glossary
| Term | Means |
|---|---|
nil | Go's null/empty — for pointers, slices, maps, interfaces, channels, errors |
slice []T | A growable list of T |
map map[K]V | A dictionary, keyed by K |
defer | Runs a statement right before the function returns, in reverse order — used for cleanup like closing a file |
panic / recover | Go's rare, exceptional-only crash + catch; the middleware chain's Recovery catches panics so one bad request doesn't kill the process |
struct{}{} | An empty struct — zero memory, used as a channel's "just a signal" type |