routegroup

repository·master·Indexed 42 days ago

https://github.com/go-pkgz/routegroup

A lightweight Go wrapper for http.ServeMux that enables efficient route grouping and middleware application. It allows developers to organize routes into logical groups, apply scoped middleware using Use and With, and manage nested routing via Mount. Requires Go 1.23 or higher to utilize enhanced http.ServeMux routing behavior and http.Request.Pattern.

Tokens
4.4K
Snippets
16
Records
19
Agent score
4%

What's inside routegroup

  1. Overview of routegroup

    master
    routegroup is a lightweight Go package designed to provide efficient route grouping and middleware integration for the standard http.ServeMux. It acts as a wrapper that allows you to organize routes into logical groups and apply middleware to those groups or individual routes seamlessly.
  2. Understand middleware ordering and panics

    master

    Middleware registration follows strict rules to ensure predictable execution:

    1. Order of Operations: You must call Use(...) before registering any routes (via Handle, HandleFunc, etc.) on that specific bundle.
    2. Panic Condition: Calling Use on a bundle after any handler has already been registered on that bundle will cause a panic.
    3. Global vs Scoped:
      • Root bundle middlewares (router.Use) apply to all requests.
      • Group middlewares (group.Use) apply to that group and its descendants.
      • With(...) is the preferred way to add scoped middleware to specific routes without affecting the parent group.
    4. Concurrency: Route registration should be performed during application initialization in a single goroutine; do not register routes concurrently during runtime.
  3. Requirements for routegroup

    master

    To use routegroup, your project must meet the following requirement:

    • Go 1.23 or higher: The library relies on the enhanced http.ServeMux routing behavior introduced in Go 1.22/1.23 and specifically uses http.Request.Pattern to make route patterns available to global middlewares.
  4. Create and manage route groups

    master

    Use routegroup.New(mux) to initialize a new route group wrapping an http.ServeMux. You can add routes using Handle or HandleFunc and apply middleware to the entire group using Use.

    To create nested groups under a specific path prefix, use the Mount method. To create a new group without a path prefix (useful for inheriting middleware from a parent), use the Group method.

    // Initialize
    mux := http.NewServeMux()
    group := routegroup.New(mux)
    
    // Add middleware to the group
    group.Use(loggingMiddleware, corsMiddleware)
    
    // Add routes
    group.Handle("/hello", helloHandler)
    
    // Create a nested group under /api
    apiGroup := group.Mount("/api")
    apiGroup.Handle("/v1", apiV1Handler)
  5. How middleware and route groups work together

    master

    The routegroup package uses a hierarchical model to manage middleware application across different URL segments:

    1. Root Bundle: Created via New. Middlewares added here are 'Global Middlewares'. They wrap the entire http.ServeMux and execute for every request. They can see the route pattern via r.Pattern but cannot access path parameters (like r.PathValue("id")) because they run before the mux performs parameter parsing.
    2. Mounted Groups: Created via Mount or Group. These inherit the middleware stack of their parent. Middlewares added to a group execute after the mux has routed the request, meaning they have full access to path parameters.
    3. Middleware Execution: When a request arrives, the root middlewares wrap the entire mux. When the mux finds a match, the specific middlewares for that route/group are then applied to the final handler.
    // 1. Root bundle with global logging
    root := routegroup.New(mux)
    root.Use(loggingMiddleware)
    
    // 2. Mounted group with auth
    // authMiddleware can access path parameters
    api := root.Mount("/api")
    api.Use(authMiddleware)
    
    // 3. Route registration
    api.HandleFunc("/user/{id}", userHandler) 
    // Execution flow: loggingMiddleware -> authMiddleware -> userHandler
  6. Implement complex routing with routegroup

    master

    The following example demonstrates how to use routegroup to organize a real-world web service. It showcases:

    1. Mounting a base router: Using routegroup.Mount(http.NewServeMux(), s.rootURL) to create a bundle with a base path.
    2. Global Middleware: Applying middleware to the entire router using .Use().
    3. Route Grouping: Using .Group().Route(func(b *routegroup.Bundle) { ... }) to create logical segments (like /admin) with their own middleware stack.
    4. Middleware Scoping: Using .With(middleware) to apply specific middleware to a subset of routes within a group without affecting the rest of the group.
    5. Nested Routing: Using .Mount(path).Route(...) to create deeply nested route hierarchies.
    6. Static File Serving: Integrating standard http.FileServer with routegroup patterns.
    // Routes returns http.Handler that handles all the routes for the Service.
    func (s *Service) Routes() http.Handler {
    	router := routegroup.Mount(http.NewServeMux(), s.rootURL) 
    
    	// add common middlewares
    	router.Use(rest.Maybe(handlers.CompressHandler, func(*http.Request) bool { return !s.skipGZ }))
    	router.Use(rest.Throttle(s.limitActiveReqs))
    	router.Use(s.middleware.securityHeaders(s.skipSecurityHeaders))
    
    	// prepare csrf middleware
    	csrfMiddleware := s.middleware.csrf(s.skipCSRFCheck)
    
    	// add open routes
    	router.HandleFunc("GET /login", s.loginPageHandler)
    	router.HandleFunc("POST /login", s.loginCheckHandler)
    	router.HandleFunc("GET /logout", s.logoutHandler)
    
    	// add routes with auth middleware
    	router.Group().Route(func(auth *routegroup.Bundle) {
    		auth.Use(s.middleware.Auth())
    		auth.HandleFunc("GET /update", s.pwdUpdateHandler)
    		auth.With(csrfMiddleware).HandleFunc("PUT /update", s.pwdUpdateHandler)
    	})
    
    	// add admin routes
    	router.Mount("/admin").Route(func(admin *routegroup.Bundle) {
    		admin.Use(s.middleware.Auth("admin"))
    		admin.Use(s.middleware.AdminOnly)
    		admin.HandleFunc("GET /", s.admin.renderHandler)
    		admin.With(csrfMiddleware).Route(func(csrf *routegroup.Bundle) {
    			csrf.HandleFunc("DELETE /sessions", s.admin.deleteSessionsHandler)
    			csrf.HandleFunc("POST /user", s.admin.addUserHandler)
    			csrf.HandleFunc("DELETE /user", s.admin.deleteUserHandler)
    		})
    	})
    
    	router.HandleFunc("GET /static/*", s.fileServerHandlerFunc())
    	return router
    }
  7. Handle root paths without trailing slashes with `HandleRoot`

    master

    When using Mount, a request to the group's base path (e.g., /api) might not match a route defined as /api/ due to how http.ServeMux handles trailing slashes (which often triggers a redirect).

    To handle the exact path without a trailing slash and avoid the redirect round-trip, use HandleRoot or HandleRootFunc. This is particularly useful for non-GET requests.

    apiGroup := router.Mount("/api")
    
    // This handles requests to exactly "/api" without a redirect
    apiGroup.HandleRoot("GET", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("API Documentation"))
    }))
    
    // You can also specify different methods for the root path
    apiGroup.HandleRoot("POST", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Create API Resource"))
    }))
  8. Apply middleware to specific routes with `With`

    master

    If you want to apply middleware to a specific route without affecting the existing middleware stack of the current group, use the With method. With returns a new bundle (group) containing the additional middlewares, allowing you to register routes on it immediately.

    // apiGroup's existing middleware is preserved,
    // but this specific route also gets corsMiddleware and apiMiddleware
    apiGroup.With(corsMiddleware, apiMiddleware).Handle("GET /hello", helloHandler)
  9. Wrap a single handler with middleware using `Wrap`

    master

    If you do not need a full route group and simply want to apply one or more middlewares to a single http.Handler, use the routegroup.Wrap function. This is useful when working directly with a standard http.ServeMux.

    mux := http.NewServeMux()
    // Wrap the handler directly in the mux registration
    mux.HandleFunc("/hello", routegroup.Wrap(helloHandler, loggingMiddleware, corsMiddleware))
  10. Configure a custom `NotFoundHandler`

    master

    You can set a custom handler to run when a request matches no routes in the underlying http.ServeMux. This handler only executes on 404 errors and does not interfere with 405 Method Not Allowed responses or path-cleanup redirects. Global middlewares applied to the root bundle will still run for this handler.

    group.NotFoundHandler(func(w http.ResponseWriter, _ *http.Request) {
        http.Error(w, "404 page not found!", http.StatusNotFound)
    })
  11. Serve static files with `HandleFiles`

    master

    The HandleFiles helper is a wrapper around http.FileServer that allows you to serve static files from a specific directory at a given path prefix.

    // Serves files from "assets/static" at the "/static/" URL path
    router.HandleFiles("/static/", http.Dir("assets/static"))