Documentation Index Fetch the complete documentation index at: https://mintlify.com/platforma-dev/platforma/llms.txt
Use this file to discover all available pages before exploring further.
Platforma’s HTTP routing is built on Go’s http.ServeMux with enhanced support for middleware, route grouping, and domain organization.
HTTPServer
The HTTPServer type from httpserver/httpserver.go:18 is a service that runs an HTTP server with graceful shutdown:
type HTTPServer struct {
* HandlerGroup
port string
shutdownTimeout time . Duration
}
Creating an HTTP Server
From httpserver/httpserver.go:25:
import (
" time "
" github.com/platforma-dev/platforma/httpserver "
)
// Create server on port 8080 with 5 second shutdown timeout
api := httpserver . New ( "8080" , 5 * time . Second )
The shutdown timeout controls how long the server waits for in-flight requests to complete during graceful shutdown.
Registering with Application
HTTP servers implement the Runner interface and can be registered as services:
app := application . New ()
app . RegisterService ( "api" , api )
The server starts when you run ./myapp run and shuts down gracefully on interrupt signals.
Route Registration
Handle() - Register Handlers
Use the Handle() method from httpserver/handlergroup.go:31 with Go 1.22+ routing syntax:
api . Handle ( "GET /users" , getUsersHandler )
api . Handle ( "POST /users" , createUserHandler )
api . Handle ( "GET /users/{id}" , getUserHandler )
api . Handle ( "PUT /users/{id}" , updateUserHandler )
api . Handle ( "DELETE /users/{id}" , deleteUserHandler )
The pattern "METHOD /path" syntax requires Go 1.22 or later . This gives you method-specific routing without external libraries.
HandleFunc() - Register Functions
From httpserver/handlergroup.go:36, for simple function handlers:
api . HandleFunc ( "/ping" , func ( w http . ResponseWriter , r * http . Request ) {
w . Write ([] byte ( "pong" ))
})
Path Parameters
Extract path parameters using Go’s standard library:
api . HandleFunc ( "GET /users/{id}" , func ( w http . ResponseWriter , r * http . Request ) {
userID := r . PathValue ( "id" )
// Use userID...
w . Write ([] byte ( "User ID: " + userID ))
})
HandlerGroup
The HandlerGroup type from httpserver/handlergroup.go:8 organizes routes that share common middleware:
type HandlerGroup struct {
mux * http . ServeMux
middlewares [] Middleware
}
Creating Handler Groups
From httpserver/handlergroup.go:14:
// Create a new handler group
adminAPI := httpserver . NewHandlerGroup ()
// Add routes
adminAPI . Handle ( "GET /stats" , getStatsHandler )
adminAPI . Handle ( "POST /config" , updateConfigHandler )
// Apply middleware to all routes in this group
adminAPI . Use ( adminAuthMiddleware )
adminAPI . Use ( auditLogMiddleware )
Mounting Handler Groups
Use HandleGroup() from httpserver/handlergroup.go:41 to mount groups at a path prefix:
// Mount admin API under /admin
api . HandleGroup ( "/admin" , adminAPI )
// Now accessible as:
// GET /admin/stats
// POST /admin/config
HandleGroup() automatically strips the prefix before routing. Routes in the group should be defined without the mount prefix.
Complete Example
From demo-app/cmd/api/main.go:13, showing server setup with groups:
package main
import (
" context "
" net/http "
" time "
" github.com/platforma-dev/platforma/application "
" github.com/platforma-dev/platforma/httpserver "
" github.com/platforma-dev/platforma/log "
)
func main () {
ctx := context . Background ()
app := application . New ()
// Create HTTP server
api := httpserver . New ( "8080" , 3 * time . Second )
// Add top-level endpoints
api . HandleFunc ( "/ping" , func ( w http . ResponseWriter , r * http . Request ) {
w . Write ([] byte ( "pong" ))
})
api . HandleFunc ( "/long" , func ( w http . ResponseWriter , r * http . Request ) {
time . Sleep ( 10 * time . Second )
w . Write ([] byte ( "pong" ))
})
// Add middleware to entire server
api . Use ( log . NewTraceIDMiddleware ( nil , "" ))
// Create handler group
subApiGroup := httpserver . NewHandlerGroup ()
// Add endpoint to group
subApiGroup . HandleFunc ( "/clock" , func ( w http . ResponseWriter , r * http . Request ) {
w . Write ([] byte ( time . Now (). String ()))
})
// Add middleware to group only
subApiGroup . UseFunc ( func ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
log . InfoContext ( r . Context (), "incoming request" , "addr" , r . RemoteAddr )
h . ServeHTTP ( w , r )
})
})
// Mount group at /subApi
api . HandleGroup ( "/subApi" , subApiGroup )
// Register server
app . RegisterService ( "api" , api )
// Run application
if err := app . Run ( ctx ); err != nil {
log . ErrorContext ( ctx , "app finished with error" , "error" , err )
}
}
This creates:
GET /ping - Top-level endpoint with trace ID middleware
GET /long - Top-level endpoint (tests graceful shutdown)
GET /subApi/clock - Group endpoint with both trace ID and logging middleware
Mounting Domain APIs
Domains expose their HTTP API via a HandlerGroup:
// From auth/domain.go:30
authAPI := httpserver . NewHandlerGroup ()
authAPI . Handle ( "POST /register" , registerHandler )
authAPI . Handle ( "POST /login" , loginHandler )
authAPI . Handle ( "POST /logout" , logoutHandler )
authAPI . Handle ( "GET /me" , getUserHandler )
authAPI . Handle ( "POST /change-password" , changePasswordHandler )
authAPI . Handle ( "DELETE /me" , deleteHandler )
Mount it in your main application:
api := httpserver . New ( "8080" , 5 * time . Second )
api . HandleGroup ( "/auth" , authDomain . HandleGroup )
app . RegisterService ( "api" , api )
This pattern keeps domain routing logic inside the domain while giving you flexibility in how you expose it.
Middleware Application
Server-Level Middleware
Applies to all routes on the server:
api := httpserver . New ( "8080" , 5 * time . Second )
api . Use ( log . NewTraceIDMiddleware ( nil , "" ))
api . Use ( corsMiddleware )
Group-Level Middleware
Applies only to routes in the group from httpserver/handlergroup.go:19:
protectedAPI := httpserver . NewHandlerGroup ()
protectedAPI . Use ( authMiddleware ) // Only affects this group
protectedAPI . Handle ( "GET /profile" , getProfileHandler )
api . HandleGroup ( "/user" , protectedAPI )
UseFunc() - Inline Middleware
From httpserver/handlergroup.go:24, add middleware as a function:
group . UseFunc ( func ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
// Before request
log . InfoContext ( r . Context (), "request started" )
h . ServeHTTP ( w , r )
// After request
log . InfoContext ( r . Context (), "request completed" )
})
})
Middleware Ordering
Middleware is applied in reverse order from httpserver/middleware.go:26:
// Applied as: traceID -> logging -> auth -> handler
api . Use ( traceIDMiddleware ) // Outermost
api . Use ( loggingMiddleware )
api . Use ( authMiddleware ) // Innermost
The first registered middleware is the outermost wrapper. Request flow: trace ID → logging → auth → handler. Response flow is reversed.
Route Organization Patterns
Pattern 1: Versioned APIs
v1 := httpserver . NewHandlerGroup ()
v1 . Handle ( "GET /users" , v1GetUsersHandler )
v1 . Handle ( "POST /users" , v1CreateUserHandler )
v2 := httpserver . NewHandlerGroup ()
v2 . Handle ( "GET /users" , v2GetUsersHandler )
v2 . Handle ( "POST /users" , v2CreateUserHandler )
api . HandleGroup ( "/v1" , v1 )
api . HandleGroup ( "/v2" , v2 )
Pattern 2: Public vs Protected
publicAPI := httpserver . NewHandlerGroup ()
publicAPI . Handle ( "GET /health" , healthHandler )
publicAPI . Handle ( "POST /webhooks/stripe" , stripeWebhookHandler )
protectedAPI := httpserver . NewHandlerGroup ()
protectedAPI . Use ( authMiddleware )
protectedAPI . Handle ( "GET /profile" , profileHandler )
protectedAPI . Handle ( "POST /upload" , uploadHandler )
api . HandleGroup ( "/public" , publicAPI )
api . HandleGroup ( "/api" , protectedAPI )
Pattern 3: Domain-Based
userDomain := user . New ( db )
orderDomain := order . New ( db )
productDomain := product . New ( db )
api := httpserver . New ( "8080" , 5 * time . Second )
api . HandleGroup ( "/users" , userDomain . HandleGroup )
api . HandleGroup ( "/orders" , orderDomain . HandleGroup )
api . HandleGroup ( "/products" , productDomain . HandleGroup )
Graceful Shutdown
From httpserver/httpserver.go:30, the server handles shutdown automatically:
func ( s * HTTPServer ) Run ( ctx context . Context ) error {
server := & http . Server {
Addr : ":" + s . port ,
Handler : wrapHandlerInMiddleware ( s . mux , s . middlewares ),
ReadHeaderTimeout : 1 * time . Second ,
}
go func () {
log . InfoContext ( ctx , "starting http server" , "address" , server . Addr )
if err := server . ListenAndServe (); ! errors . Is ( err , http . ErrServerClosed ) {
log . ErrorContext ( ctx , "HTTP server error" , "error" , err )
}
}()
<- ctx . Done () // Wait for shutdown signal
shutdownCtx , cancel := context . WithTimeout (
context . Background (),
s . shutdownTimeout ,
)
defer cancel ()
if err := server . Shutdown ( shutdownCtx ); err != nil {
return fmt . Errorf ( "failed to gracefully shutdown: %w " , err )
}
log . InfoContext ( ctx , "graceful shutdown completed" )
return nil
}
In-flight requests have shutdownTimeout duration to complete before the server force-closes connections.
Health Checks
The HTTPServer implements Healthchecker from httpserver/httpserver.go:62:
func ( s * HTTPServer ) Healthcheck ( _ context . Context ) any {
return map [ string ] any {
"port" : s . port ,
}
}
This is automatically included when you register the server:
app . RegisterService ( "api" , api )
health := app . Health ( ctx )
// health.Services["api"] contains port information
Advanced Routing
Wildcard Paths
// Match anything under /static/
api . Handle ( "/static/" , http . StripPrefix ( "/static/" ,
http . FileServer ( http . Dir ( "./static" ))))
Method-Specific Routing
// Same path, different methods
api . Handle ( "GET /items/{id}" , getItemHandler )
api . Handle ( "PUT /items/{id}" , updateItemHandler )
api . Handle ( "DELETE /items/{id}" , deleteItemHandler )
Multiple Path Params
api . HandleFunc ( "GET /users/{userID}/posts/{postID}" ,
func ( w http . ResponseWriter , r * http . Request ) {
userID := r . PathValue ( "userID" )
postID := r . PathValue ( "postID" )
// Handle request...
},
)
Next Steps
Middleware Learn how to create and apply HTTP middleware
Domains Organize routes by business domain
Application Lifecycle Register and run HTTP servers