zolamk
zolamk

Reputation: 6377

how to organize gorilla mux routes?

i am using Gorilla Mux for writing a REST API and i am having trouble organizing my routes, currently all of my routes are defined in the main.go file like this

//main.go
package main

import (
    "NovAPI/routes"
    "fmt"
    "github.com/gorilla/mux"
    "net/http"
)

func main() {

    router := mux.NewRouter().StrictSlash(true)

    router.HandleFunc("/hello", func(res http.ResponseWriter, req *http.Request) {
        fmt.Fprintln(res, "Hello")
    })

    router.HandleFunc("/user", func(res http.ResponseWriter, req *http.Request) {
        fmt.Fprintln(res, "User")
    })

    router.HandleFunc("/route2", func(res http.ResponseWriter, req *http.Request) {
        fmt.Fprintln(res, "Route2")
    })

    router.HandleFunc("/route3", func(res http.ResponseWriter, req *http.Request) {
        fmt.Fprintln(res, "Route3")
    })

    // route declarations continue like this

    http.ListenAndServe(":1128", router)

}

so what i want to do is take out and split this route declaration into multiple files, how would i go about doing that? thanks in advance.

Upvotes: 19

Views: 11590

Answers (4)

Rohan Kumar Thakur
Rohan Kumar Thakur

Reputation: 733

Since I am new to Go, I prefer a less verbose solution. In each module, we can create a Route function that expects a main route pointer and creates sub-routes to it. Our main.go file would be as follows

package main

import (
    "net/http"
    "github.com/user-name/repo-name/auth"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    auth.Router(r)
    http.ListenAndServe(":8080", r)
}

then in auth module, we can create a route file

package auth

import "github.com/gorilla/mux"

func Router(r *mux.Router) {
    routes := r.PathPrefix("/auth").Subrouter()

    routes.HandleFunc("/register", Register)
}

Upvotes: 1

Loris
Loris

Reputation: 791

You can modularize your routers independently into different packages, and mount them on the main router

Elaborating just a little on the following issue, you can come up with this approach, that makes it quite scalable (and easier to test, to some degree)

/api/router.go

package api

import (
    "net/http"

    "github.com/gorilla/mux"
)

func Router() *mux.Router {
    router := mux.NewRouter()
    router.HandleFunc("/", home)
    return router
}

func home(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("hello from API"))
}

/main.go

package main

import (
    "log"
    "net/http"
    "strings"

    "github.com/...yourPath.../api"
    "github.com/...yourPath.../user"
    "github.com/gorilla/mux"
)

func main() {
    router := mux.NewRouter()

    router.HandleFunc("/", home)
    mount(router, "/api", api.Router())
    mount(router, "/user", user.Router())

    log.Fatal(http.ListenAndServe(":8080", router))
}

func mount(r *mux.Router, path string, handler http.Handler) {
    r.PathPrefix(path).Handler(
        http.StripPrefix(
            strings.TrimSuffix(path, "/"),
            handler,
        ),
    )
}

func home(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("Home"))
}

Upvotes: 23

abm
abm

Reputation: 629

I like checking out other projects in github to grab ideas on how to do stuff, and for these cases I usually take a look first at the Docker repo. This is the way they do it:

For the system's routes, define all handlers in system_routes.go and then initialize those routes on a NewRouter function in system.go.

type systemRouter struct {
    backend Backend
    routes  []router.Route
}

func NewRouter(b Backend) router.Router {
    r := &systemRouter{
        backend: b,
    }

    r.routes = []router.Route{
        local.NewOptionsRoute("/", optionsHandler),
        local.NewGetRoute("/_ping", pingHandler),
        local.NewGetRoute("/events", r.getEvents),
        local.NewGetRoute("/info", r.getInfo),
        local.NewGetRoute("/version", r.getVersion),
        local.NewPostRoute("/auth", r.postAuth),
    }

    return r
}

// Routes return all the API routes dedicated to the docker system.
func (s *systemRouter) Routes() []router.Route {
    return s.routes
}

Notice that systemRouter implements the router.Router interface and the Routes function returns a []router.Route, and their handlers are defined as

func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error

instead of Go's standard http handler:

func(w http.ResponseWriter, r *http.Request)

So there's extra code of theirs to convert a Docker API Handler to a Go HTTP Handler at the makeHttpHandler function.

And finally, to add those routes to their mux router, on their server.go they implement several other functions to add middleware to their handlers.

If this is something that you think it's what you are looking for, then take your time to analyze the Docker code for their routes, and if you need me to elaborate more or if I missed anything, post a comment.

Upvotes: 6

Elwinar
Elwinar

Reputation: 9509

What about something like this ?

//main.go
package main

import (
    "NovAPI/routes"
    "fmt"
    "github.com/gorilla/mux"
    "net/http"
)

func main() {

    router := mux.NewRouter().StrictSlash(true)

    router.HandleFunc("/hello", HelloHandler)
    router.HandleFunc("/user", UserHandler)
    router.HandleFunc("/route2", Route2Handler)
    router.HandleFunc("/route3", Route3Handler)
    // route declarations continue like this

    http.ListenAndServe(":1128", router)

}

func HelloHandler(res http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(res, "Hello")
}

func UserHandler(res http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(res, "User")
}

func Route2Handler(res http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(res, "Route2")
}

func Route3Handler(res http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(res, "Route3")
}

That way you can put your handlers in other files, or even other packages.

If you endup with additionnal dependencies like a database, you can even avoid the need of the global var using a constructor trick:

//main.go

func main() {
    db := sql.Open(…)

    //...

    router.HandleFunc("/hello", NewHelloHandler(db))

    //...
}

func NewHelloHandler(db *sql.DB) func(http.ResponseWriter, *http.Request) {
    return func(res http.ResponseWriter, req *http.Request) {
        // db is in the local scope, and you can even inject it to test your
        // handler
        fmt.Fprintln(res, "Hello")
    }
}

Upvotes: 13

Related Questions