Method-based routing
Next, let’s follow HTTP good practice and restrict our application so that it only responds to requests with an appropriate HTTP method.
As we progress through our application build, our home
, snippetView
and snippetCreate
handlers will merely retrieve information and display pages to the user, so it makes sense that these handlers should be restricted to acting on GET
requests.
To restrict a route to a specific HTTP method, you can prefix the route pattern with the necessary HTTP method when declaring it, like so:
It’s also worth mentioning that when you register a route pattern which uses the GET
method, it will match both GET
and HEAD
requests. All other methods (like POST
, PUT
and DELETE
) require an exact match.
Let’s test out this change by using curl to make some requests to our application. If you’re following along, start by making a regular GET
request to http://localhost:4000/
, like so:
The response here looks good. We can see that our route still works, and we get back a 200 OK
status and a Hello from Snippetbox
response body, just like before.
You can also go ahead and try making a HEAD
request for the same URL. You should see that this also works correctly, returning just the HTTP response headers, and no response body.
In contrast, let’s try making a POST
request to http://localhost:4000/
. The POST
method isn’t supported for this route, so you should get an error response similar to this:
So that’s looking really good. We can see that Go’s servemux has automatically sent a 405 Method Not Allowed
response for us, including an Allow
header which lists the HTTP
methods that are supported for the request URL.
Adding a POST-only route and handler
Let’s also add a new snippetCreatePost
handler to our codebase, which we’ll use later on to create and save a new snippet in a database. Because creating and saving a snippet is a non-idempotent action that will change the state of our server, we want make sure that this handler acts on POST
requests only.
All in all, we’ll want our fourth handler and route to look like this:
Route pattern | Handler | Action |
---|---|---|
GET /{$} | home | Display the home page |
GET /snippet/view/{id} | snippetView | Display a specific snippet |
GET /snippet/create | snippetCreate | Display a form for creating a new snippet |
POST /snippet/create | snippetCreatePost | Save a new snippet |
Let’s go ahead and add the necessary code to our main.go
file, like so:
Notice that it’s totally OK to declare two (or more) separate routes that have different HTTP methods but otherwise have the same pattern, like we are doing here with"GET /snippet/create"
and "POST /snippet/create"
.
If you restart the application and try making some requests with the URL path /snippet/create
, you should now see different responses depending on the request method that you use.
Additional information
Method precedence
The most specific pattern wins rule also applies if you have route patterns that overlap because of a HTTP method.
It’s important to be aware that a route pattern which doesn’t include a method — like "/article/{id}"
— will match incoming HTTP requests with any method. In contrast, a route like "POST /article/{id}"
will only match requests which have the method POST
. So if you declare the overlapping routes "/article/{id}"
and "POST /article/{id}"
in your application, then the "POST /article/{id}"
route will take precedence.
Handler naming
I’d also like to emphasize that there is no right or wrong way to name your handlers in Go.
In this project, we’ll follow a convention of postfixing the names of any handlers that deal with POST
requests with the word ‘Post’. Like so:
Route pattern | Handler | Action |
---|---|---|
GET /snippet/create | snippetCreate | Display a form for creating a new snippet |
POST /snippet/create | snippetCreatePost | Create a new snippet |
But in your own work it’s not necessary to follow this pattern. For example, you could prefix handler names with the words ‘get’ and ‘post’ instead, like this:
Route pattern | Handler | Action |
---|---|---|
GET /snippet/create | getSnippetCreate | Display a form for creating a new snippet |
POST /snippet/create | postSnippetCreate | Create a new snippet |
Or even give the handlers completely different names. For example:
Route pattern | Handler | Action |
---|---|---|
GET /snippet/create | newSnippetForm | Display a form for creating a new snippet |
POST /snippet/create | createSnippet | Create a new snippet |
Basically, you have the freedom in Go to choose a naming convention for your handlers that works for you and fits with your brain.
Third-party routers
The wildcard and method-based routing functionality that we’ve been using in the past two chapters is relatively new to Go — it only became part of the standard library in Go 1.22. While this is a very welcome addition to the language and a big improvement, you might find that there are still times where the standard library routing functionality doesn’t provide everything that you need.
For example, the following things are not currently supported:
- Sending custom
404 Not Found
and405 Method Not Allowed
responses to the user (although there is an open proposal regarding this). - Using regular expressions in your route patterns or wildcards.
- Matching multiple HTTP methods in a single route declaration.
- Automatic support for
OPTIONS
requests. - Routing requests to handlers based on unusual things, like HTTP request headers.
If you need these features in your application, you’ll need to use a third-party router package. The ones that I recommend are httprouter, chi, flow and gorilla/mux, and you can find a comparison of them and guidance about which one to use in this blog post.