Route Patterns
5 minute read
The Rivaas Router supports multiple route patterns, from simple static routes to dynamic parameters and wildcards.
Static Routes
Static routes match exact path strings and have the best performance:
r.GET("/", homeHandler)
r.GET("/about", aboutHandler)
r.GET("/api/health", healthHandler)
r.GET("/admin/dashboard", dashboardHandler)
Characteristics:
- Exact string match required.
- Fastest route type. Sub-microsecond lookups.
- Uses hash table lookups with bloom filters.
- No pattern matching overhead.
curl http://localhost:8080/about
# Matches: /about
# Does NOT match: /about/, /about/us
Parameter Routes
Routes can capture dynamic segments using the :param syntax:
Single Parameter
// Capture user ID
r.GET("/users/:id", func(c *router.Context) {
userID := c.Param("id")
c.JSON(200, map[string]string{"user_id": userID})
})
Matches:
/users/123→id="123"/users/abc→id="abc"/users/uuid-here→id="uuid-here"
Does NOT match:
/users- missing parameter/users/- empty parameter/users/123/posts- too many segments
Multiple Parameters
r.GET("/users/:id/posts/:post_id", func(c *router.Context) {
userID := c.Param("id")
postID := c.Param("post_id")
c.JSON(200, map[string]string{
"user_id": userID,
"post_id": postID,
})
})
Matches:
/users/123/posts/456→id="123",post_id="456"/users/alice/posts/hello-world→id="alice",post_id="hello-world"
Mixed Static and Parameter Segments
r.GET("/api/v1/users/:id/profile", userProfileHandler)
r.GET("/organizations/:org/teams/:team/members", membersHandler)
Example:
/api/v1/users/123/profile→id="123"/organizations/acme/teams/engineering/members→org="acme",team="engineering"
Wildcard Routes
Wildcard routes capture the rest of the path using *param:
// Serve files from any path under /files/
r.GET("/files/*filepath", func(c *router.Context) {
filepath := c.Param("filepath")
c.JSON(200, map[string]string{"filepath": filepath})
})
Matches:
/files/images/logo.png→filepath="images/logo.png"/files/docs/api/v1/index.html→filepath="docs/api/v1/index.html"/files/a/b/c/d/e/f.txt→filepath="a/b/c/d/e/f.txt"
Important:
- Wildcards match everything after their position, including slashes
- Only one wildcard per route
- Wildcard must be the last segment
// ✅ Valid
r.GET("/static/*filepath", handler)
r.GET("/api/v1/files/*path", handler)
// ❌ Invalid - wildcard must be last
r.GET("/files/*path/metadata", handler) // Won't work
// ❌ Invalid - only one wildcard
r.GET("/files/*path1/other/*path2", handler) // Won't work
Route Matching Priority
When multiple routes could match a request, the router follows this priority order:
1. Static Routes (Highest Priority)
Exact matches are evaluated first:
r.GET("/users/me", currentUserHandler) // Static
r.GET("/users/:id", getUserHandler) // Parameter
Request: GET /users/me
- ✅ Matches
/users/me(static) - Selected - ❌ Could match
/users/:idbut static wins
2. Parameter Routes
After static routes, parameter routes are checked:
r.GET("/posts/:id", getPostHandler)
r.GET("/posts/*filepath", catchAllHandler)
Request: GET /posts/123
- ❌ No static match
- ✅ Matches
/posts/:id- Selected - ❌ Could match
/posts/*filepathbut parameter wins
3. Wildcard Routes (Lowest Priority)
Wildcards are the catch-all:
r.GET("/files/*filepath", serveFileHandler)
Request: GET /files/images/logo.png
- ❌ No static match
- ❌ No parameter match
- ✅ Matches
/files/*filepath- Selected
Priority Examples
func main() {
r := router.MustNew()
// Priority 1: Static
r.GET("/users/me", func(c *router.Context) {
c.String(200, "Current user")
})
// Priority 2: Parameter
r.GET("/users/:id", func(c *router.Context) {
c.String(200, "User: "+c.Param("id"))
})
// Priority 3: Wildcard
r.GET("/users/*path", func(c *router.Context) {
c.String(200, "Catch-all: "+c.Param("path"))
})
http.ListenAndServe(":8080", r)
}
Tests:
curl http://localhost:8080/users/me
# Output: "Current user" (static route)
curl http://localhost:8080/users/123
# Output: "User: 123" (parameter route)
curl http://localhost:8080/users/123/posts
# Output: "Catch-all: 123/posts" (wildcard route)
Parameter Design Best Practices
The router optimizes parameter storage for routes with ≤8 parameters using fast array-based storage. Routes with >8 parameters fall back to map-based storage.
Optimization Threshold
- ≤8 parameters: Array-based storage (fastest, zero allocations)
- >8 parameters: Map-based storage (one allocation per request)
Best Practices
1. Keep Parameter Count ≤8
// ✅ GOOD: 2 parameters
r.GET("/users/:id/posts/:post_id", handler)
// ✅ GOOD: 4 parameters
r.GET("/api/:version/users/:id/posts/:post_id/comments/:comment_id", handler)
// ⚠️ WARNING: 9 parameters (requires map allocation)
r.GET("/a/:p1/b/:p2/c/:p3/d/:p4/e/:p5/f/:p6/g/:p7/h/:p8/i/:p9", handler)
2. Use Query Parameters for Additional Data
Instead of many path parameters, use query parameters:
// ❌ BAD: Too many path parameters
r.GET("/search/:category/:subcategory/:type/:status/:sort/:order/:page/:limit", handler)
// ✅ GOOD: Use query parameters for filters
r.GET("/search/:category", handler)
// Query: ?subcategory=electronics&type=product&status=active&sort=price&order=asc&page=1&limit=20
3. Use Request Body for Complex Data
For complex operations, use the request body:
// ❌ BAD: Many path parameters
r.POST("/api/:version/:resource/:action/:target/:scope/:context/:mode/:format", handler)
// ✅ GOOD: Use request body
r.POST("/api/v1/operations", handler)
// Body: {"resource": "...", "action": "...", "target": "...", ...}
4. Restructure Routes
Flatten hierarchies or consolidate parameters:
// ❌ BAD: 10 parameters in path
r.GET("/:a/:b/:c/:d/:e/:f/:g/:h/:i/:j", handler)
// ✅ GOOD: Flatten hierarchy or use query parameters
r.GET("/items", handler) // Use query: ?a=...&b=...&c=...
Runtime Warnings
The router automatically logs a warning when registering routes with >8 parameters:
WARN: route has more than 8 parameters, using map storage instead of fast array
method=GET
path=/api/:v1/:r1/:r2/:r3/:r4/:r5/:r6/:r7/:r8/:r9
param_count=9
recommendation=consider restructuring route to use query parameters or request body for additional data
When >8 Parameters Are Acceptable
- Low-frequency endpoints (<100 req/s)
- Legacy API compatibility requirements
- Complex hierarchical resource structures that can’t be flattened
Performance Impact
- ≤8 params: ~119ns/op, 0 allocations
- >8 params: ~119ns/op, 1 allocation (~24 bytes)
- Real-world impact: Negligible for most applications (<1% overhead)
Route Constraints
Add validation to parameters with constraints:
// Integer constraint
r.GET("/users/:id", getUserHandler).WhereInt("id")
// UUID constraint
r.GET("/entities/:uuid", getEntityHandler).WhereUUID("uuid")
// Custom regex
r.GET("/files/:filename", getFileHandler).WhereRegex("filename", `[a-zA-Z0-9.-]+`)
// Enum constraint
r.GET("/status/:state", getStatusHandler).WhereEnum("state", "active", "pending", "deleted")
Learn more: See the Route Constraints reference for all available constraints.
Common Patterns
RESTful Resources
// Standard REST endpoints
r.GET("/users", listUsers) // List all
r.POST("/users", createUser) // Create new
r.GET("/users/:id", getUser) // Get one
r.PUT("/users/:id", updateUser) // Update (full)
r.PATCH("/users/:id", patchUser) // Update (partial)
r.DELETE("/users/:id", deleteUser) // Delete
Nested Resources
// Comments belong to posts
r.GET("/posts/:post_id/comments", listComments)
r.POST("/posts/:post_id/comments", createComment)
r.GET("/posts/:post_id/comments/:id", getComment)
r.PUT("/posts/:post_id/comments/:id", updateComment)
r.DELETE("/posts/:post_id/comments/:id", deleteComment)
Action Routes
// Actions on resources
r.POST("/users/:id/activate", activateUser)
r.POST("/users/:id/deactivate", deactivateUser)
r.POST("/posts/:id/publish", publishPost)
r.POST("/orders/:id/cancel", cancelOrder)
File Serving
// Static file serving
r.GET("/assets/*filepath", serveAssets)
r.GET("/downloads/*filepath", serveDownloads)
func serveAssets(c *router.Context) {
filepath := c.Param("filepath")
c.ServeFile("./public/" + filepath)
}
Anti-Patterns
Avoid Ambiguous Routes
// ❌ BAD: Ambiguous - which route matches /users/delete?
r.GET("/users/:id", getUser)
r.DELETE("/users/:action", performAction)
// ✅ GOOD: Clear distinction
r.GET("/users/:id", getUser)
r.POST("/users/:id/actions/:action", performAction)
Avoid Overly Deep Hierarchies
// ❌ BAD: Too deep
r.GET("/api/v1/organizations/:org/teams/:team/projects/:proj/tasks/:task/comments/:id", handler)
// ✅ GOOD: Flatten or use query parameters
r.GET("/api/v1/comments/:id", handler) // Include org/team/proj/task in query or auth context
Next Steps
- Route Groups: Learn to organize routes with groups and prefixes
- Middleware: Add middleware for authentication, logging, etc.
- Constraints: See all route constraints available
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.