how to organize gorilla mux routes?
Asked Answered
H

4

19

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.

Harvest answered 31/12, 2015 at 14:53 Comment(0)
D
13

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")
    }
}
Delrio answered 31/12, 2015 at 15:5 Comment(3)
I did that for simplicity sake, but my handlers are actually defined in the routes package, so i still need to take the routes out of the main functionHarvest
I don't get the point of this: either way you're still going to write your routes somewhere… If your "main" is too long, maybe you can write an NewRouter helper that will initialize it for you.Delrio
Another solution would be to have an initialization function in your route package that take the router as input and add the routes as it see fit. But I strongly advise against.Delrio
E
23

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"))
}
Extravascular answered 24/9, 2018 at 6:47 Comment(0)
D
13

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")
    }
}
Delrio answered 31/12, 2015 at 15:5 Comment(3)
I did that for simplicity sake, but my handlers are actually defined in the routes package, so i still need to take the routes out of the main functionHarvest
I don't get the point of this: either way you're still going to write your routes somewhere… If your "main" is too long, maybe you can write an NewRouter helper that will initialize it for you.Delrio
Another solution would be to have an initialization function in your route package that take the router as input and add the routes as it see fit. But I strongly advise against.Delrio
B
6

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.

Bierman answered 31/12, 2015 at 15:54 Comment(0)
U
1

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)
}
Utas answered 1/9, 2022 at 3:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.