go
53 lines · 8 steps
Layering route groups and middleware in Gin
A Gin router builds nested groups so authentication and role checks apply exactly where they're needed.
Explained by
highlit
1package api
2
3func RegisterRoutes(r *gin.Engine, deps *Dependencies) {
4 r.GET("/health", func(c *gin.Context) {
5 c.JSON(http.StatusOK, gin.H{"status": "ok"})
6 })
7
8 v1 := r.Group("/api/v1")
9 v1.POST("/login", deps.AuthHandler.Login)
10 v1.POST("/register", deps.AuthHandler.Register)
11
12 authed := v1.Group("")
13 authed.Use(RequireAuth(deps.TokenService))
14 {
15 authed.GET("/me", deps.UserHandler.CurrentUser)
16
17 projects := authed.Group("/projects")
18 {
19 projects.GET("", deps.ProjectHandler.List)
20 projects.POST("", deps.ProjectHandler.Create)
21 projects.GET("/:id", deps.ProjectHandler.Show)
22 projects.DELETE("/:id", deps.ProjectHandler.Delete)
23 }
24
25 admin := authed.Group("/admin")
26 admin.Use(RequireRole("admin"))
27 {
28 admin.GET("/users", deps.AdminHandler.ListUsers)
29 admin.PATCH("/users/:id/ban", deps.AdminHandler.BanUser)
30 }
31 }
32}
33
34func RequireAuth(tokens *TokenService) gin.HandlerFunc {
35 return func(c *gin.Context) {
36 header := c.GetHeader("Authorization")
37 token := strings.TrimPrefix(header, "Bearer ")
38 if token == header || token == "" {
39 c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing bearer token"})
40 return
41 }
42
43 claims, err := tokens.Verify(c.Request.Context(), token)
44 if err != nil {
45 c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid or expired token"})
46 return
47 }
48
49 c.Set("userID", claims.Subject)
50 c.Set("role", claims.Role)
51 c.Next()
52 }
53}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Route groups let you attach middleware once and have it cover every nested endpoint.
- 2Middleware that calls Abort short-circuits the chain so protected handlers never run.
- 3Stashing verified claims in the request context passes auth data cleanly to downstream handlers.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
go
package middleware import ( "net/http"
Per-plan export limits in Gin middleware
middleware
rate-limiting
authorization
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/layering-route-groups-and-middleware-in-gin-explained-go-2b21/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.