Go, from zero to a running service · Chapter 2 of 12 · Languages · new chapter every Tuesday night

By the end of this chapter: Create, build and run a Go project laid out the way the ecosystem expects.

The problem

Emma is a junior backend engineer on a fintech startup that just landed its first B2B client. The client wants a new “transaction‑validation” service that will sit behind the existing Java monolith. The product manager has pushed the deadline to two weeks because the client’s contract starts next month.

Emma, eager to prove herself, decides to write the service in Go because the team’s senior engineer swore it would “just work faster”. She clones a starter repository, adds a single http.Handler that parses JSON, validates a few fields and returns a 200 OK. She runs the binary locally and everything looks fine.

However, when she tries to build the project using the command go build, she gets an error message: go: cannot find main module; see 'go help modules'. She is not sure what this means or how to fix it. The error is costing her time, and the deadline is looming. Emma needs to get her project set up correctly and build a working Go service.

Before you start

To follow along with this chapter, you need to have Go installed on your machine. You can verify this by running the command go version in your terminal. You should see a version number, such as go1.19. You also need to have a code editor or IDE set up. If you have completed the previous chapter, you should have a basic understanding of why Go exists and what it optimises for.

Understanding Go modules

To create a new Go project, we need to understand how Go modules work. Go modules are the package management system for Go. They allow us to manage dependencies and ensure that our project is reproducible. A Go module is a collection of Go packages stored in a directory with a go.mod file. The go.mod file contains the module path, which is the unique identifier for the module, and the dependencies required by the module.

Creating a new Go project

To create a new Go project, we need to create a new directory for our project and navigate to it in our terminal. We can then run the command go mod init to create a new Go module. The go mod init command will prompt us to enter the module path, which should be in the format of github.com/username/repository. Once we have created the module, we can start adding dependencies to our project using the go get command.

Working with dependencies

When working with dependencies in Go, it's essential to understand the concept of versions. Go modules use semantic versioning, which means that each version of a dependency is identified by a unique version number. We can specify the version of a dependency in our go.mod file using the require directive. For example, to require version 1.2.3 of the github.com/gorilla/mux package, we would add the following line to our go.mod file:

require github.com/gorilla/mux v1.2.3 

We can also use the go get command to update the version of a dependency. For example, to update the github.com/gorilla/mux package to the latest version, we would run the following command:

go get github.com/gorilla/mux@latest 

Build it

Here are the steps to create, build and run a Go project:

  1. Create a new directory for your project: Run the command mkdir transaction-validation to create a new directory for your project.
  2. Navigate to the new directory: Run the command cd transaction-validation to navigate to the new directory.
  3. Create a new Go module: Run the command go mod init transaction-validation to create a new Go module.
  4. Create a new Go file: Run the command touch main.go to create a new Go file.
  5. Add the http.Handler code: Add the following code to the main.go file:
package main import ( "encoding/json" "fmt" "log" "net/http" ) type Transaction struct { ID string `json:"id"` Amount float64 `json:"amount"` } func main() { http.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) { var transaction Transaction err := json.NewDecoder(r.Body).Decode(&transaction) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if transaction.Amount < 0 { http.Error(w, "Invalid transaction amount", http.StatusBadRequest) return } w.WriteHeader(http.StatusOK) }) log.Fatal(http.ListenAndServe(":8080", nil)) } 
  1. Build the project: Run the command go build to build the project. To confirm that the project has been built successfully, you should see a transaction-validation executable file in your project directory.
  2. Run the project: Run the command ./transaction-validation to run the project. To confirm that the project is running successfully, you should be able to access the /validate endpoint using a tool like curl. For example:
curl -X POST -H "Content-Type: application/json" -d '{"id": "123", "amount": 10.99}' http://localhost:8080/validate 

This should return a 200 OK response.

When this breaks

One common issue that can occur when building a Go project is the go: cannot find main module error. This error occurs when the go build command cannot find the go.mod file. To fix this error, make sure that you are in the correct directory and that the go.mod file exists.

Another issue that can occur is the undefined: Transaction error. This error occurs when the Transaction struct is not defined. To fix this error, make sure that the Transaction struct is defined in the main.go file.

What it costs

Using Go modules has several benefits, including reproducibility and dependency management. However, it also has some costs, such as the need to manage the go.mod file and the potential for version conflicts. Additionally, Go modules can make it more difficult to work with local dependencies, as they require a go.mod file to be present in the dependency directory.

In the interview

In an interview, you may be asked to explain how Go modules work and how to create a new Go project. A weak answer might be: "Go modules are a way to manage dependencies, but I'm not really sure how they work." A strong answer would be: "Go modules are a package management system for Go that allows us to manage dependencies and ensure that our project is reproducible. To create a new Go project, we need to create a new directory, navigate to it in our terminal, and run the command go mod init to create a new Go module."

A follow-up question might be: "How do you handle version conflicts in a Go project?" A strong answer would be: "To handle version conflicts in a Go project, we can use the go mod tidy command to remove any unnecessary dependencies and the go mod vendor command to vendor our dependencies. We can also use the go mod verify command to verify that our dependencies are correct."

As a senior engineer, you may be asked more advanced questions, such as: "How do you optimize the performance of a Go project?" or "How do you handle errors in a Go project?" A strong answer would demonstrate a deep understanding of Go and its ecosystem, as well as the ability to apply best practices to real-world problems.

Your tasks

  1. Create a new Go project using the go mod init command. Verify that the project has been created successfully by checking for the presence of a go.mod file.
  2. Add a new dependency to your project using the go get command. Verify that the dependency has been added successfully by checking the go.mod file.
  3. Run the go mod tidy command to remove any unnecessary dependencies. Verify that the command has been run successfully by checking the go.mod file.
  4. Run the go mod vendor command to vendor your dependencies. Verify that the command has been run successfully by checking for the presence of a vendor directory.
  5. Create a new Go file and add a new http.Handler to it. Verify that the handler is working correctly by using a tool like curl to access the endpoint.

Your tasks this week

Do the exercises above before the next chapter. Reading a tutorial and doing
one are different activities and only one of them changes what you can build.

Stuck on any of them? Say so — describe what you tried and what happened:
tell me where you got stuck. I read every one, and the questions
that come back more than twice get answered in the next chapter.

Go, from zero to a running service

Chapter 2 of 12. New chapter every Tuesday night.
Next: Types, structs and methods without inheritance.

· The full syllabus and every chapter so far
· Subscribers also get the condensed notes for this chapter, the running
recap of everything the series has covered, and the extended guidance:
subscribe

Written by Amit Chakraborty — founding engineer and senior architect: React Native, AI and RAG systems, production architecture. Portfolio · LinkedIn · GitHub.

Need this built, reviewed or taught to your team? Get in touch or email amit@devamit.co.in. Available for senior and founding engineering roles, consulting and training, remote worldwide.