David.dev

dynamic routes in Go with net/http


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. 


13

made with ❤ī¸ by david.dev 2024 RSS Feed