Architecture
Overview
Lemmego follows a layered, plugin-based architecture with clear separation of concerns. Understanding these architectural patterns helps you build maintainable, testable applications.
Application Layers
HTTP Request
|
v
+------------------+
| HTTP Middleware | Recovery, Logging, Method Override (net/http level)
+------------------+
|
v
+------------------+
| Router | Chi-based routing, URL matching
+------------------+
|
v
+------------------+
| App Middleware | CSRF, Auth, custom app middleware (Handler level)
+------------------+
|
v
+------------------+
| Route Handler | Business logic, renders response
+------------------+
|
v
+------------------+
| Service Layer | Business logic services
+------------------+
|
v
+------------------+
| Repository/GPA | Data access (type-safe generic repositories)
+------------------+
|
v
+------------------+
| Database/Storage | PostgreSQL, MySQL, SQLite, Redis, S3, etc.
+------------------+Key Patterns
Builder Pattern (Bootstrap)
The application is configured through a chain of With* methods:
app.Configure().
WithConfig(cfg).
WithRoutes(routes).
WithProviders(providers).
Run()This provides a clear, immutable configuration pipeline where each step adds a concern.
Provider/Plugin Pattern
Services are registered via Provider implementations. Each provider has access to the full App instance during bootstrapping:
type Provider interface {
Provide(a App) error
}This enables:
- Loose coupling between components
- Pluggable backend support (GPA providers)
- Framework extensibility without modifying core code
Middleware Pipeline
Two middleware layers serve different purposes:
- HTTP Middleware wraps the entire router (recovery, logging)
- App Middleware operates at the handler level (CSRF, auth)
Service Container
Simple type-based service registry with generic retrieval:
a.AddService(myService)
svc := app.Get[*MyService](a)Repository Pattern
Data access is abstracted through generic repositories:
type Repository[T any] interface {
Create(ctx, entity *T) error
FindByID(ctx, id) (*T, error)
// ...
}Convention over Configuration
The framework follows sensible defaults:
- Route files in
internal/routes/ - Templates in
templates/with.page.gohtml/.layout.gohtml/.partial.gohtmlnaming - Configuration auto-loaded from
internal/configs/viainit() - Auto-detection of frontend tooling (Templ, Vite, etc.)
Error Handling via ErrMap
Errors are mapped to handlers through a central ErrMap:
type ErrMap = map[error]HandlerThis provides centralized, consistent error responses across the application.
Data Flow
Request Lifecycle
- Client sends HTTP request
- HTTP middleware processes (recovery, logging)
- Router matches the URL to a route
- Route-level middleware runs (CSRF, auth)
- Input decoding middleware parses the request body
- Handler executes business logic
- Handler renders response (JSON, HTML, redirect, etc.)
- Response middleware runs (logging, headers)
Dependency Flow
Config → Providers → Services → Repositories → GPA Providers → Database
↓
Routes + Middleware
↓
HTTP Server