How to Organize a Go Project with Multiple Files: Under the Hood

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

A Go project from one file is easy. But from ten?

\n\n

Imagine: you are writing a log parsing utility in Go. At first, everything fits into one file main.go. It works, everything is fine. But after a week, the code grows: functions for reading files, for filtering, for sending data to the database appear. One file turns into a 2000-line monster. Sound familiar?

\n\n

This raises a question that torments every second beginner (and which has gathered thousands of views on Stack Overflow): how to properly organize a Go project with multiple files? Not just scatter code across folders, but do it so that it works, compiles, and doesn't cause errors like undefined or import cycle not allowed.

\n\n

In this article, we won't just give a template. We'll look under the hood of the Go compiler and understand how the connection of files, packages, and modules actually works. You'll learn why go run *.go sometimes saves the day and sometimes doesn't, and how to build an architecture that scales.

\n\n

File, Package, Module — Basic Hierarchy

\n\n

Before diving into code, let's break down three key concepts. Without them, organizing a Go project turns into guesswork.

\n\n
    \n
  • File (.go) — is just a text file with code. By itself, it means nothing to the compiler.
  • \n
  • Package (package) — is a logical group of files located in the same directory. All files in a folder must have the same package declaration. If a folder contains files with different packages, the compiler will throw an error.
  • \n
  • Module (go.mod) — is the root of your project. It defines the module name, its dependencies, and Go versions. A module can contain many packages.
  • \n
\n\n

Golden rule: one directory = one package. The exception is tests, but more on that later.

\n\n

Practical Example: "Log Analyzer" Project

\n\n

Let's create a small project that reads a log file, filters lines by a keyword, and outputs the result. We'll do this first "the brute force way" (one file), and then split it into multiple files correctly.

\n\n

Stage 1: Everything in one file (bad)

\n\n
// main.go\npackage main\n\nimport (\n    "bufio"\n    "fmt"\n    "os"\n    "strings"\n)\n\nfunc readLog(path string) ([]string, error) {\n    file, err := os.Open(path)\n    if err != nil {\n        return nil, err\n    }\n    defer file.Close()\n\n    var lines []string\n    scanner := bufio.NewScanner(file)\n    for scanner.Scan() {\n        lines = append(lines, scanner.Text())\n    }\n    return lines, scanner.Err()\n}\n\nfunc filterLines(lines []string, keyword string) []string {\n    var result []string\n    for _, line := range lines {\n        if strings.Contains(line, keyword) {\n            result = append(result, line)\n        }\n    }\n    return result\n}\n\nfunc main() {\n    lines, err := readLog("server.log")\n    if err != nil {\n        fmt.Println("Error:", err)\n        return\n    }\n    filtered := filterLines(lines, "ERROR")\n    for _, l := range filtered {\n        fmt.Println(l)\n    }\n}\n
\n\n

It works. But imagine that readLog becomes more complex (needs gzip support, encodings), and filterLines gets cluttered with regular expressions. The file will become unreadable.

\n\n

Stage 2: Split into packages (correct)

\n\n

Let's create the directory structure:

\n\n
log-analyzer/\n├── go.mod\n├── main.go\n└── pkg/\n    └── logreader/\n        ├── reader.go\n        └── filter.go\n
\n\n

Initialize the module:

\n\n
go mod init log-analyzer
\n\n

Now let's write the code for the logreader package. Note: all files in the pkg/logreader/ folder have package logreader.

\n\n

pkg/logreader/reader.go

\n\n
package logread                

Blogs

Book Recommendations