Back

/ 4 min read

How To Handle Request Parameters In Go

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:

Terminal window
iwr "http://localhost:8080/profile?user=442"
HTTP/1.1 200 OK
Content-Length: 25
Content-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.

Terminal window
iwr "http://localhost:8080/profile"
HTTP/1.1 200 OK
Content-Length: 22
Content-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…

Terminal window
iwr "http://localhost:8080/profile"
HTTP/1.1 400 Bad Request
Content-Length: 12
Content-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.

Terminal window
iwr "http://localhost:8080/profile/442"
HTTP/1.1 200 OK
Content-Length: 25
Content-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.

Terminal window
iwr http://localhost:8080/profile
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Content-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!