---
title: "GN005 – Group Routes and Apply Middleware at Group Level"
impact: medium
impactDescription: "Registering middleware per-route duplicates code and makes it easy to miss a route; group-level middleware is exhaustive by design."
tags: [go, gin, routing, middleware]
---

# GN005 – Group Routes and Apply Middleware at Group Level

## Rule

Use `router.Group()` to organise routes by prefix and protection level. Apply authentication, logging, and rate-limiting middleware to the group — not individual routes.

## Why

Per-route middleware registration (`r.GET("/users", authMiddleware, handler)`) requires the developer to remember to add middleware to every new route. A missed route is a security hole. Group-level middleware is applied to all current and future routes in the group automatically.

## Wrong

```go
r := gin.Default()

// ❌ Auth repeated on every route — easy to miss
r.GET("/users",         authMiddleware, userHandler.List)
r.POST("/users",        authMiddleware, userHandler.Create)
r.PUT("/users/:id",     authMiddleware, userHandler.Update)
r.DELETE("/users/:id",  authMiddleware, userHandler.Delete)   // ❌ forgot auth on DELETE

// ❌ Mixed public and protected routes without groups — hard to audit
r.GET("/products", productHandler.List)
r.POST("/products", authMiddleware, adminMiddleware, productHandler.Create)
```

## Correct

```go
r := gin.New()
r.Use(gin.Recovery(), requestLogger())   // global middleware

// Public routes — no auth
public := r.Group("/api/v1")
{
    public.POST("/auth/login", authHandler.Login)
    public.POST("/auth/register", authHandler.Register)
    public.GET("/products", productHandler.List)
}

// Authenticated routes
authenticated := r.Group("/api/v1")
authenticated.Use(authMiddleware())
{
    authenticated.GET("/users/me", userHandler.Profile)
    authenticated.PUT("/users/me", userHandler.Update)
    authenticated.GET("/orders", orderHandler.List)
    authenticated.POST("/orders", orderHandler.Create)
}

// Admin-only routes
admin := r.Group("/api/v1/admin")
admin.Use(authMiddleware(), adminMiddleware())
{
    admin.GET("/users", adminUserHandler.List)
    admin.DELETE("/users/:id", adminUserHandler.Delete)
}
```

## Notes

- Use curly braces `{}` inside `Group()` to make the scope visually clear (they're just Go blocks, not syntactically required).
- Apply rate limiting at the group level for public endpoints to prevent brute force on `/auth/login`.
- Version your API with a group: `r.Group("/api/v1")` makes future versioning (`/api/v2`) straightforward.
