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
func UploadDocument(c *gin.Context) { fileHeader, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "file is required"})
Handling multipart uploads in Gin
multipart-upload
validation
error-handling
Intermediate
9 steps
rust
use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use axum::body::Body;
A maintenance-mode gate in Axum
middleware
shared-state
atomics
Intermediate
7 steps
go
package handlers type WebhookEvent struct { ID string `json:"id"`
Verifying and processing webhooks in Gin
webhooks
hmac
idempotency
Intermediate
9 steps
typescript
import { Body, Controller, Ip, Post, UnauthorizedException } from '@nestjs/common'; import { Throttle, ThrottlerGuard } from '@nestjs/throttler'; import { UseGuards } from '@nestjs/common'; import { AuthService } from './auth.service';
Rate-limiting an auth flow in NestJS
rate-limiting
authentication
guards
Intermediate
8 steps
javascript
const express = require('express'); const app = express();
Enforcing HTTPS with Express middleware
middleware
https
security
Intermediate
6 steps
go
package logparse import ( "bufio"
Splitting multi-line logs with a Scanner
parsing
streaming
bufio
Intermediate
9 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.