Echo — Structure#

Layout pattern#

Flat / Framework-specific

Echo uses a deliberately flat layout with the entire framework core in the root package (github.com/labstack/echo/v5). There is no cmd/, no internal/, and no pkg/ hierarchy. This is a conscious design choice for a library framework: consumers import one package, the API surface is immediately visible, and there is no internal abstraction tax. Only two sub-packages exist: middleware/ for built-in middleware and echotest/ for testing utilities offered to consumers.

Directory map#

echo/                         — Root package: the entire Echo framework
├── echo.go                   — Echo struct, route registration, server start
├── context.go                — Context concrete struct (HTTP req/resp + helpers)
├── context_generic.go        — Generic helpers (GetParamAs[T], etc.) — Go 1.18+
├── binder.go                 — Request data binding (JSON, XML, form, path, query)
├── binder_generic.go         — Type-safe generic binding helpers
├── bind.go                   — DefaultBinder implementation
├── router.go                 — Radix-tree HTTP router
├── router_concurrent.go      — Concurrent-safe router wrapper
├── route.go                  — Route struct and metadata
├── group.go                  — Route group (prefix + shared middleware)
├── server.go                 — HTTP/HTTPS server lifecycle (Start, StartTLS, etc.)
├── response.go               — ResponseWriter wrapper tracking status/size
├── renderer.go               — Template renderer interface + default
├── httperror.go              — HTTPError type and helpers
├── ip.go                     — Real IP extraction from headers/RemoteAddr
├── json.go                   — JSON codec abstraction
├── vhost.go                  — Virtual host routing support
├── version.go                — Version constant
│
├── middleware/               — Built-in middleware (24 implementations)
│   ├── middleware.go         — Shared middleware utilities and config helpers
│   ├── basic_auth.go         — HTTP Basic Authentication
│   ├── body_dump.go          — Request/response body logging
│   ├── body_limit.go         — Request body size limiting
│   ├── compress.go           — Gzip/Deflate response compression
│   ├── context_timeout.go    — Per-request context deadline
│   ├── cors.go               — CORS (Cross-Origin Resource Sharing)
│   ├── csrf.go               — CSRF token protection
│   ├── decompress.go         — Request decompression
│   ├── extractor.go          — Value extraction from request (headers, cookies, etc.)
│   ├── key_auth.go           — API key authentication
│   ├── method_override.go    — HTTP method override (X-HTTP-Method-Override)
│   ├── proxy.go              — Reverse proxy
│   ├── rate_limiter.go       — Token bucket rate limiting
│   ├── recover.go            — Panic recovery
│   ├── redirect.go           — HTTP→HTTPS and trailing-slash redirects
│   ├── request_id.go         — Unique request ID injection
│   ├── request_logger.go     — Structured request logging
│   ├── rewrite.go            — URL rewriting
│   ├── secure.go             — Security headers (XSS, HSTS, etc.)
│   ├── slash.go              — Trailing slash addition/removal
│   ├── static.go             — Static file serving (non-Windows)
│   ├── static_other.go       — Static file serving (Windows, build-tagged)
│   └── util.go               — Shared middleware utility functions
│
├── echotest/                 — Public testing helpers for Echo consumers
│   ├── context.go            — NewRequest / NewResponseRecorder helpers
│   ├── reader.go             — Helpers for reading response bodies in tests
│   └── testdata/             — Fixture files used by echotest itself
│
├── _fixture/                 — Test fixtures (leading _ = ignored by Go tooling)
│   ├── certs/                — TLS certificates for server tests
│   ├── dist/public/          — Static asset tree for static middleware tests
│   ├── images/               — Image files for file-serving tests
│   └── folder/               — Directory-listing fixture
│
├── .github/workflows/        — GitHub Actions CI
│   ├── echo.yml              — Matrix test (ubuntu/macos/windows × Go 1.25/1.26) + benchstat
│   └── checks.yml            — Lint / vet checks
│
├── Makefile                  — Developer workflow targets
├── go.mod                    — Module: github.com/labstack/echo/v5, go 1.25.0
└── go.sum

Entry points#

Echo is a library, not a binary. There is no cmd/ directory and no main.go. Consumers import github.com/labstack/echo/v5 and call echo.New() in their own main functions. The framework provides no standalone executable.

Package organization#

  • Internal packages: None. Echo has no internal/ packages — all source is either exported root package or sub-packages visible to consumers.
  • Public packages (pkg/): No pkg/ directory exists. The two sub-packages are at top level:
    • github.com/labstack/echo/v5/middleware — 24 built-in middleware implementations; consumers import this alongside the root package
    • github.com/labstack/echo/v5/echotest — Testing utilities for framework consumers; provides helpers to construct echo.Context and read responses in unit tests without starting a real server
  • Layering: Essentially flat. The root package depends on nothing within the module. middleware/ imports the root package (to access echo.HandlerFunc, echo.Context, etc.) but the root package does not import middleware/. echotest/ similarly imports the root package. No cyclic dependencies, no layered abstraction. The dependency arrow is: echotest → root ← middleware.

Build system#

  • Build tool: make (Makefile in repo root)
  • Key targets:
    • make check (default) — runs lint + vet + race-detected tests across all packages
    • make test — short tests without race detector
    • make race — full tests with -race
    • make benchmark — benchmarks with -benchmem
    • make test_version goversion=X.Y — runs full check inside a golang:X.Y Docker container for version verification
  • CI (GitHub Actions):
    • echo.yml: Matrix across ubuntu/macos/windows and Go 1.25/1.26; coverage uploaded to Codecov; benchstat comparison between base and PR branch on every pull request
    • checks.yml: Lint/vet checks
  • Docker: Available only via make test_version for cross-version testing; no production Docker image (library, not service)

Notable structural decisions#

  1. Root-package-as-framework: Placing the entire core in the root package means import "github.com/labstack/echo/v5" gives you everything. There is no echo/core, echo/http, or similar split. This maximizes approachability but means the root package is large (~20 source files).

  2. middleware/ as a sibling, not a sub-system: Middleware implementations are separate enough to be in their own package (avoiding root package bloat) yet tightly coupled enough that they import the root package directly. The split is practical rather than architectural.

  3. echotest/ as a first-class consumer utility: Shipping a dedicated testing helper package (echotest) acknowledges that testing Echo handlers is a common friction point. This is more deliberate than most frameworks, which leave test setup to the user.

  4. _fixture/ convention: Using a leading underscore (_fixture/) rather than testdata/ at the root is unusual — go test ignores directories starting with _ or ., so this keeps test assets out of the import graph. The middleware/testdata/ sub-directory uses the more conventional testdata/ name.

  5. No internal/: The absence of internal/ is consistent with Echo’s role as a framework — it wants consumers to be able to access and customize internals if needed (e.g., custom routers, custom binders). v5’s removal of the Context interface reduces the need for consumers to access internals for context extension.

  6. Feature files by concern, not layer: Files like ip.go, vhost.go, json.go, renderer.go each handle a single cross-cutting capability directly in the root package rather than being tucked into sub-packages. This keeps the package flat but makes the file list a good table of contents for the framework’s capabilities.