Go Project Layout
Introduction
When starting a new Go project, one of the first decisions you'll face is how to organize your code. A well-structured project makes your code more maintainable, readable, and collaborative. This guide will walk you through common Go project layouts, conventions, and best practices that will help you build scalable applications.
Unlike some programming languages, Go doesn't enforce a strict project structure. However, the Go community has developed conventions that have become standard practices. Understanding these patterns will help you create projects that are familiar to other Go developers and leverage the language's built-in tools effectively.
Basic Project Structure
Let's start with a minimal Go project structure:
my-go-project/
├── go.mod # Module definition
├── go.sum # Dependency checksums
├── main.go # Application entry point
├── README.md # Project documentation
└── LICENSE # License information
This simple structure works well for small applications or utilities. The main.go
file contains your application's entry point with the main()
function, while go.mod
defines your module and manages dependencies.
Example: Creating a Basic Go Project
Let's create a simple "Hello, World!" application with this structure:
# Create a directory for your project
mkdir hello-world
cd hello-world
# Initialize a Go module
go mod init github.com/yourusername/hello-world
# Create main.go file
touch main.go
Now, let's write a simple program in main.go
:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
To run this application:
go run main.go
Output:
Hello, World!
Standard Go Project Layout
As your project grows, you'll need a more organized structure. The Go community has developed a standard project layout that works well for most applications:
my-go-project/
├── cmd/ # Main applications
│ └── myapp/ # Application-specific directory
│ └── main.go # Application entry point
├── internal/ # Private code
│ ├── app/ # Application code
│ └── pkg/ # Private library code
├── pkg/ # Public library code
├── api/ # API specifications (OpenAPI, Protobuf, etc.)
├── configs/ # Configuration files
├── docs/ # Documentation
├── examples/ # Example code
├── scripts/ # Build and CI scripts
├── test/ # Test data and additional test utilities
├── web/ # Web assets (templates, static files)
├── go.mod # Module definition
├── go.sum # Dependency checksums
└── README.md # Project documentation
Let's explore each of these directories in detail:
cmd/
The cmd
directory contains the main applications for the project. Each subdirectory represents a separate executable application:
// cmd/myapp/main.go
package main
import (
"fmt"
"github.com/yourusername/my-go-project/internal/app"
)
func main() {
app := app.NewApp()
fmt.Println(app.Greeting("world"))
}
This structure is particularly useful when your project has multiple entry points or binaries.
internal/
The internal
directory contains code that's specific to your application and shouldn't be imported by other projects. Go's compiler enforces this restriction - any package path containing /internal/
can only be imported by code in the same module.
// internal/app/app.go
package app
type App struct{}
func NewApp() *App {
return &App{}
}
func (a *App) Greeting(name string) string {
return "Hello, " + name + "!"
}
pkg/
The pkg
directory contains code that's safe for external projects to import:
// pkg/greeting/greeting.go
package greeting
// GenerateGreeting creates a greeting message for the given name
func GenerateGreeting(name string) string {
return "Hello, " + name + "!"
}
Other projects can now import and use this code:
import "github.com/yourusername/my-go-project/pkg/greeting"
Project Layout Example
Let's see a more complete example of a REST API service using this layout:
flowchart TD A[my-service] --> B[cmd] A --> C[internal] A --> D[pkg] A --> E[api] A --> F[configs]
B --> G[api/main.go] B --> H[worker/main.go]
C --> I[handler] C --> J[middleware] C --> K[model] C --> L[repository] C --> M[service]
D --> N[logger] D --> O[validator]
Let's walk through what each component does:
cmd/api/main.go
- The entry point for the REST API servercmd/worker/main.go
- The entry point for a background workerinternal/handler
- HTTP handlers that process requestsinternal/middleware
- HTTP middleware (authentication, logging, etc.)internal/model
- Data models and business logicinternal/repository
- Data access layerinternal/service
- Business logic connecting handlers and repositoriespkg/logger
- Reusable logging packagepkg/validator
- Reusable validation utilities
Example Code: REST API Service
Here's a simplified implementation of this structure:
// cmd/api/main.go
package main
import (
"log"
"net/http"
"github.com/yourusername/my-service/internal/handler"
"github.com/yourusername/my-service/internal/repository"
"github.com/yourusername/my-service/internal/service"
"github.com/yourusername/my-service/pkg/logger"
)
func main() {
// Initialize components
log := logger.New()
repo := repository.NewUserRepository()
svc := service.NewUserService(repo)
handler := handler.NewUserHandler(svc, log)
// Set up HTTP routes
http.HandleFunc("/users", handler.GetUsers)
// Start server
log.Info("Starting server on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal("Server failed to start", err)
}
}
// internal/handler/user.go
package handler
import (
"encoding/json"
"net/http"
"github.com/yourusername/my-service/internal/service"
"github.com/yourusername/my-service/pkg/logger"
)
type UserHandler struct {
service service.UserService
logger *logger.Logger
}
func NewUserHandler(svc service.UserService, log *logger.Logger) *UserHandler {
return &UserHandler{
service: svc,
logger: log,
}
}
func (h *UserHandler) GetUsers(w http.ResponseWriter, r *http.Request) {
users, err := h.service.GetAllUsers()
if err != nil {
h.logger.Error("Failed to get users", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}