Modules vs Composer
Composer is one of the best things that ever happened to PHP. Before it, "dependency management" meant a lib/ folder and a prayer. So the good news: Go's equivalent, modules, will feel familiar. The interesting part is the handful of places where Go made a very different call.
go.mod is your composer.json
Every Go project has a go.mod at its root. It says three things: what this module is called, which Go version it targets, and what it depends on.
module github.com/you/departures go 1.26 require github.com/spf13/cobra v1.9.1
That first line, the module path, matters more than a Packagist name does. It's the prefix for every import in your project, and for public code it's usually where the code lives: github.com/you/departures. There's no central registry like Packagist. Go fetches modules through a public proxy (proxy.golang.org) using that path.
The everyday commands
You'll rarely edit go.mod by hand:
- Start a project:
go mod init github.com/you/departuresis yourcomposer init. It writes a three-linego.modand nothing else. No questionnaire. (That's the command from 0.2, now with a name that means something.) - Add a dependency:
go get github.com/google/uuid, or just import it in your code and rungo mod tidy. - Install: there's no
composer installstep.go buildandgo testdownload whatever's missing into a shared module cache (~/go/pkg/mod), not into your project. - Clean up:
go mod tidyadds anything you import but haven't required, and removes anything you no longer use. Run it whenever you change imports. It's thecomposer dump-autoloadyou'll actually remember to run.
Where's the lock file?
There isn't one, and that's deliberate.
Composer lets you write ranges like ^7.9, resolves them to the newest matching versions, and records the result in composer.lock. Without the lock file, two installs a week apart could pull different code.
Go doesn't do ranges. go.mod lists one version per dependency, and Go uses minimal version selection: for each module it picks the highest version that anything in your build explicitly asks for, and never a newer one just because it exists. So the same go.mod always builds the same code, today or in two years. The lock file's job is done by go.mod itself.
| composer update, Fri 16:55 | Updating dependencies (including require-dev)… |
|---|---|
| Fri 16:58 | 147 packages updated. Tests: mostly green. |
| Mon 09:00 | "Did anyone change anything?" |
| go build, any day | Same versions as yesterday. Go only upgrades when you ask. |
Mind the gap
go.sum is not composer.lock. It doesn't choose versions. It stores a checksum for every module your build uses, so Go can prove the code it downloaded is byte-for-byte what everyone else got. Commit both go.mod and go.sum, and never delete go.sum to "fix" something.
Imports instead of an autoloader
PHP's autoloader maps class names to files: PSR-4 turns App\Stations\Station into src/Stations/Station.php. Go has no autoloader because it doesn't need one. An import path names a directory, and everything in that directory is one package:
"fmt","net/http": standard library. No domain, nothing to download."github.com/you/departures/internal/stations": your module path plus a directory in your project. (internalis a special name: Go won't let any other module import what's under it, so it's the place for code that's nobody else's business.)"github.com/google/uuid": someone else's module.
You'll need fewer packages
A fresh Laravel install pulls dozens of packages into vendor/. A typical Go service has a handful, because the standard library already covers HTTP servers and clients, JSON, testing, crypto, templating, logging and more. There's a Go proverb about it: a little copying is better than a little dependency. Before you go get something, check whether the standard library already does it. The exercise is built around that habit.
Recap
go.moddeclares your module path, Go version and dependencies.go getadds them,go mod tidykeeps them honest.- No version ranges and no lock file: minimal version selection makes builds reproducible.
go.sumholds checksums, so commit it. - Import paths name directories. The standard library has no domain, and it covers a lot.