1.4 Structure Your Go Project Right(cmd, internal, pkg)
TL;DR
- Put your main, runnable programs in a
/cmddirectory. Think of this as the "On" switch for your application. - Hide private code that only your project should use inside an
/internaldirectory. This is your secret workshop. - Place code that other projects can safely use in a
/pkgdirectory. This is your public, shareable toolkit. - Start simple. You don't need these folders for a small project. Add them as your code grows.
Why This Matters
Ever tried to cook in a messy kitchen? You can't find the knife, the spices are all mixed up, and it takes forever to make a simple meal. A messy code project is just like that.
A good folder structure is like having a clean, organized kitchen. It tells you exactly where everything belongs. This makes it easier for you (and your teammates) to find code, fix bugs, and add new features without making a mess. It’s the blueprint that brings peace and order to your project. 🧘
Concepts in Plain English
Before we start creating folders, let's get a few simple ideas straight.
- Package: A package is like a single toolbox for a specific job. You might have a
databasetoolbox or atext-formattingtoolbox. In Go, it's just a directory with some.gofiles in it. - Module: A module is your entire project—a collection of all your toolboxes (packages) that are versioned and shipped together.
mainpackage: This is the special "ignition key" package. When Go seespackage main, it knows, "Aha! This is a program I can actually run."cmd/directory: This is the garage where you keep your runnable applications. Each subfolder insidecmdis a different car you can start.internal/directory: Your private workshop. The code and tools in here are exclusively for your project. Go actually enforces this—no other project can import code from yourinternalfolder.pkg/directory: A public showroom. The packages you put here are well-polished and designed to be imported and used by other developers in their own projects.
Here's a quick reference table.
| Term | Simple Definition | Where It Shows Up |
| Module | The entire project. | The root folder with a go.mod file. |
| Package | A single folder of Go code for one purpose. | Any directory with .go files. |
cmd/ | A folder to hold your main, runnable programs. | cmd/my-app/main.go |
internal/ | A folder for private code, only for your project. | internal/database/connect.go |
pkg/ | A folder for public code, meant to be shared. | pkg/api-client/client.go |
Do It Step by Step
Let's see how a project grows from a single file into a well-structured application. We'll start with a new Go module.
# 1. Make a new folder for our project
mkdir my-awesome-project
cd my-awesome-project
# 2. Tell Go this is a new project (a "module")
# WHY: This creates a go.mod file, which tracks all your project's dependencies.
go mod init github.com/your-username/my-awesome-project
Category 1: The Simple Start (One File)
Goal: Create a tiny program without any special folders.
For very small projects, you don't need a complex structure. One file is perfectly fine.
Run it. Your folder structure is just one file.
my-awesome-project/
├── go.mod
└── main.go
# Run the main.go file directly
go run main.go
You should see: Hello, World!
Create a main.go file.
// main.go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Pro tip: Most projects start this way. Don't add complexity you don't need yet!
Category 2: Adding a Runnable Program (cmd/)
Goal: Give our project a formal "entry point" so it's clear what the main application is.
As your project grows, you might have a web server, a command-line tool, and other programs. The cmd directory keeps them organized.
Run the app from its new location.
# Tell Go to run the package located in that folder
go run ./cmd/my-app
You should see: Hello, World!
Move main.go into it.
mv main.go cmd/my-app/main.go
Your structure now looks like this:
my-awesome-project/
├── cmd/
│ └── my-app/
│ └── main.go
└── go.mod
Create the cmd directory and a subfolder for your app.
mkdir -p cmd/my-app
Category 3: Separating Code (internal/ vs. pkg/)
Goal: Move reusable logic out of main.go into helper packages.
Your main.go should be short and simple. It should call other, more specialized packages to do the real work. But should those packages be private or public?
| Directory | When to Use It | Can others import it? | Analogy |
internal/ | For code that is specific to your application and not useful to others. | No ❌ | Your secret family recipe. |
pkg/ | For code you want to share with the world, that others can import into their projects. | Yes ✅ | A cookbook you published. |
Let's create a private helper package in internal to store our greeting logic.
Run it!
go run ./cmd/my-app
You should see: Hello from an internal package!
Update main.go to use the new package.
// cmd/my-app/main.go
package main
import (
"fmt"
// WHY: We import our new package using the module path.
"github.com/your-username/my-awesome-project/internal/greeting"
)
func main() {
message := greeting.GetGreeting()
fmt.Println(message)
}
Add a new file with a greeting function.
// internal/greeting/greeting.go
package greeting
// GetGreeting returns a friendly message.
// WHY: We moved the logic here so other parts of our app can use it,
// but no outside projects can import it.
func GetGreeting() string {
return "Hello from an internal package!"
}
Create an internal/greeting package.
mkdir -p internal/greeting
Warning: Don't put everything inpkg/by default. It signals that the code is safe for public use. If it's not, useinternal/.
Examples Section
Let's put it all together in a more realistic example.
Example A: A Web Server with Private Logic
Here's a common layout for a web server. The main package starts the server, but the actual request handling logic lives in an internal package.
Directory Structure:
my-web-server/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ └── httphandlers/
│ └── handlers.go
└── go.mod
Code:
// go.mod
module github.com/your-username/my-web-server
// cmd/server/main.go
package main
import (
"fmt"
"net/http"
// WHY: Importing our private handler logic.
"github.com/your-username/my-web-server/internal/httphandlers"
)
func main() {
// The main package sets up the routes and starts the server.
http.HandleFunc("/", httphandlers.HomeHandler)
fmt.Println("Server listening on port 8080...")
http.ListenAndServe(":8080", nil)
}
// internal/httphandlers/handlers.go
package httphandlers
import (
"fmt"
"net/http"
)
// HomeHandler contains the business logic for the home page.
// WHY: Keeping this logic separate makes it easier to test and manage.
// Since it's in `internal`, no other Go module can import this package.
func HomeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Welcome to the home page!")
}
Example B: Adding a Sharable Utility from pkg/
Now, let's add a simple, shareable math utility. Since this could be useful for other projects, we'll put it in pkg/.
Mermaid Diagram: Code Flow
This diagram shows how main.go acts as a controller, using helpers from both private (internal) and public (pkg) locations.
flowchart LR
subgraph Your Project
A[main.go in cmd/] --> B(Private logic in internal/)
A --> C(Public utilities in pkg/)
end
ASCII Fallback: (main.go) --> (internal logic) and (main.go) --> (pkg utilities)
Code to Add:
First, create the pkg directory and the utility file.
// pkg/mathutil/add.go
package mathutil
// Add returns the sum of two integers.
// WHY: This is a generic, simple function. It's safe to share,
// so we place it in `pkg` for other projects to potentially use.
func Add(a, b int) int {
return a + b
}
Next, update main.go to use it.
// cmd/server/main.go (Updated)
package main
import (
"fmt"
"net/http"
"github.com/your-username/my-web-server/internal/httphandlers"
// WHY: Now we're also importing our new public package.
"github.com/your-username/my-web-server/pkg/mathutil"
)
func main() {
// Use the utility function.
sum := mathutil.Add(5, 10)
fmt.Printf("Calculation from pkg: 5 + 10 = %d\n", sum)
http.HandleFunc("/", httphandlers.HomeHandler)
fmt.Println("Server listening on port 8080...")
http.ListenAndServe(":8080", nil)
}
Common Pitfalls
- Creating all the folders at once. You don't need
cmd,internal, andpkgfor a tiny project. Start with onemain.gofile and add these directories only when you need the organization. - Putting everything in
pkg/. This is a common mistake. Putting code inpkg/sends a signal that it's stable and safe for public use. If the code is specific to your app, it belongs ininternal/. - Creating deep, nested folders for no reason. A structure like
internal/app/core/logic/user/is usually overkill. Keep your package paths as flat and simple as possible.
FAQ
1. Do I have to use this structure?
No, Go is flexible! This is just a very common and effective convention. It helps other Go developers understand your project instantly.
2. What if I just have one program? Do I still need cmd/my-app/?
You don't have to. But it's good practice because it clearly separates your "runnable" code from your "library" code (the helper packages in internal or pkg).
3. Can I have multiple programs in cmd/?
Absolutely! That's what it's for. You could have cmd/web-server/, cmd/cli-tool/, and cmd/worker/ all in the same project.
4. When should I move code from the project root into internal/?
A good time is when your main.go starts getting crowded with helper functions that aren't directly related to starting the application. Moving them to internal/ cleans things up.
Recap
You've learned the fundamentals of building a clean, scalable Go project.
- Start simple: A single
main.gois fine for small projects. - Use
cmd/: When you create a runnable application, place itsmainpackage incmd/app-name/. - Use
internal/: For all the private helper code that is specific to your project. This is your default choice for most of your code. - Use
pkg/sparingly: Only for code that is truly generic and intended to be used by other projects. - This structure isn't about rules; it's about communication. It tells the next person (or future you) how your project works at a single glance.
Assumptions: This post is based on standard Go project layout conventions, often referred to as "Standard Go Project Layout." The Go toolchain itself does not enforce this structure, except for the special behavior of the internal directory.