3 minute read

From โ€œHow do I use packages?โ€ to actually understanding Go Project layout

So I was learning Go and I hit this point where I asked:

โ€œHow do I use external packages (like GitHub imports)? And how do people structure real-world Go projects?โ€

At the time, it felt like two separate questions. Turns outโ€ฆ theyโ€™re very connected.

This post is basically that conversation plus what clicked for me after.


๐Ÿ“ฆ First: using external packages

I expected this to be complicated. It wasnโ€™t.

For example, if you want to use a popular HTTP framework like Gin, you just write:

import "github.com/gin-gonic/gin"

And Go justโ€ฆ handles it.

The first time you run your code:

  • it downloads the package
  • adds it to go.mod
  • caches it locally

Thatโ€™s it. No npm install, no setup rituals.


Alternate way (explicit commands)

go get github.com/gin-gonic/gin   # add dependency
go mod tidy                       # clean unused deps
go list -m all                    # list all modules

Example I tried

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()

    r.GET("/", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "Hello, world!"})
    })

    r.Run()
}

Run:

go run .

The first time you run it, Go:

  • downloads the package
  • updates go.mod and go.sum

Your go.mod will now include something like:

require github.com/gin-gonic/gin vX.X.X

What clicked here

Go doesnโ€™t want you to manage dependencies manually. You declare them, and Go takes care of the rest.

That part felt very clean.


๐Ÿ—๏ธ Then I asked: okayโ€ฆ but how do I structure a real project?

Because putting everything in main.go clearly isnโ€™t it.

I found a structure like this:

myapp/
  โ”œโ”€โ”€ go.mod
  โ”œโ”€โ”€ cmd/
  โ”‚    โ””โ”€โ”€ api/
  โ”‚         โ””โ”€โ”€ main.go
  โ”œโ”€โ”€ internal/
  โ”‚    โ”œโ”€โ”€ handlers/
  โ”‚    โ”œโ”€โ”€ services/
  โ”‚    โ””โ”€โ”€ models/
  โ”œโ”€โ”€ pkg/
  โ”‚    โ””โ”€โ”€ utils/

What these folders generally mean:

  • cmd/ โ†’ entry points (your apps)
  • internal/ โ†’ private application logic (This actually can be used for encapsulation!!)
  • pkg/ โ†’ reusable/shared code

Typical flow:

  • main.go โ†’ starts the app
  • handlers โ†’ HTTP layer
  • services โ†’ business logic
  • models โ†’ data structures

๐Ÿ” Then I got stuck on this word: internal/

I thought:

โ€œWaitโ€ฆ Go already has lowercase functions for private stuff. So why do we need internal/?โ€


๐Ÿ”ก First thing I learned (this surprised me)

Lowercase functions are only private to the package, not the whole project.

package services

func CreateUser() {}   // exported
func validateUser() {} // not exported

From another package:

services.CreateUser()   // โœ… works
services.validateUser() // โŒ error

Even though both are in the same project.

Thatโ€™s when it clicked:

lowercase โ‰  project-private itโ€™s just package-private


๐Ÿคฏ Then came the confusing part

โ€œOther packages in your module still canโ€™t access it but without internal/, other projects can still import your packageโ€

This sounded contradictory at first.


๐Ÿง  Letโ€™s break this properly (this is the key part)

There are two completely different rules:


๐Ÿ”น Rule 1: Lowercase (inside your project)

Controls what other packages in your project can access

  • validateUser() โ†’ โŒ not accessible outside its package
  • even within the same module

๐Ÿ‘‰ This is package-level privacy


๐Ÿ”น Rule 2: internal/ (outside your project)

Controls who can import your package

Case A โ€” without internal/

myapp/services/

Another project can do:

import "myapp/services" // โœ… allowed

Case B โ€” with internal/

myapp/internal/services/

Now from another project:

import "myapp/internal/services" // โŒ NOT allowed

๐Ÿ‘‰ This is enforced by the Go compiler.


๐Ÿ” The clean way to think about it

Instead of mixing them, separate them:

1. Can this package be imported at all?

โ†’ controlled by internal/

2. What inside the package is accessible?

โ†’ controlled by uppercase/lowercase


๐Ÿงฉ The mental model that finally worked for me

  • internal/ โ†’ who can enter the house
  • uppercase โ†’ what is visible to guests
  • lowercase โ†’ what stays private inside rooms

๐ŸŸข Final takeaway

  • Lowercase functions โ†’ private inside a package

  • internal/ โ†’ private to your project

  • Both solve different problems โ†’ and you usually use both together


โœ๏ธ If youโ€™re learning Go right now

If youโ€™re at that stage where:

  • modules make sense
  • packages kinda make sense
  • but structure feels fuzzy

This is the piece that clears it up.

At least it did for me.


And honestly, this was one of those โ€œohhh okayโ€ moments that made Go feel a lot more intentional ๐Ÿš€

Updated:

๐Ÿ’ก Knowledge grows when shared.

If this helped you, pass it on to someone who might benefit.