Go Project Anatomy
How a Go project is laid out
Section titled “How a Go project is laid out”Coming from Node.js/TypeScript, you are used to package.json, tsconfig.json, a src/ directory, and node_modules. Go projects have a different but equally structured layout. Understanding it up front will save you hours of confusion.
go.mod — the package.json of Go
Section titled “go.mod — the package.json of Go”go.mod is created by go mod init and is the root manifest for your Go module. It declares the module name (your import path prefix) and the minimum Go version required.
// package.json{ "name": "my-app", "version": "1.0.0", "scripts": { "build": "tsc", "start": "node dist/index.js" }, "dependencies": { "express": "^4.18.2" }, "devDependencies": { "typescript": "^5.0.0" }}// go.modmodule github.com/yourname/myapp
go 1.22
require ( github.com/gin-gonic/gin v1.9.1)
// go.sum is the lockfile (like package-lock.json)// it is auto-managed — never edit it manuallyThe module name (github.com/yourname/myapp) is not just a name — it is the import path prefix used throughout your code. Conventionally it matches your source repository URL.
Packages — one per directory
Section titled “Packages — one per directory”In Go, a package is a directory of .go files that all declare the same package name. This is stricter than TypeScript/ESM, where every file is its own module. In Go you import at the package level, not the file level.
// TypeScript: file-level importsimport { UserService } from './services/user.service';import type { User } from './models/user.model';import * as utils from '../shared/utils';// Go: package-level importsimport ( "github.com/yourname/myapp/internal/services" "github.com/yourname/myapp/internal/models" "github.com/yourname/myapp/pkg/utils")
// then use: services.NewUserService(...)// models.User{...}// utils.SomeHelper(...)Idiomatic project structure
Section titled “Idiomatic project structure”The Go community has converged on a standard layout for services and CLIs. Here it is compared to a typical Node/TypeScript service:
// TypeScript / Node.js projectmy-app/ src/ controllers/ # route handlers services/ # business logic models/ # TypeScript interfaces/types middleware/ # Express middleware index.ts # entry point dist/ # compiled output package.json tsconfig.json .env// Go project (idiomatic layout)myapp/ cmd/ myapp/ main.go # entry point (package main) internal/ handlers/ # HTTP handlers services/ # business logic models/ # struct definitions middleware/ # HTTP middleware pkg/ # reusable packages (public API) go.mod go.sum .envKey rules:
cmd/holds your entry points (mainpackages). One subdirectory per binary.internal/is Go-enforced private code — packages here cannot be imported by external modules.pkg/holds packages you intentionally expose for external use.
Exported vs unexported identifiers
Section titled “Exported vs unexported identifiers”In TypeScript you use export and import. In Go, visibility is determined by the first letter of an identifier’s name — no export keyword needed.
// TypeScript: explicit exportexport interface User { id: string; name: string; }export function greet(name: string): string { return `Hello, ${name}!`;}// internal helper (no export)function formatName(n: string): string { return n.trim(); }// Go: uppercase = exported, lowercase = unexportedtype User struct { // exported (capital U) ID string Name string}
func Greet(name string) string { // exported (capital G) return "Hello, " + formatName(name) + "!"}
func formatName(n string) string { // unexported (lowercase) return strings.TrimSpace(n)}Try it — packages and imports
Section titled “Try it — packages and imports”package main
import ( "fmt" "strings")
// Exported function — capital letterfunc Greet(name string) string { return "Hello, " + formatName(name) + "!"}
// unexported helper — lowercase letterfunc formatName(n string) string { return strings.ToUpper(n[:1]) + n[1:]}
func main() { fmt.Println(Greet("gopher")) fmt.Println(Greet("world"))
// The standard library is imported by path // "strings" -> the strings package fmt.Println(strings.ToUpper("go is great"))}Loading Go runtime (first run only, ~8 MB)…