Monorepo with Go: Code Organization in Practice

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 in Practice



A monorepo is an approach to code management where all projects, libraries, and services are stored in a single repository. In the Go community, this approach is gaining popularity due to its simplicity and efficiency. In this article, we will explore how to organize a Go project in a monorepo in practice: from choosing a structure to setting up CI/CD.



Why a Monorepo for Go?



Go was originally designed as a language for large codebases. Tools like go mod and go work make working with a monorepo natural. The main advantages are:



  • Simplified dependencies: all modules use a single version of packages, no version conflicts.
  • Unified CI/CD: one configuration file for all services, easier to maintain.
  • Refactoring: changing an API in a library immediately affects all consumers in the repository.
  • Unified standards: linters, formatters, and code reviews apply to all code.


Monorepo Structure: A Proven Scheme



Let's consider a structure that works well for medium-sized Go projects. Suppose we have two services: auth-service and api-gateway, as well as shared libraries.



my-project/├── apps/                    # Executable applications│   ├── auth-service/│   │   ├── cmd/│   │   │   └── main.go│   │   ├── internal/│   │   │   ├── handler/│   │   │   ├── service/│   │   │   └── repository/│   │   ├── go.mod│   │   └── go.sum│   └── api-gateway/│       ├── cmd/│       │   └── main.go│       ├── internal/│       │   ├── middleware/│       │   └── routes/│       ├── go.mod│       └── go.sum├── libs/                    # Shared libraries│   ├── config/│   │   ├── config.go│   │   ├── go.mod│   │   └── go.sum│   ├── logger/│   │   ├── logger.go│   │   ├── go.mod│   │   └── go.sum│   └── db/│       ├── postgres.go│       ├── go.mod│       └── go.sum├── scripts/                 # Scripts for CI/CD and utilities│   ├── build.sh│   └── test.sh├── go.work                  # Go Workspace (optional)├── .golangci.yml            # Linter configuration├── Makefile                 # Common commands└── README.md


Key Structure Elements



  • apps/ — all executable applications. Each application has its own entry point (cmd/main.go) and its own go.mod.
  • libs/ — shared libraries. Each library is a separate Go module.
  • scripts/ — auxiliary scripts for building, testing, and deployment.
  • go.work — file for Go Workspace, simplifies local development.


Setting Up Go Workspace for Local Development



Go 1.18+ supports Workspace via the go.work file. This allows you to locally reference libraries from libs/ without publishing to a remote repository.



Create a go.work file in the project root:



go 1.22

use ( ./apps/auth-service ./apps/api-gateway ./libs/config ./libs/logger ./libs/db)


Now during development, you can import libraries directly:



// apps/auth-service/cmd/main.gopackage main

import ( "my-project/libs/config" "my-project/libs/logger")

func main() { cfg := config.Load() log := logger.New(cfg.LogLevel) log.Info("Auth service started")}


Important note: in each application's go.mod, the dependency on the library must point to a local path using replace:



// apps/auth-service/go.modmodule github.com/company/my-project/a                

Blogs

Book Recommendations