Practical tips on Golang package usage and project structure
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.modandgo.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 apphandlersโ HTTP layerservicesโ business logicmodelsโ 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 ๐