Stage 7 · Master
Project Foundations & Workspace Layout
One Module, One Binary
Fieldwork starts as the smallest thing that can compile and run. No go.work, no services/ folder, no packages/ folder — just a module and an entrypoint.
The Temptation to Scaffold the Future
Fieldwork is going to grow into several binaries: an HTTP API, a background worker, eventually a gateway. Knowing that in advance creates a strong temptation to set the whole thing up on day one — a go.work file, a services/ directory with empty folders waiting to be filled, a packages/ directory for 'shared code we'll obviously need.' It looks organized. It is actually the opposite of organized, because every one of those folders is a guess about a future that hasn't happened yet.
Right now Fieldwork has zero services, zero shared packages, and zero reason for a multi-module workspace. A workspace exists to let multiple Go modules develop together with local replace-free imports. That problem doesn't exist until there is a second module. Building the folder for it anyway just gives a new reader a repo that lies about its own maturity — empty directories implying decisions that were never actually made.
Every folder and file in this course gets introduced at the moment the current feature needs it, not before. If you can't point to the specific requirement that justifies a new package existing right now, it doesn't get created yet.
What Fieldwork Actually Is on Day One
On day one, Fieldwork is one Go module and one binary: an HTTP process that can start up, respond to a health check, and shut down cleanly. That's it. No database, no router library, no auth — those all get introduced in later modules, each time a real requirement forces the decision. For now we just need something that runs, so every later decision has a working baseline to build from.
go.modcreateResponsibility: Declares the Go module path and the Go version this codebase targets.
Why now: A go.mod is the minimum required for anything in this repo to compile. There's exactly one binary, so there's exactly one module — no workspace file is needed to coordinate modules that don't exist yet.
cmd/api/main.gocreateResponsibility: The process entrypoint: starts an HTTP server and responds to /healthz.
Why now: cmd/api/ (rather than main.go at the repo root) costs nothing today and means when a second binary shows up later, it gets its own cmd/<name>/ sibling instead of forcing a rename of everything that already exists.
module github.com/thesyscoder/fieldwork
go 1.23One module path for the whole repo. There is nothing to version independently yet — that only becomes a real question once a second module exists.
Notice what's missing on purpose: no config package (env vars are read inline for now — Module 2's next lesson introduces config.go once there's more than one setting to manage), no logging package (log.Println is enough for a server with one handler), no internal/ directory at all yet. internal/ shows up in the very next lesson, the moment main.go actually accumulates enough logic to be worth extracting — not before.
Why Not go.work Yet
go.work solves one specific problem: letting you develop two or more Go modules together, using each other's latest local code without publishing versions or using replace directives. That's a real problem — but only once a second module exists. Fieldwork doesn't get a second binary until Module 6, when the API Gateway is introduced as a genuinely separate process with its own reverse-proxying concerns. That's also the earliest point where a go.work workspace has anything to coordinate.
| Signal | Present now? | Would justify go.work? |
|---|---|---|
| A second binary that needs to run independently | No — one binary (cmd/api) | Yes, this is the actual trigger |
| Code that two binaries both need to import | No — nothing to share yet | Only combined with a second binary |
| "We'll probably need more services eventually" | True, but not a present requirement | No — this is speculation, not a requirement |
Scaffolding for a future need has a real cost today: more surface area to navigate, more empty structure to misread as a design decision, more to get wrong before there's any code to validate the design against. Pay that cost when the need actually arrives, when you have a real second module to organize around — not before.
The Decision
- Fieldwork starts as a single go.mod at the repo root — one module, because there's one binary.
- cmd/api/main.go is the entrypoint, using stdlib net/http — no router library until Module 4 gives a concrete reason to add one.
- No go.work file, no services/ directory, no packages/ directory. Those get introduced later, each time a specific, present requirement forces the decision — go.work in Module 6, when a second binary (the gateway) actually appears.
- Every future lesson that adds a file will say exactly which file, why it's needed now, and how it connects to what already exists — the same discipline applied to this first, smallest step.
This lesson is deliberately small. That's the point: the smallest possible starting structure, with every later addition earning its place on its own merits. The next lesson picks up the moment main.go starts doing more than one thing.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.