Monorepo with Go: Code Organization for Scalable Projects
In modern software development, the question increasingly arises: how to organize code so that it remains manageable, reusable, and scalable? One popular solution is using a monorepo — a single repository containing multiple projects or services. In this article, we will explore how to effectively organize a monorepo in Go (Golang), covering best practices, directory structure, working with modules, and CI/CD setup.
What is a monorepo and why is it needed in Go?
A monorepo is an approach where all code for a company or large project is stored in a single repository. This can include:
- Multiple microservices
- Libraries and utilities
- Configurations and scripts
- Documentation
Advantages of a monorepo for Go:
- Single version of dependencies — eliminates version conflicts between services.
- Simplified refactoring — changes to a shared library are immediately visible to all consumers.
- Easy code reuse — no need to publish packages to a registry.
- Unified commit history — simplifies debugging and auditing.
However, a monorepo requires discipline: without a clear structure, it quickly turns into a "dump." Go, with its strict module system (Go Modules), provides excellent tools for organizing such a repository.
Directory structure in a Go monorepo
A proper structure is the foundation of success. Let's look at the recommended organization for a Go monorepo:
my-monorepo/├── apps/ # Applications (services, CLI utilities)│ ├── api-gateway/│ │ ├── cmd/│ │ │ └── main.go│ │ ├── internal/│ │ ├── go.mod│ │ └── go.sum│ ├── auth-service/│ │ └── ...│ └── user-service/│ └── ...├── libs/ # Shared libraries│ ├── common/│ │ ├── logger.go│ │ ├── config.go│ │ ├── go.mod│ │ └── go.sum│ └── models/│ ├── user.go│ ├── go.mod│ └── go.sum├── scripts/ # Build and deployment scripts│ ├── build.sh│ └── deploy.sh├── docs/ # Documentation├── .github/ # CI/CD configurations│ └── workflows/│ └── ci.yml├── go.work # Workspace file (Go 1.18+)├── Makefile # Common commands├── README.md└── .gitignoreKey elements of the structure:
- apps/ — contains executable applications (services, utilities). Each application is a separate Go module with its own
go.mod. - libs/ — shared libraries reused across applications.
- go.work — workspace file that allows working with all modules locally without publishing changes.
Working with Go Modules in a monorepo
Starting with Go 1.18, support for workspaces (go.work) was introduced. This solves the problem of local development when modules depend on each other. Let's look at a configuration example.
Create a go.work file in the root of the monorepo:
go 1.21
use ( ./apps/api-gateway ./apps/auth-service ./libs/common ./libs/models)Now, during local development, Go will use local versions of modules instead of published ones. This is critically important for fast iteration.
Example dependency in apps/auth-service/go.mod:
module github.com/company/auth-service
go 1.21
require ( github.com/company/libs/common v0.0.0 github.com/company/libs/model