Let’s say you want to have two Go packages pkg1 and pkg2 in a monorepo setup. Here’s what a good project structure would look like.

Here’s my recommended project structure in that case

src/pkg1
|
|- go.mod
|- go.sum
|- pkg - Optional: required if pkg1 exports any code. It is just a convention to put all exported code in pkg
|- internal - most code should go here by default, code here cannot be accessed outside of pkg1
|- cmd - CLI tools or web server startup code goes here

 

src/pkg2‘s structure would look very similar.

Now let’s assume that you are hosting this monorepo at https://github.com/ashishb/golang-template-repo. I would recommend that src/pkg1‘s module name, defined in go.mod be github.com/ashishb/golang-template-repo/src/pkg1 and pkg2’s module name be github.com/ashishb/golang-template-repo/src/pkg2. This is not a hard-and-fast requirement, however, this will avoid a lot of confusion if the package name maps to the URL.

Now, let’s assume that there is code in src/pkg1/pkg/module1 that’s exported and is being used by pkg2. To add that mapping just use the replace directive and add the following to src/pkg2/go.mod.

replace (
    github.com/ashishb/golang-template-repo/src/pkg1 => ../pkg1/
)

Now, import github.com/ashishb/golang-template-repo/src/pkg1/pkg/module1 would just work.

As a reminder, import github.com/ashishb/golang-template-repo/src/internal would never work as internal is never exported.