This is my third attempt at writing this article, mainly because each time I sit down to write it, I end up creating a whole new Router architecture and in the last attempt, also made my own generic middleware library. So going forward, we’re going to continue using only the net/http
library without making our own router. This isn’t to say that we’re giving up on making our router, just that I’d like to go through the basics before we get bogged down with the technical stuff.
Great! With that out of the way, let’s begin!
Introduction
In the last post, we learnt how to handle query strings and parameters in our routes.
Of course, passing a query string whenever you want some state passed in isn’t always the best apporach. Sometimes the state you need to pass in is essential to the correct functioning of the website, and isn’t optional. In cases like this, it’s best to use something we call request parameters
. Request parameters allow you to define the required arguments right in the URL without having to pass in additional state like a query string.
Where earlier you would have had to do something like: https://website.com/profile?user=442
now, you can do do something like https://website.com/profile/442
. This is not only cleaner, but also makes it clear to the user that /profile
would error out if a user
isn’t passed in.
Query Strings Recap
Let’s write a simple server to handle our /profile
route.
First, let’s see what the old, query string
version of it looked like, for comparing and contrasting later.
server := http.NewServeMux()
server.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) { user := r.URL.Query().Get("user") fmt.Fprintf(w, "Profile data for User %v", user)})
log.Fatal(http.ListenAndServe(":8080", server))
Making a request to this route, we get:
iwr "http://localhost:8080/profile?user=442"
HTTP/1.1 200 OKContent-Length: 25Content-Type: text/plain; charset=utf-8
Profile data for User 442
Awesome! We’re getting back what we expect.
But what if we didn’t pass in the user? Even though it is clearly essential to the functioning of this route.
iwr "http://localhost:8080/profile"
HTTP/1.1 200 OKContent-Length: 22Content-Type: text/plain; charset=utf-8
Profile data for User
We seemingly do get a 200
response back, but if you look closely, we don’t actually have any user information. The route works even when it shouldn’t.
Let’s fix it.
server.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) { user := r.URL.Query().Get("user") if user == "" { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "Missing User") return } fmt.Fprintf(w, "Profile data for User %v", user)})
Let’s make that request again, and…
iwr "http://localhost:8080/profile"
HTTP/1.1 400 Bad RequestContent-Length: 12Content-Type: text/plain; charset=utf-8
Missing User
Yay! Our route errors out! But right now, we’re handling it manually. And I’m sure you can imagine, doing this for every essential query parameter would get tedious really quickly.
Let’s now look at the request parameter
approach.
Request Parameters
We’ll rewrite our /profile
route to also take in a user
parameter. So the route in the final request would look something like this: /profile/1124
with 1124
being the user
we pass in.
server.HandleFunc("/profile/{user}", func(w http.ResponseWriter, r *http.Request) { user := r.PathValue("user") fmt.Fprintf(w, "Profile data for User %v", user)})
Let’s see what’s going on here.
If you look at the patterns
section of the net/http
docs](https://pkg.go.dev/net/http#hdr-Patterns-ServeMux) you’ll see that you can define wildcards
using the curly braces in the URL
pattern.
In the above route handler, we used a wildcard
that we called user
in the pattern. This allowed us to reference it using the PathValue
function on the http.Request
parameter in the Handler
.
Let’s try making a request using this new route.
iwr "http://localhost:8080/profile/442"
HTTP/1.1 200 OKContent-Length: 25Content-Type: text/plain; charset=utf-8
Profile data for User 442
Awesome! We passed in 442
as the user and got it’s details as the response!
But last time we had to manually error-handle the case of no user being passed in. Let’s see if we still need to do that.
iwr http://localhost:8080/profile
HTTP/1.1 404 Not FoundContent-Type: text/plain; charset=utf-8X-Content-Type-Options: nosniffContent-Length: 19
404 page not found
It seems like it already throws an error when a correct URL isn’t supplied, which is exactly what we want anyway. This means we don’t need to make any changes to it. It’s good to go!
And now you know how to handle request parameters in go using the standard net/http
library!