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.sumEntry 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 packagegithub.com/labstack/echo/v5/echotest— Testing utilities for framework consumers; provides helpers to constructecho.Contextand 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 accessecho.HandlerFunc,echo.Context, etc.) but the root package does not importmiddleware/.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 packagesmake test— short tests without race detectormake race— full tests with-racemake benchmark— benchmarks with-benchmemmake test_version goversion=X.Y— runs full check inside agolang:X.YDocker 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 requestchecks.yml: Lint/vet checks
- Docker: Available only via
make test_versionfor cross-version testing; no production Docker image (library, not service)
Notable structural decisions#
Root-package-as-framework: Placing the entire core in the root package means
import "github.com/labstack/echo/v5"gives you everything. There is noecho/core,echo/http, or similar split. This maximizes approachability but means the root package is large (~20 source files).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.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._fixture/convention: Using a leading underscore (_fixture/) rather thantestdata/at the root is unusual —go testignores directories starting with_or., so this keeps test assets out of the import graph. Themiddleware/testdata/sub-directory uses the more conventionaltestdata/name.No
internal/: The absence ofinternal/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 theContextinterface reduces the need for consumers to access internals for context extension.Feature files by concern, not layer: Files like
ip.go,vhost.go,json.go,renderer.goeach 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.