Web Analytics

Packages & Modules

Intermediate ~30 min read

Packages are Go's way of organizing code. Modules are collections of packages with versioning. In this lesson, you'll learn how to create packages, use modules, and organize Go projects professionally.

What is a Package?

A package is a collection of Go source files in the same directory that are compiled together.

Package Rules:
  • Every Go file belongs to a package
  • Package name is declared at the top: package name
  • Package name should match directory name (convention)
  • main package is specialβ€”it's executable
  • Exported names start with capital letter

Creating a Package

// File: math/calculator.go
package math

// Add is exported (capital A)
func Add(a, b int) int {
    return a + b
}

// subtract is not exported (lowercase s)
func subtract(a, b int) int {
    return a - b
}

Using a Package

// File: main.go
package main

import (
    "fmt"
    "myproject/math"  // Import custom package
)

func main() {
    result := math.Add(5, 3)
    fmt.Println(result)  // 8
    
    // math.subtract(5, 3)  // Error: subtract is not exported
}

Go Modules

Modules are collections of packages with dependency management and versioning.

Creating a Module

# Initialize a new module
go mod init github.com/username/myproject

# This creates go.mod file

go.mod File

module github.com/username/myproject

go 1.21

require (
    github.com/gorilla/mux v1.8.0
    github.com/lib/pq v1.10.9
)
Module Commands:
  • go mod init - Initialize module
  • go mod tidy - Add missing/remove unused dependencies
  • go get package@version - Add/update dependency
  • go mod download - Download dependencies
  • go mod vendor - Copy dependencies to vendor/

Project Structure

Standard Layout

myproject/
β”œβ”€β”€ go.mod
β”œβ”€β”€ go.sum
β”œβ”€β”€ main.go
β”œβ”€β”€ cmd/
β”‚   └── server/
β”‚       └── main.go
β”œβ”€β”€ internal/
β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   └── auth.go
β”‚   └── database/
β”‚       └── db.go
β”œβ”€β”€ pkg/
β”‚   └── utils/
β”‚       └── helpers.go
└── vendor/
Directory Conventions:
  • cmd/ - Main applications (executables)
  • internal/ - Private packages (not importable by others)
  • pkg/ - Public packages (importable by others)
  • vendor/ - Vendored dependencies
  • api/ - API definitions (OpenAPI, Protocol Buffers)
  • web/ - Web assets (HTML, CSS, JS)

Import Statements

Import Styles

// Single import
import "fmt"

// Multiple imports
import (
    "fmt"
    "os"
    "strings"
)

// Aliased import
import (
    f "fmt"
    str "strings"
)

// Blank import (for side effects)
import _ "github.com/lib/pq"

// Dot import (not recommended)
import . "fmt"  // Now can use Println instead of fmt.Println

Import Paths

import (
    // Standard library
    "fmt"
    "net/http"
    
    // External packages
    "github.com/gorilla/mux"
    "github.com/lib/pq"
    
    // Local packages
    "myproject/internal/auth"
    "myproject/pkg/utils"
)
Best Practice: Group imports into three sections: standard library, external packages, and local packages. Use goimports to format automatically.

Exported vs Unexported

package user

// Exported type (capital U)
type User struct {
    Name  string  // Exported field
    email string  // Unexported field
}

// Exported function
func NewUser(name, email string) *User {
    return &User{
        Name:  name,
        email: email,
    }
}

// Exported method
func (u *User) GetEmail() string {
    return u.email
}

// Unexported function
func validateEmail(email string) bool {
    return strings.Contains(email, "@")
}
Pro Tip: Use unexported fields with exported getter/setter methods to control access and maintain encapsulation.

Package Initialization

package database

import "database/sql"

var db *sql.DB

// init runs automatically when package is imported
func init() {
    var err error
    db, err = sql.Open("postgres", "connection-string")
    if err != nil {
        panic(err)
    }
}

func GetDB() *sql.DB {
    return db
}
Important: init() functions run automatically in order:
  1. Imported packages' init functions
  2. Package-level variables
  3. Current package's init functions
Use sparinglyβ€”explicit initialization is often better!

Common Mistakes

1. Circular imports

// ❌ Wrong - circular dependency
// package a imports package b
// package b imports package a
// Error: import cycle not allowed

// βœ… Correct - extract common code
// Create package c with shared code
// Both a and b import c

2. Package name doesn't match directory

// ❌ Wrong
// Directory: utils/
// File: package helpers

// βœ… Correct
// Directory: utils/
// File: package utils

3. Not using go mod tidy

# ❌ Wrong - manually editing go.mod
# Don't manually add dependencies

# βœ… Correct - use go commands
go get github.com/gorilla/mux
go mod tidy  # Clean up

Exercise: Create a Math Package

Task: Create a reusable math package.

Requirements:

  • Create a module called mathlib
  • Create package calculator with Add, Subtract, Multiply, Divide
  • Create package geometry with Circle and Rectangle types
  • Export appropriate functions and types
  • Use from main.go
Show Solution
# Project structure
mathlib/
β”œβ”€β”€ go.mod
β”œβ”€β”€ main.go
β”œβ”€β”€ calculator/
β”‚   └── calculator.go
└── geometry/
    └── shapes.go
// go.mod
module mathlib

go 1.21

// calculator/calculator.go
package calculator

func Add(a, b float64) float64 {
    return a + b
}

func Subtract(a, b float64) float64 {
    return a - b
}

func Multiply(a, b float64) float64 {
    return a * b
}

func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// geometry/shapes.go
package geometry

import "math"

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

type Rectangle struct {
    Width  float64
    Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// main.go
package main

import (
    "fmt"
    "mathlib/calculator"
    "mathlib/geometry"
)

func main() {
    // Calculator
    sum := calculator.Add(10, 5)
    fmt.Printf("10 + 5 = %.2f\n", sum)
    
    diff := calculator.Subtract(10, 5)
    fmt.Printf("10 - 5 = %.2f\n", diff)
    
    product := calculator.Multiply(10, 5)
    fmt.Printf("10 * 5 = %.2f\n", product)
    
    quotient, err := calculator.Divide(10, 5)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Printf("10 / 5 = %.2f\n", quotient)
    }
    
    // Geometry
    circle := geometry.Circle{Radius: 5}
    fmt.Printf("\nCircle (r=5):\n")
    fmt.Printf("  Area: %.2f\n", circle.Area())
    fmt.Printf("  Perimeter: %.2f\n", circle.Perimeter())
    
    rect := geometry.Rectangle{Width: 4, Height: 6}
    fmt.Printf("\nRectangle (4x6):\n")
    fmt.Printf("  Area: %.2f\n", rect.Area())
    fmt.Printf("  Perimeter: %.2f\n", rect.Perimeter())
}

Summary

  • Packages organize code into reusable units
  • Modules manage dependencies and versioning
  • Exported names start with capital letter
  • go.mod defines module and dependencies
  • Standard layout: cmd/, internal/, pkg/
  • init() runs automatically on import
  • Avoid circular imports by extracting common code

What's Next?

Now that you understand packages and modules, you're ready to learn about File I/O. You'll discover how to read and write files, work with directories, and handle file operations in Go.