Monorepository with Go: Code Organization for Beginners

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

What is a monorepository and why is it needed in Go?



A monorepository (monorepo) is a code management approach where all projects, libraries, and services are stored in a single repository. In the Go ecosystem, this approach is becoming particularly popular due to built-in support for modules and workspaces.



For beginner developers, a monorepository offers several key advantages:

  • Single code version — all dependencies are synchronized
  • Simplified refactoring — changes are immediately visible across all projects
  • Standardization — consistent linter, CI/CD, and test settings
  • Transparency — easy to understand how different parts of the system are connected


In this article, we will break down how to properly organize a monorepository in Go, starting from the basic structure and ending with practical examples.



Basic structure of a Go monorepository



Proper code organization is the foundation of a successful monorepository. Let's look at a typical structure:



my-monorepo/├── apps/│   ├── web-api/          # Main application│   │   ├── main.go│   │   ├── go.mod│   │   └── internal/│   └── worker/           # Background worker│       ├── main.go│       └── go.mod├── libs/│   ├── config/           # Shared configuration│   │   ├── config.go│   │   └── go.mod│   └── logger/           # Logging│       ├── logger.go│       └── go.mod├── scripts/              # Helper scripts├── docs/                 # Documentation├── go.work               # Go workspace└── README.md


Key principles of this structure:

  • apps/ — executable applications (binaries)
  • libs/ — reusable libraries
  • scripts/ — automation and utilities
  • docs/ — project documentation


Setting up Go Workspace for a monorepository



Starting with Go 1.18, official support for workspaces was introduced via the go.work file. This allows working with multiple modules simultaneously without needing to publish them to a remote repository.



Let's create a workspace for our monorepository:



// go.workgo 1.22

use ( ./apps/web-api ./apps/worker ./libs/config ./libs/logger)


After creating go.work, all modules in the use list will be accessible to each other locally. This is especially convenient during development — changes in a library are immediately visible in the applications.



Initializing modules



Each component needs its own go.mod. Example for the configuration library:



// libs/config/go.modmodule github.com/your-org/libs/config

go 1.22


And for the web-api application:



// apps/web-api/go.modmodule github.com/your-org/apps/web-api

go 1.22

require github.com/your-org/libs/config v0.0.0

replace github.com/your-org/libs/config => ../../libs/config


Note the replace directive — it tells Go to use the local version of the library instead of searching for it online.



Code example: library and application



Let's create a simple configuration library and an application that uses it.



Library libs/config



// libs/config/config.gopackage config

import ( "fmt" "os")

type Config struct { Port string Env string}

func Load() (*Config, error) { port := os.Getenv("PORT") if port == "" { port = "8080" }

env := os.Getenv("ENV") if env == "" { env = "development" }

return &Config{ Port: port, Env: env, }, nil}

func (c *Config) String() string { return fmt.Sprintf("Config{

Blogs

Book Recommendations