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
php
<?php function buildTree(array $items, ?int $parentId = null): array {
Building a tree from a flat list in PHP
recursion
tree
grouping
Intermediate
6 steps
go
package payments import ( "context"
Fetching a charge over HTTP in Go
context-timeout
http-client
json-decoding
Intermediate
6 steps
javascript
const express = require('express'); const compression = require('compression'); const zlib = require('zlib');
Tuning Express response compression
compression
middleware
http
Intermediate
9 steps
go
package scheduler import ( "context"
A context-aware heartbeat ticker in Go
concurrency
channels
context
Intermediate
5 steps
python
from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel, ConfigDict, EmailStr from sqlalchemy.orm import Session
Building a users router in FastAPI
routing
serialization
dependency-injection
Intermediate
7 steps
javascript
const logger = require('./logger'); class AppError extends Error { constructor(message, statusCode = 500, details = null) {
Centralized error handling in Express
error-handling
middleware
custom-errors
Intermediate
8 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.