dynamic routes in Go with net/http
Sunntig,18 Oktoober, 2020

Sometime ago I wrote a simple CMS in Go that uses just the default library net/http for routing.
One of the benefits of go is that, unlike other languages like python, ruby, nodejs etc. you donโ€™t have to use a framework.

The default libraries comes with templates and net/http can indeed be used for routing. My initial code dealt with routes like this:

 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        pages := r.URL.Query()["page"]
        if len(pages) == 0 {
            fmt.Fprintf(w, "ยงยงยงยงยงยงยงยงยงยง You need to specify a page ยงยงยงยงยงยงยงยงยงยง")
            return
        }
        page := pages[0]
        var a Page
        err := db.QueryRow("SELECT * FROM pages where page = ?", page).Scan(&a.Page, &a.Date, &a.Url)
        a.Year = time.Now().UTC().Year()
        if err != nil {
            if err == sql.ErrNoRows {
                fmt.Fprintf(w, "Page %s not found", page)
                return
            } else {
                fmt.Fprintf(w, "Some error happened")
                return
            }
        }
        http.Redirect(w, r, a.Url, 301)
    }) 

Now this worked fine but would results in your routes being something like yourwebsite.com/?page=privacy because in http.HandleFunc you canโ€™t add a variable.

I recently wanted to make a URL shortener to avoid remember very long links (think zoom conferences URL or shared excel files) and in that case the /?q= was not as a nice as having the shortener to work with a cleaner /pagename with no r.URL.Query().

A lot of googling revealed that most people had this problem for multiple level routes like /users/active/list. Others suggested to use gorilla/mux or a http router that handles dynamic routes better or even faster than the standard libs.

But keeping with the idea to use just net/http I found this simple solution:

 pages := r.URL.Path[1:]
       if len(pages) == 0 {
            fmt.Fprintf(w, "ยงยงยงยงยงยงยงยงยงยง You need to specify a page ยงยงยงยงยงยงยงยงยงยง")
          return
      } 

what this code does is first check if we get a page parameter, if there is none it will return an error. Of course you can decide to do something else e.g. show an index page.

Now we remove the trailing slash / that can appear if the user types mywebsite.com/redirect/ with an additional / at the end

     page := strings.Replace(r.URL.Path[1:], "/", "", -1) 
       var a Page 
and then we query the database to see is such page exists

 err := db.QueryRow("SELECT * FROM pages where page = ?", page).Scan(&a.Page, &a.Date, &a.Url)
       a.Year = time.Now().UTC().Year()
        if err != nil {
         if err == sql.ErrNoRows {
               fmt.Fprintf(w, "Page %s not found", page)
               return
          } else {
                fmt.Fprintf(w, "Some error happened")
               return
          }
       }
       http.Redirect(w, r, a.Url, 301) 

and then do the redirect. This can be easily changed to display a template with a dynamic page (e.g. for a blog) by replacing the http.Redirect with something like

tmpl.ExecuteTemplate(w, โ€œpage.htmlโ€, a)
This is not ๐Ÿš€ science but since most of the tutorials about go propose to use routers like gorilla/mux, or a light frameworks like gin but I thought this would be useful to the purists that wish to use the main library when possible