Hi, everyone!

In this article, I wanna show how we can create monorepo with Golang and Bazel.

Bazel

Requirements:

Go Modules — we wanna use go mod for managing dependencies Shared codebase — we wanna reuse our code in different application Test — we wanna run tests on single/all applications Build — we wanna build every application separately

These requirements go beyond the definition of monorepo. Okay, let's start.

1. Init go modules

We need to create go mod in our folder:

go mod init monorepo

After this command, we can get go dependence on our project.

2. Create for example two application

Let’s create a folder packages that folder will have all our applications.

mkdir packages

cd packages

mkdir main_app

mkdir second_app

cd main_app && touch main.go && cd ../

cd second_app && touch main.go && cd ../

mkdir shared && touch shared.go - that file for shared code

For example, our application will be use gin like HTTP server:

go get github.com/gin-gonic/gin

3. Create a workspace for our monorepo

In the root folder, we need to create WORKSPACE file with content:

WORKSPACE

And BUILD file with content:

Build

After we need setup Bazel to use dependence from go.mod with command:

bazel run //:gazelle -- update-repos -from_file=go.mod

That command will be updating our WORKSPACE file with dependencies from go.mod

We finish setup our workspace after that we need to set up our applications.

4. Create a build for applications

Every application in the application folder should have BUILD like an entry point for Bazel

Application BUILD file

Here we have two sections:

go_binary go_test

Section go_binary

This section has some field full list you can find in the link. I will just describe some of them:

“name” — the name for Bazel, for deps and Bazel resolver

“srcs” — entry points for modules, will be like artifacts

“importpath” — how we should import this module in our applications

“deps” — an array of dependencies

Section go_test

“name” — for the run test

“srcs” — entry points for tests

Let's add some shared content

For creating an application like real life I add a router in both applications with similar content

Router in main_app

If you see that package using some dependencies from a shared folder.

And lets create BUILD file for that package:

Build file for Router in main_app

That file has a section ‘go_library’

Section go_library

“name” — the name for Bazel, for use in dep

“srcs” — the entry point for lib

“importpath” — path for import outside(Bazel will be resolved that alias like webpack in nodejs)

“visibility” — visibility of the module in Bazel

“deps” — dependencies, in this example we have dep from “@com_github_gin_gonic_gin” — it is the internal name of gin, you can get from WORKSPACE file and from our shared handler.

5. Create a shared folder and build

In the previous step, we have a reference on a shared folder.

I create a shared handler for gin like that:

Health handler

And build for package:

Health handler build

As we see gin in dep too.

I will skip the part in creating code for the second application because it is totally the same. Full code you can find here

6. Let's build and test our applications

For build main application we can use the command:

bazel build //packages/main_app:main_app

That command will be build application and show the path to bin file

For build second application you can use:

bazel build //packages/second_app:second_app

Test

Single application test:

bazel test //packages/second_app/...:all

All applications test:

bazel test //packages/...:all

Dep graph

Bazel can create dep graph with the command:

bazel query 'allpaths(packages/...,//packages/shared/handlers/health:health)' --output graph | dot -Tpng > dep.png

That command will be shown all packages who dependent on health module.

7. Enjoy!

In this example, I showed the basis for creating a monorepo in Go, if you are interested, I will continue the article and tell how to use for example protobuf or general static files in monorepo.