Question

Import Local Packages Without GOPATH in Go Using Modules

gopackage

Question

How can I create and import packages that are local to a Go project without relying on GOPATH?

For example, I would like a project to contain multiple executables and reusable project-specific packages:

myproject/
├── binary1.go
├── binary2.go
├── package1.go
└── package2.go

I want code used by binary1.go or binary2.go to import functionality from package1, then build an executable without receiving an error that the package cannot be found in GOROOT or GOPATH. What is the correct Go project structure and import approach for larger projects that need multiple packages and files?

Short Answer

Go modules let a project define its own import root, so local packages do not need to live in GOPATH. You will learn why Go packages are directory-based, how to create a go.mod file, how to import project packages using a module path, and how to organize multiple executables cleanly.

Concept

In Go, a package is a directory, not an individual .go file. All ordinary Go source files in one directory must normally declare the same package name.

That means this structure cannot contain both an executable and a reusable package in the same directory:

myproject/
├── binary1.go     # package main
└── package1.go    # package package1

Go reports an error because a directory cannot mix package main and package package1.

The modern solution is Go modules. A module is a collection of packages identified by a module path in a go.mod file. Imports within that module use the module path plus the package directory.

For example, if go.mod says:

module example.com/alex/myproject

and a package is stored in internal/greeting, its import path is:

import "example.com/alex/myproject/internal/greeting"

Go finds that code relative to the module root. It does not need the package to be installed in GOPATH.

This matters because large Go applications are naturally split into focused packages: HTTP handlers, database access, business rules, configuration, and command-line programs can each live in appropriate directories.

Mental Model

Think of a Go module as a company campus.

  • The go.mod file is the campus address: example.com/alex/myproject.
  • Each directory is a department, such as internal/greeting or internal/config.
  • An import path is the full mailing address for a department.
  • Files inside the same department share the same package name and work together.

A single .go file is not a department by itself. To create a reusable package, place its files in their own directory.

Take Quiz

Syntax and Examples

Start a module from the project root:

go mod init example.com/alex/myproject

A practical layout with two executable commands and one reusable package is:

myproject/
├── go.mod
├── cmd/
│   ├── binary1/
│   │   └── main.go
│   └── binary2/
│       └── main.go
└── internal/
    └── greeting/
        └── greeting.go

internal/greeting/greeting.go:

package greeting

import "fmt"

func Hello(name string) string {
	return fmt.Sprintf("Hello, %s!", name)
}

cmd/binary1/main.go:

package main

import (
	"fmt"

	"example.com/alex/myproject/internal/greeting"
)

func main() {
	fmt.Println(greeting.Hello("Mina"))
}

Build the command from the module root:

go build ./cmd/binary1

The import path starts with the value in go.mod, followed by the directory containing the package. The package is referenced in code by its package name, greeting.

You can use a regular directory such as pkg/greeting instead of internal/greeting. The internal directory is useful for code that should be reusable only inside this module.

Step by Step Execution

Consider this code:

package main

import (
	"fmt"

	"example.com/alex/myproject/internal/greeting"
)

func main() {
	message := greeting.Hello("Mina")
	fmt.Println(message)
}

When you run:

go run ./cmd/binary1

Go performs these steps:

  1. It starts at the current directory and finds go.mod.
  2. It reads the module path, such as example.com/alex/myproject.
  3. It sees the import example.com/alex/myproject/internal/greeting.
  4. Because that import begins with the current module path, Go maps the remaining path to internal/greeting in the project.
  5. It compiles the greeting package and its Hello function.
  6. It compiles cmd/binary1 as package main.
  7. main calls greeting.Hello("Mina"), receiving "Hello, Mina!".
  8. fmt.Println prints the message.

Output:

Hello, Mina!

Real World Use Cases

Local packages are used whenever an application grows beyond a few files:

  • Web APIs: put routing, request validation, and response handling in separate packages.
  • Database code: isolate queries and repository logic from HTTP or command-line code.
  • Business rules: keep calculations, permissions, and workflows in testable packages.
  • Multiple commands: build an API server, migration tool, worker, and CLI from one module.
  • Data processing: separate file parsing, transformation, and output formatting into small packages.
  • Shared configuration: load environment variables in one package and use the result across commands.

For example, a service may use cmd/api for its server executable and internal/orders for order-related business logic.

Take Quiz

Real Codebase Usage

Most Go codebases place each executable command in its own cmd/<name> directory:

cmd/
├── api/
│   └── main.go
└── worker/
    └── main.go

This avoids having several main functions in one directory and makes each build target explicit:

go build ./cmd/api
go build ./cmd/worker

Application-only code is often stored under internal/:

internal/
├── config/
├── database/
├── service/
└── transport/

The internal rule is enforced by Go: packages outside the parent tree of internal cannot import them. This prevents another module from treating private implementation details as a public API.

Common project patterns include:

  • Validation: a handler calls a validation package before doing work.
  • Early returns: functions return errors immediately when input or configuration is invalid.
  • Configuration: one package reads environment variables and returns typed settings.
  • Error handling: packages return errors; the command package decides how to log them and exit.
  • Tests: each package has focused _test.go files beside its implementation files.

Common Mistakes

Treating a file as a package

This does not create a separate importable package:

myproject/
├── main.go
└── greeting.go

If main.go uses package main and greeting.go uses package greeting, the directory contains mixed packages and will fail to build.

Avoid it: move greeting.go into a subdirectory such as internal/greeting/greeting.go.

Importing only a local folder name

This usually fails in module mode:

import "greeting"

Go expects an import path, not merely the package name.

Avoid it: import the module path plus directory:

import "example.com/alex/myproject/internal/greeting"

Using relative imports

This is not supported in module-based Go code:

import "./greeting"

Avoid it: use a module import path.

Forgetting go.mod

Without a module definition, Go cannot reliably determine your project's import root.

Avoid it: run this once in the project root:

go mod init example.com/alex/myproject

Building a file when you mean to build a package

A command such as this builds only the named file:

go build binary1.go

It can be useful for small experiments, but it is less reliable for a multi-file command and does not match the usual multi-command layout.

Avoid it: build a package directory instead:

go build ./cmd/binary1

Importing an unexported identifier

Only names beginning with an uppercase letter are available to importing packages:

func hello() string { // not accessible outside this package
	return "hello"
}

Avoid it: export it when it is part of the package API:

func Hello() string {
	return "hello"
}

Comparisons

ApproachHow it worksWhen to use it
Go modulesUses go.mod and module-based import pathsStandard approach for modern Go projects
GOPATH workspaceUses a special directory layout under GOPATH/srcPrimarily legacy projects
internal/ packageA module-local package protected from outside importersPrivate application implementation
Regular package directoryCan be imported by other modules if its module is availableIntended public or reusable APIs
One package, many filesMultiple .go files in one directory use the same package declarationSplitting one cohesive package into files
Multiple packagesEach package lives in a different directorySeparating responsibilities and APIs

A package name and an import path are related but different. For example:

import "example.com/alex/myproject/internal/greeting"

The path identifies the location. The code normally refers to the declared package name:

greeting.Hello("Mina")
Take Quiz

Cheat Sheet

# Create a module in the project root
go mod init example.com/you/project

# Run or build a command package
go run ./cmd/app
go build ./cmd/app

# Format all Go files
gofmt -w .

# Test every package in the module
go test ./...
project/
├── go.mod
├── cmd/
│   └── app/
│       └── main.go       # package main
└── internal/
    └── feature/
        └── feature.go    # package feature
// Import path = module path + package directory
import "example.com/you/project/internal/feature"

// Exported identifiers begin with uppercase letters.
feature.DoWork()

Rules to remember:

  • A Go package is a directory.
  • Normal .go files in one directory use one package name.
  • Every executable has package main and a main() function.
  • Do not use relative imports such as ./feature in module projects.
  • Use internal/ for packages that must not be imported by unrelated modules.
  • Prefer go build ./path/to/package over naming an individual source file for application builds.

FAQ

Do I need GOPATH to import local packages in Go?

No. Go modules are the standard approach. Create go.mod in the project root and use module-based import paths.

Can I import a Go package by only its folder name?

Usually no. Use the full module import path, such as example.com/you/project/internal/config.

Can different Go packages be in the same directory?

No, except that external test files may use a separate <package>_test package. Ordinary source files in a directory must use the same package name.

Why does Go use a URL-like module path for local code?

The path is a unique identifier. It can use a future repository location, such as github.com/you/project, but it does not need to be publicly hosted for local development.

What is the difference between cmd and internal?

cmd contains executable programs, usually one directory per command. internal contains implementation packages that are restricted to the surrounding project tree.

Can I have two executables in one Go module?

Yes. Put each executable in its own directory, for example cmd/server and cmd/worker.

Should I run go build binary1.go?

It works for simple single-file cases, but building a package directory such as go build ./cmd/binary1 is the conventional and scalable choice.

Does the package name have to match the directory name?

No, but matching them is strongly recommended because it makes imports easier to understand.

Take Quiz

Mini Project

Description

Create a small Go module with two commands that share one project-local greeting package. This demonstrates the directory-based package rule, module import paths, and the conventional cmd and internal project layout.

Goal

Build and run two independent executables that import the same local package without using GOPATH.

Requirements

Create a Go module with a go.mod file at the project root.

Create a reusable greeting package in an internal/greeting directory.

Create separate hello and goodbye executable commands under cmd.

Make both commands import and use the local greeting package.

Run each command from the module root.

Take Quiz

Keep learning

Related questions

View more