1.4 Structure Your Go Project Right(cmd, internal, pkg)

1.4 Structure Your Go Project Right(cmd, internal, pkg)

TL;DR

  • Put your main, runnable programs in a /cmd directory. Think of this as the "On" switch for your application.
  • Hide private code that only your project should use inside an /internal directory. This is your secret workshop.
  • Place code that other projects can safely use in a /pkg directory. 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 database toolbox or a text-formatting toolbox. In Go, it's just a directory with some .go files in it.
  • Module: A module is your entire project—a collection of all your toolboxes (packages) that are versioned and shipped together.
  • main package: This is the special "ignition key" package. When Go sees package 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 inside cmd is 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 your internal folder.
  • 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.

TermSimple DefinitionWhere It Shows Up
ModuleThe entire project.The root folder with a go.mod file.
PackageA 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?

DirectoryWhen to Use ItCan others import it?Analogy
internal/For code that is specific to your application and not useful to others.NoYour secret family recipe.
pkg/For code you want to share with the world, that others can import into their projects.YesA 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 in pkg/ by default. It signals that the code is safe for public use. If it's not, use internal/.

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, and pkg for a tiny project. Start with one main.go file and add these directories only when you need the organization.
  • Putting everything in pkg/. This is a common mistake. Putting code in pkg/ sends a signal that it's stable and safe for public use. If the code is specific to your app, it belongs in internal/.
  • 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.go is fine for small projects.
  • Use cmd/: When you create a runnable application, place its main package in cmd/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.