3 minute read

From GOPATH to Go Modules: How Go Packages Evolved (With a Simple Example)

As my liking is to learn from books, i chose Head First Go, to brush up my golang skills. While i was going through the packages topic. i found it very inconveineint to use the folder strucutre mentioned in the book. I needed to create a ~/go/src workspace. If i quote the book text here:

The workspace directory isn’t created by default when Go is installed, so you’ll need to create it yourself. Start by going to your home directory. (The path is C:\Users\yournameon most Windows systems, /Users/yournameon Macs, and /home/yournameon most Linux systems.) Within the home directory, create a directory named go— this will be our new workspace directory. Within the godirectory, create a directory named src.

But if you try modern Go today, none of that seems necessary.

So what changed?

In this post, we’ll walk through:

  • How Go packages worked before modules (GOPATH era)
  • How they work now (Go modules)
  • The same example implemented in both styles

🧭 The Old Way: GOPATH Workspace

Before Go 1.11, Go relied on a strict workspace model called GOPATH.

You had to manually create a directory structure like this:

~/go/
  ├── src/
  ├── pkg/
  └── bin/

📦 Where your code lived

All your Go code had to be inside:

~/go/src/

So if you were building a project called myapp, it had to be:

~/go/src/myapp/

🧪 Example (GOPATH style)

Structure:

~/go/src/myapp/
  ├── main.go
  └── mathutils/
       └── add.go

mathutils/add.go

package mathutils

func Add(a int, b int) int {
    return a + b
}

main.go

package main

import (
    "fmt"
    "myapp/mathutils"
)

func main() {
    fmt.Println(mathutils.Add(2, 3))
}

⚠️ Limitations of GOPATH

  • You must place code inside GOPATH/src
  • No versioning of dependencies
  • Hard to manage multiple projects cleanly
  • Not flexible for modern workflows

🚀 The Modern Way: Go Modules

With the introduction of Go modules, everything changed.

You no longer need:

  • GOPATH/src
  • A fixed workspace
  • Manual dependency handling

Instead, Go uses a file called:

go.mod

This defines your project as a module.


🧱 Same Example (Modern Go Modules)

Let’s rebuild the same app the modern way.

Step 1: Create project

mkdir myapp
cd myapp
go mod init myapp

Step 2: Add package

myapp/
  ├── go.mod
  ├── main.go
  └── mathutils/
       └── add.go

mathutils/add.go

package mathutils

func Add(a int, b int) int {
    return a + b
}

main.go

package main

import (
    "fmt"
    "myapp/mathutils"
)

func main() {
    fmt.Println(mathutils.Add(2, 3))
}

▶️ Run it

go run .

🔑 What Stayed the Same?

Interestingly, packages themselves didn’t change much.

  • A package is still just a folder
  • You still use package <name>
  • You still import using module/package

👉 The concept of packages remained stable.


🔄 What Actually Changed?

Concept GOPATH Era Modules Era
Project location Fixed (~/go/src) Anywhere
Dependency mgmt Manual Automatic (go.mod)
Flexibility Low High
Imports Path-based Module-based

🧠 Mental Model (Modern Go)

  • Module = your project (defined by go.mod)
  • Package = a folder inside your project
  • Exported names = start with uppercase

Example import:

import "myapp/mathutils"
  • myapp → module name
  • mathutils → folder

❌ Common Mistake

Trying to use relative imports:

import "./mathutils" // ❌ Don't do this

Always use module-based imports:

import "myapp/mathutils" // ✅ Correct

The folder name we mention during go mod init is the name which is eventually used as the pathname in the code. I by mistake had committed a spelling mistake during go mod init command:

whereas the folder name was :

so the import path in code will look like this:

As you can see here, the import path took the name we provided during “go mod init” command..


🟢 Final Thoughts

If you’re following an older Go book, don’t worry—you’re not doing anything wrong. You’re just seeing how Go used to work.

But for real-world development today:

  • Skip GOPATH/src
  • Use go mod init
  • Organize code using folders as packages

Go has become much more flexible—and much easier to work with.


✍️ TL;DR

  • Old Go required a strict workspace (GOPATH)
  • Modern Go uses modules (go.mod)
  • Packages are still just folders
  • You can now work from anywhere on your system

If you want to understand more practical stuff on packages and project structure used in big Go projects, consider reading this post: Go visibility across packages

If you’re learning Go today, you’re already using the better version 🚀

Updated:

💡 Knowledge grows when shared.

If this helped you, pass it on to someone who might benefit.