| name | lambda-cosmos |
| description | Complete reference for the Cosmos HTTP framework modules (github.com/studiolambda/cosmos). Covers all four modules: contract (interfaces, request/response helpers), router (generic HTTP routing), problem (RFC 9457 error responses), and framework (handlers, middleware, sessions, cache, crypto, hash, database, events). Use when building, consuming, or reviewing any Cosmos module API.
|
Cosmos
Cosmos is a modular HTTP framework for Go. Four modules, one dependency
direction: contract (zero deps) -> router, problem (standalone) ->
framework (uses all).
go get github.com/studiolambda/cosmos/framework
Quick Start
app := framework.New()
app.Use(middleware.Recover())
app.Use(middleware.Logger(slog.Default()))
app.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) error {
id := request.Param(r, "id")
user, err := findUser(id)
if err != nil {
return ErrNotFound.WithError(err).With("user_id", id)
}
return response.JSON(w, http.StatusOK, user)
})
server := framework.NewServer(":8080", app)
server.ListenAndServe()
Core Types
type Handler = func(w http.ResponseWriter, r *http.Request) error
type Router = router.Router[Handler]
type Middleware = router.Middleware[Handler]
framework.New() returns *Router. Use the full router API (Get, Post,
Group, Use, With, etc.).
Error Handling Pipeline
When a handler returns an error:
context.Canceled / context.DeadlineExceeded -> status 499.
- Error implements
HTTPStatus interface -> custom status code.
- Error implements
http.Handler -> error renders itself (e.g. Problem).
- Otherwise -> wrapped in
problem.NewProblem(err, status) and served.
If the handler returns nil and never writes -> 204 No Content.
Router
r := router.New[framework.Handler]()
r.Get("/users/{id}", handler)
r.Post("/users", handler)
r.Any("/health", handler)
r.Method("CUSTOM", "/rpc", handler)
r.Use(loggingMiddleware)
authed := r.With(authMiddleware)
r.Group("/api/v1", func(api *router.Router[framework.Handler]) {
api.Get("/users", listUsers)
})
Patterns follow http.ServeMux syntax: {id}, {path...}.
Trailing slashes are registered automatically.
For the full router API, load references/router.md.
Problem Details (RFC 9457)
var ErrNotFound = problem.Problem{
Type: "https://api.example.com/errors/not-found",
Title: "Resource Not Found",
Status: http.StatusNotFound,
}
return ErrNotFound.WithError(err).With("user_id", id)
Problem implements error, http.Handler, and json.Marshaler
simultaneously. Content negotiation: application/problem+json,
application/json, text/plain.
For the full problem API, load references/problem.md.
Contract Interfaces
The contract module defines all service interfaces:
| Interface | Purpose |
|---|
Cache | Key-value caching (10 methods) |
Database | SQL queries and transactions |
Encrypter | Symmetric encrypt/decrypt |
Hasher | Password hash/check |
Session | Session read/write/regenerate |
SessionDriver | Session persistence |
Events | Pub/sub messaging |
Hooks | Request lifecycle hooks |
For full interface signatures and helpers, load
references/contract.md.
Request Helpers
id := request.Param(r, "id")
page := request.QueryOr(r, "page", "1")
user, err := request.JSON[User](r)
sess, ok := request.Session(r)
sess.Put("user_id", 123)
sess.Regenerate()
hooks := request.Hooks(r)
hooks.AfterResponse(func(err error) { })
Response Helpers
return response.Status(w, http.StatusNoContent)
return response.JSON(w, http.StatusOK, user)
return response.HTML(w, http.StatusOK, "<h1>hi</h1>")
return response.Redirect(w, http.StatusFound, "/login")
return response.SSE(w, r, eventChan)
Built-in Middleware
middleware.Recover()
middleware.Logger(slog.Default())
middleware.CSRF("https://example.com")
middleware.CORS(middleware.CORSOptions{})
middleware.SecureHeaders()
middleware.RateLimit()
middleware.Provide("db", db)
middleware.HTTP(stdlibMiddleware)
Order matters: Recover first, Logger second, Session third.
Subpackages
Correlation
app.Use(correlation.Middleware())
logger := slog.New(correlation.Handler(h))
id := correlation.From(r)
Sessions
driver := session.NewCacheDriver(memCache)
app.Use(session.Middleware(driver))
Cache
mem := cache.NewMemory(5*time.Minute, 10*time.Minute)
rdb := cache.NewRedis(&cache.RedisOptions{Addr: "localhost:6379"})
val, err := c.Remember(ctx, "key", ttl, computeFn)
Crypto
aes, err := crypto.NewAES(key)
cc, err := crypto.NewChaCha20(key)
ciphertext, err := enc.Encrypt(plaintext)
Hash
hasher := hash.NewArgon2()
hasher := hash.NewBcrypt()
hashed, err := hasher.Hash(password)
ok, err := hasher.Check(password, hashed)
Database
db, err := database.NewSQL("postgres", connString)
err := db.Find(ctx, query, &user, id)
err := db.WithTransaction(ctx, func(tx contract.Database) error {
return tx.Exec(ctx, query, args...)
})
Events
broker := event.NewMemoryBroker()
broker := event.NewRedisBroker(opts)
broker, err := event.NewNATSBroker(url)
unsub, err := broker.Subscribe(ctx, "user.*.created", handler)
err := broker.Publish(ctx, "user.42.created", user)
For complete subpackage APIs, load
references/framework.md.
Gotchas
- Framework handlers return
error, stdlib handlers don't.
nil return with no writes -> 204 No Content.
- Middleware order matters: Recover and Logger first.
- Session middleware required before accessing sessions.
MustSession panics without session middleware (prefer Session).
- Body parsing consumes the request body — one call per request.
response.JSON appends a trailing newline.
- Problem methods return new instances (immutable copy-on-write).
Use() mutates the router, With() creates a new sub-router.
- Database transactions cannot be nested.
- Always use
framework.NewServer() instead of http.ListenAndServe.
When to Load References