Internal Packages and Workspaces
internal/, replace directives, go work
Internal Packages and Workspaces is a free Go Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Go Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The internal/ Directory
Packages inside an internal/ directory can only be imported by code in the parent directory tree. This enforces package encapsulation:
// Project structure:
// myapp/
// main.go
// internal/
// auth/auth.go <- can only be imported by myapp/
// db/db.go <- can only be imported by myapp/
// api/
// handlers.go <- can import myapp/internal/auth
// External package CANNOT do:
// import "myapp/internal/auth" <- compile errorUsing internal/ Packages
Code within the same module (above the internal/ directory) can freely import internal packages:
// myapp/api/handlers.go
package api
import (
"myapp/internal/auth"
"myapp/internal/db"
)
func GetUser(id int) (*db.User, error) {
if !auth.IsValid() {
return nil, auth.ErrUnauthorized
}
return db.FindUser(id)
}Why Use internal/?
Benefits of the internal/ convention:
- Prevents other modules from depending on implementation details
- Lets you refactor freely without breaking external callers
- Clear signal: this is not part of the public API
- Standard Go tooling enforces the restriction at compile time
Go Workspaces — go work
Go 1.18 introduced workspaces for working with multiple local modules simultaneously without replace directives:
# Create a workspace in parent directory:
go work init ./myapp ./mylib
# Creates go.work:
# go 1.21
# use (
# ./myapp
# ./mylib
# )
# Now myapp can import mylib without replace directivesgo work sync
Synchronize workspace dependencies with go work sync:
# go.work file:
go 1.21
use (
./api
./worker
./shared
)
# Sync all module dependencies:
go work sync
# Build across all workspace modules:
go build ./...
# Test across all workspace modules:
go test ./...Workspace vs replace Directive
Comparison of two approaches for local multi-module development:
// BEFORE workspaces (cumbersome):
// go.mod in myapp:
// replace github.com/me/mylib => ../mylib
// Must remove before publishing!
// WITH workspaces (cleaner):
// go.work at root:
// use (./myapp ./mylib)
// go.mod files stay clean — go.work is gitignoredWorkspace Use Cases
When to use Go workspaces:
- Developing a library and its consumer simultaneously
- Monorepo with multiple modules that reference each other
- Testing changes to a shared module before publishing
- Working on a fork of a dependency alongside your app
go work add and go work use
Manage workspace entries with go work subcommands:
# Add a module to the workspace
go work use ./newservice
# Remove a module from workspace
go work use -r ./oldservice
# Edit go.work directly (opens $EDITOR):
go work edit
# go.work should usually be gitignored:
echo "go.work" >> .gitignore
echo "go.work.sum" >> .gitignoreInternal + Workspace Together
Internal packages and workspaces combine naturally in monorepos:
// Monorepo structure:
// services/
// go.work
// auth-service/
// internal/tokens/ <- private to auth-service
// order-service/
// internal/pricing/ <- private to order-service
// shared/
// models/ <- shared across services
// go.work:
// use (./auth-service ./order-service ./shared)Best Practices for Package Organization
Go project layout recommendations:
- Flat package structure for small projects — avoid premature organization
- Use
internal/for implementation details not meant for external use - Put CLI main packages under
cmd/appname/ - Use workspaces for multi-module repos, but gitignore
go.work - Avoid deep nesting — max 3-4 levels of packages
Quick Check
Who can import a package in an internal/ directory?
Recap: Internal Packages & Workspaces
Summary:
internal/restricts imports to the parent directory tree — enforced by the compiler- Use
internal/to hide implementation details from external consumers go work initcreates a workspace for multi-module local development- Workspaces replace
replacedirectives for local development - Gitignore
go.workandgo.work.sum
Frequently asked questions
Is the “Internal Packages and Workspaces” lesson free?
Yes — the full text of “Internal Packages and Workspaces” is free to read here on the web, and the Go Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Go Academy course, upgrade to CoddyKit PRO.
What will I learn in “Internal Packages and Workspaces”?
internal/, replace directives, go work You practise Go Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Go Academy?
No prior experience is required. Go Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Internal Packages and Workspaces” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Go Academy lesson?
Yes. Every Go Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Packages: Organizing Go Code
- Go Modules with go mod
- Adding External Dependencies
- Internal Packages and Workspaces