Monorepo with Go: Code Organization for Scalable Projects

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

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└── .gitignore


Key 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

Blogs

Book Recommendations