Zurück zum Blog

Go-Projektstruktur (2026): Leitfaden für idiomatisches Layout

Die Ordnerstruktur, die die meisten Einsteiger aus einem Repo mit 50.000 Sternen kopieren, ist kein offizieller Go-Standard, und das Go-Team sagt das selbst. So strukturierst du ein Go-Projekt nach dem offiziellen Ansatz, und der ist einfacher, als du denkst.

Go-Projektstruktur (2026): Leitfaden für idiomatisches Layout

Such nach „Go-Projektstruktur“ und nach zwei Klicks landest du bei golang-standards/project-layout, einem GitHub-Repo mit mehr als 50.000 Sternen und einem Verzeichnisbaum voller cmd/, pkg/, internal/, api/ und build/. Das wirkt wie eine verbindliche Vorgabe, und die meisten Einsteiger übernehmen den kompletten Baum für ein Programm aus fünf Dateien.

Ein offizieller Standard ist es allerdings nicht, und das Go-Team hat das öffentlich gesagt. Die offizielle Empfehlung ist einfacher: Starte flach und leg Ordner erst an, wenn der Code sie braucht. Die folgenden Abschnitte gehen diese Empfehlung Ordner für Ordner durch und nutzen dabei ein Wetter-CLI und eine Buchladen-API als durchgehende Beispiele.

Was ist die Standard-Projektstruktur in Go?

Es gibt keinen einheitlich vorgeschriebenen Verzeichnisbaum. Go verlangt weder src/ noch cmd/ und überhaupt keinen Ordner außer einer go.mod-Datei im Modul-Root. Der offizielle Leitfaden „Organizing a Go module“ knüpft das Layout an die Projektgröße. Er zeigt vier Layouts, jedes eine Stufe größer als das vorherige: ein einfaches Package, ein einfaches Command, ein Package, zu dem Hilfs-Packages hinzukommen, und ein „Server“-Projekt mit mehreren Binaries. (Quelle)

Die sinnvolle Frage ist also, wie viel Struktur dein Projekt gerade braucht, und für die meisten Programme lautet die Antwort: sehr wenig. Der Rest dieses Beitrags geht die vier Layouts vom einfachsten bis zum aufwendigsten durch. Such dir das Layout, das zu deinem Projekt passt, und hör dort auf.

Flach starten: Ein einziges Package ist idiomatisch

Die ersten beiden Layouts der offiziellen Doku sind ein einzelnes Package und ein einzelnes Command, beide direkt im Modul-Root ohne Unterverzeichnisse. Für ein kleines Kommandozeilentool, das eine Stadt einliest und das Wetter ausgibt, ist das schon das ganze Projekt:

example.texttext
weather/
├── go.mod
├── main.go
├── weather.go
└── weather_test.go

go.mod deklariert den Modulpfad (etwa module github.com/you/weather). main.go enthält package main und den Einstiegspunkt. weather.go enthält die Logik, und zwar im selben Package. weather_test.go enthält die Tests, direkt neben dem Code, den sie abdecken, und dort erwartet Go sie. So kannst du das Programm ausliefern. Die offizielle Doku nennt diese Form „Basic command“, und sie ist die richtige für die meisten Tools, die du in deinem ersten Jahr schreibst.

Eine Regel erklärt die meisten Layout-Entscheidungen, die später kommen: Ein Verzeichnis ist ein Package. Jede Datei in einem Verzeichnis deklariert dasselbe Package, und der Package-Name ist das letzte Element des Importpfads. Legst du ein Verzeichnis weather/store/ an, steht in jeder Datei darin package store. Du importierst es als github.com/you/weather/store und rufst es als store.Get auf.

Leg noch keine Ordner an. Ein utils/-Verzeichnis mit einer einzigen Funktion oder ein models/-Verzeichnis mit einem einzigen Struct bringt einen zusätzlichen Importpfad und eine Package-Grenze und gibt dir nichts zurück. Halte das Projekt flach, bis die flache Struktur echte Probleme macht.

Wann solltest du ein internal/-Verzeichnis anlegen?

Der erste gute Grund für mehr Struktur ist Kapselung. Go kennt für Bezeichner zwei Sichtbarkeitsstufen: Namen mit großem Anfangsbuchstaben sind exportiert, und Namen mit kleinem Anfangsbuchstaben sind privat im Package. Innerhalb eines Packages funktioniert das. Sobald dein Modul aber mehrere Packages hat, willst du oft Code, den jedes Package innerhalb deines Moduls nutzen kann und den nichts außerhalb importieren kann. Das bekommst du mit internal/, und der Compiler setzt es durch.

Das ist Teil der Sprache, keine Namenskonvention. Ein Package unter einem Verzeichnis namens internal/ kann nur von Code importiert werden, der unterhalb des Elternverzeichnisses dieses internal/-Verzeichnisses liegt, und ein Import aus einem anderen Modul lässt den Build fehlschlagen. Die offizielle Doku empfiehlt, Packages „so weit wie möglich“ in internal zu halten. Externer Code kann sie nicht importieren, also kann er auch nicht von ihnen abhängig werden, und du musst nie eine API pflegen, die du gar nicht veröffentlichen wolltest. (Quelle)

Angenommen, aus dem Wetter-CLI wird eine kleine HTTP-API für einen Buchladen. Sie hat jetzt eine Authentifizierung für Requests und eine Datenzugriffsschicht, und keins von beiden gehört in eine öffentliche API:

example.texttext
bookstore/
├── go.mod
├── main.go
└── internal/
    ├── auth/
    │   ├── auth.go
    │   └── auth_test.go
    └── store/
        ├── store.go
        └── store_test.go

main.go liegt im Modul bookstore und kann deshalb github.com/you/bookstore/internal/auth und github.com/you/bookstore/internal/store frei importieren. Code in jedem anderen Modul kann das nicht. Führt jemand go get für dein Modul aus und versucht, diese Packages zu importieren, lehnt der Compiler den Import ab. Weil niemand außerhalb deines Moduls von auth oder store abhängen kann, kannst du ihre Funktionen jederzeit umbenennen, ihre Signaturen ändern oder sie umbauen, ohne fremden Code kaputtzumachen.

Wann solltest du ein cmd/-Verzeichnis anlegen?

cmd/ ist für Repositories gedacht, die mehr als ein Binary bauen oder sowohl ein Binary als auch eine importierbare Library ausliefern. Hast du nur ein Binary, brauchst du es nicht, und main.go im Root ist die sauberere Lösung.

Unter cmd/ bekommt jedes Binary ein eigenes Unterverzeichnis, und der Verzeichnisname wird zum Programmnamen. Das offizielle Layout „Server project“ macht es so: Jedes Command liegt in cmd/<name>/ mit einer kleinen main.go, die Packages aus internal/ miteinander verdrahtet. (Quelle) Angenommen, der Buchladen liefert jetzt einen API-Server und ein Tool für Datenbankmigrationen aus:

example.texttext
bookstore/
├── go.mod
├── cmd/
│   ├── api/
│   │   └── main.go
│   └── migrate/
│       └── main.go
└── internal/
    ├── auth/
    └── store/

go build ./cmd/api erzeugt ein Binary namens api, und go build ./cmd/migrate erzeugt migrate. Beide main.go-Dateien bleiben schlank und importieren die eigentliche Arbeit aus internal/.

Hier ist die ganze Abfolge in einer Tabelle. Wähl die Zeile, die zu deinem Projekt in seinem heutigen Zustand passt, nicht zu dem Projekt, das es deiner Vorstellung nach einmal wird.

Art des ProjektsEmpfohlenes LayoutWarum
Kleines Tool oder SkriptFlach: main.go + Logikdateien im RootEin einziges Package ist idiomatisch und vollständig
Ein Binary mit wiederverwendbaren Internainternal/ für private Packages hinzufügenVom Compiler erzwungene Kapselung, keine voreilige API
Zwei oder mehr Binariescmd/<name>/ pro Binary hinzufügenJedes main bekommt ein eigenes Verzeichnis
Repo mit mehreren Services (Binaries + gemeinsamer Code)cmd/ + internal/, nach Domäne aufgeteiltDas offizielle Layout „Server project“

Der Mythos golang-standards/project-layout

Das Repo golang-standards/project-layout trägt „standards“ im Namen seiner GitHub-Organisation, hat mehr als 50.000 Sterne und einen großen Baum aus api/, build/, configs/, deployments/, pkg/ und test/. Man nimmt leicht an, das Go-Team habe es abgesegnet, aber das hat es nicht. Das README des Repos enthält inzwischen einen Hinweis, dass es kein offizieller Standard ist, und der Einwand kam aus der Führung von Go selbst.

In Issue #117 schrieb Russ Cox, Tech Lead von Go, dies sei „kein Standard-Layout für Go-Projekte“. Die meisten Go-Repositories seien „viel einfacher“ und nutzten überhaupt kein pkg/-Verzeichnis. Der Thread landete auf der Startseite von Hacker News, wo erfahrene Go-Entwickler ihm überwiegend zustimmten. Für ein großes Monorepo, das sich mehrere Teams teilen, kann das Layout sinnvoll sein, als Startvorlage für ein normales Projekt schadet es aber. Kopierst du es in ein kleines Programm, landen deine fünf Dateien unter acht fast leeren Ordnern.

Daraus ergeben sich ein paar Regeln, und sie decken sich mit dem, was Go-Praktiker wie Eli Bendersky und Alex Edwards häufig raten:

  • Verwende kein src/-Verzeichnis. Diese Gewohnheit stammt aus Java und älteren Node-Projekten. Go-Code liegt im Modul-Root, und kein offizielles Layout hat ein src/.
  • Verschachtele keine Ordner, bevor du Code hast, der hineingehört. Leere Ordner wie api/, build/ und deployments/ bringen nichts. Leg einen Ordner an dem Tag an, an dem du etwas für ihn hast.
  • Benenne Packages nach dem, was sie bereitstellen, nicht nach ihrer Rolle. Ein Package namens utils, common oder helpers sagt dem Leser nichts und wird zur Rumpelkammer. Nenn es store, auth, weather, retry oder slugify. Die offizielle Go-Empfehlung zu Package-Namen lautet, sie kurz und klar zu halten und so zu wählen, dass sie sich an der Aufrufstelle gut lesen. Deshalb ist store.Get besser als utils.GetFromStore. (Quelle)

Fehler, die im Code statt in den Ordnern auftauchen, behandelt unser Leitfaden zu häufigen Fehlern in Go, die du vermeiden solltest.

Eine Faustregel für Einsteiger zum Layout von Go-Projekten

Die Abfolge lässt sich auf ein paar Fragen zu deinem Projekt reduzieren. Geh sie von oben nach unten durch und füge ein Verzeichnis nur hinzu, wenn eine Antwort es verlangt.

flowchart TD
    A["New Go project"] --> B["Is it one package<br/>of related code?"]
    B -->|Yes| C["Keep it flat:<br/>main.go + logic in the root"]
    B -->|"Grew past that"| D["Need to hide code<br/>from outside importers?"]
    D -->|Yes| E["Add internal/<br/>put private packages there"]
    E --> F["More than one<br/>binary to build?"]
    F -->|No| G["Done. main.go in root,<br/>logic in internal/"]
    F -->|Yes| H["Add cmd/name/<br/>one dir per binary"]
    H --> I["Many services sharing code?"]
    I -->|Yes| J["Server layout:<br/>cmd/ + internal/ by domain"]

Jeder Schritt beantwortet einen konkreten Bedarf: eine Package-Grenze, ein zweites Binary oder Code, den du privat halten willst. Kannst du auf keinen davon zeigen, brauchst du den Ordner wahrscheinlich nicht. Starte flach, füge internal/ hinzu, wenn der Compiler die Kapselung durchsetzen soll, füge cmd/ hinzu, wenn du ein zweites Binary hast, und wechsle erst zum vollständigen Server-Layout, wenn das Repo wirklich mehrere Services enthält.

Häufig gestellte Fragen

Gibt es eine offizielle Projektstruktur für Go?

Nein, es gibt keine einheitlich vorgeschriebene Ordnerstruktur. Das offizielle Dokument „Organizing a Go module“ beschreibt statt einer festen Struktur ein Layout, das mit der Projektgröße wächst. Es geht von einem flachen Package zu internal/, dann zu cmd/ und dann zu einem vollständigen Server-Layout. Nimm das kleinste Layout, das zu deinem Projekt passt. (Quelle)

Sollte ich das Verzeichnis pkg/ verwenden?

Meistens nicht. Russ Cox, Tech Lead von Go, merkte an, dass die meisten Go-Repositories kein pkg/-Verzeichnis nutzen und „tendenziell viel einfacher“ sind. Leg privaten Code in internal/, wo der Compiler die Kapselung durchsetzt, und stell Packages nur dann im Modul-Root bereit, wenn du sie als öffentliche API pflegen willst. (Quelle)

Was gehört in internal/ und was in cmd/?

internal/ enthält importierbaren Library-Code, den nur dein eigenes Modul verwenden kann, und das setzt der Compiler durch. cmd/<name>/ enthält das schlanke main-Package für jedes Binary, das du baust, und dieses verdrahtet Code aus internal/. Die Logik kommt in internal/, und die Einstiegspunkte kommen in cmd/. (Quelle)

Kann ein echtes Go-Projekt aus einer einzigen Datei oder einem einzigen Package bestehen?

Ja. Das erste Beispiel im offiziellen Layout-Leitfaden ist ein einzelnes Package im Modul-Root, und viele Tools in Produktion werden so ausgeliefert. Ein Package mit einer go.mod, einer main.go und den zugehörigen Tests ist idiomatisch und vollständig. Füge Struktur hinzu, wenn der Code darüber hinauswächst. (Quelle)

Wie unterscheidet sich ein Go-Projekt von einem Java- oder Node-Projekt?

Go hat kein src/-Verzeichnis. Code liegt im Modul-Root, jedes Verzeichnis ist ein Package, und der Package-Name ist das letzte Element des Pfads. Kein Build-Tool gibt einen Verzeichnisbaum vor, wie es Maven oder ein Bundler tut. Deshalb bleiben Go-Layouts flacher und wachsen nur bei Bedarf. (Quelle)

Wie es jetzt weitergeht

Das Layout gehört zu den leichteren Teilen von Go. Du lieferst ein Programm flach aus und fügst internal/ und cmd/ hinzu, wenn der Code sie braucht. Kannst du nicht sagen, warum ein Ordner existiert, kannst du ihn wahrscheinlich löschen.

Ein Gefühl dafür bekommst du am besten, indem du ein paar echte Programme baust, etwa ein CLI und einen HTTP-Service, und die Struktur dabei mitwachsen lässt. Der Lernpfad Clean Go Code von LevelUpGo behandelt idiomatisches Package-Design und Projektstruktur mit Übungen, die du im Browser schreibst und ausführst, und Go Fundamentals holt dich bei deiner ersten go.mod und der Toolchain ab. Du kannst dir auch die komplette interaktive Roadmap ansehen.

Schreib Go wie ein Senior Engineer

Interaktive Lektionen im Browser. Die ersten sind kostenlos.

Kostenlose Lektion testenOder kostenloses Konto erstellen