httpgorouter

A good way to build a subrouter with net/http


I am building an API server that handles multiple paths with net/http. I'd like to build it as a subrouter, but I don't know a good way to do it.

For now, I'm thinking as follows.

package main

import (
    "database/sql"
    "log"
    "net/http"
)

type Config struct {
    db *sql.DB
}

type UserRouter struct {
    mux *http.ServeMux
    cfg *Config
}

func (u *UserRouter) Create(w http.ResponseWriter, r *http.Request) {
    // process handler logic
    w.Write([]byte("Create"))
}

func (u *UserRouter) Delete(w http.ResponseWriter, r *http.Request) {
    // process handler logic
    w.Write([]byte("Delete"))
}

// NewUserRouter is subrouter.
func NewUserRouter(cfg *Config) *UserRouter {
    mux := http.NewServeMux()
    u := &UserRouter{
        mux: mux,
        cfg: cfg,
    }
    mux.HandleFunc("/user/create", u.Create)
    mux.HandleFunc("/user/delete", u.Delete)
    return u
}

func (u *UserRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    u.mux.ServeHTTP(w, r)
}

func main() {
    mux := http.NewServeMux()
    cfg := &Config{nil /* Describes settings such as PostgreSQL. */}
    mux.Handle("/user/", NewUserRouter(cfg))
    if err := http.ListenAndServe(":8080", mux); err != nil {
        log.Fatal(err)
    }
}

The above implementation seems a bit redundant. But, the HTTP server will not work as intended if the subrouter path is set as follows. This is because the mux passed to ListenAndServe is different from the subrouter's mux, I think.

// NewUserRouter is subrouter.
func NewUserRouter(cfg *Config) *UserRouter {
    mux := http.NewServeMux()
    u := &UserRouter{
        mux: mux,
        cfg: cfg,
    }
-   mux.HandleFunc("/user/create", u.Create)
-   mux.HandleFunc("/user/delete", u.Delete)
+   mux.HandleFunc("/create", u.Create)
+   mux.HandleFunc("/delete", u.Delete)
    return u
}

Is it difficult to build a simple subrouter with net/http and do I need to use a WAF like go-chi/chi or gorilla/mux?


Solution

  • If your goal is to separate the registration of user handlers from other handlers, then do something like the following. Sub-routers are not needed.

    type UserRouter struct {
        cfg *Config
    
        // prefix is the path prefix for the user handlers. Use this
        // value to construct paths to other user handlers.
        prefix string 
    }
    
    // RegisterUserHandler adds the user handlers to mux using the
    // specified path prefix.
    func RegisterUserHandler(cfg *Config, prefix string, mux *http.ServeMux)  {
        u := &UserRouter{cfg: cfg, prefix: prefix}
        mux.HandleFunc(prefix+"/create", u.Create)
        mux.HandleFunc(prefix+"/delete", u.Delete)
    }
    
    func main() {
        mux := http.NewServeMux()
        cfg := &Config{nil /* Describes settings such as PostgreSQL. */}
        RegisterUserHandler(cfg, "/user", mux)
        log.Fatal(http.ListenAndServe(":8080", mux))
    }