Routing the Easy Way in Go
Tags: webdev, beginners, go, tutorial
Published: 2024-03-06 19:27:36

I have come across many developers who feel overwhelmed when trying to design a backend server in a language or a framework outside their comfort zone. Considering I was a fresher once who had similar problems, it is somewhat easy to empathize. One of my mentors Chirag Nayyar always said that if your fundamentals are clear, you will never be fazed. It is a belief I have come to share and it is precisely because of that belief that I will discuss the routing in web development when it comes to the backend.
We will be continuing with our little mini-project of Passwordless Auth in Go. In this article, we will discuss the essence of routing and then frame our routes and controller in Golang using Gorilla Mux. Before we start, I guess a few words of wisdom.

> We will be using the `utils` package in this article. This Go package was discussed in the 3rd article.

and

> We will be using the `data` package in this article. This Go package was discussed in the 4th article.

The code is hosted in the following GitHub Repo:
Embed GitHub Repo. 
{% embed https://github.com/abhik-99/Passwordless-Login-in-Go %}

**Fair Warning**: This article will be a bit long.

## Crash Course into Routing
So, before we had the whole REST paradigm, we used SOAP. At the time of writing, REST is the most widely accepted and used paradigm with a portion of GraphQL and a minor portion of tech arising out of HTTP 2.1.

REST comes with a few *“verbs”*. This is meant to convey meta-information about the HTTP request. These verbs are GET, POST, PUT, PATCH, and DELETE (with a few others that are not that often used by web developers most of the time). These verbs are attached as **METHOD** on the HTTP request. `GET` means you want to fetch some data from the web server. `POST` means you want to create a resource on the server. `PUT` and `PATCH` are for updating the data stored on the server. `PUT` is when you are sending the whole of the data object while `PATCH` is when you want to send only the portion of the data object on the server which needs a change. `DELETE` is obviously for deleting a resource on the server.

We also use a series of codes as responses from the server to denote the status of the request made to the server. These are registered with an entity called the *Internet Assigned Numbers Authority*. You can browse through the complete list [here](https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml).

Now, there are other information encoded in the URL itself. The first one is the URL parameter. These are essentially identifiers that come between the front slashes. Next are the URL queries. These come after the parameters. They come in a key:value pair separated by “=” and are written after a “?” in the URL. For example, in the URL `http://www.example.com/items/1?color=grey&size=6&unit=45”` we have `1` as the URL parameter while `color`, `size` and `unit` are URL queries. These URL queries are separated by an `&`.

When we place a request to a web server, regardless of the tech stack used behind the scenes, the flow remains the same. The web server intercepts the request -> the request goes through a middleware or a series of middleware -> request is at last handled by a handler function or a controller.

What is a middleware? A middleware is a piece of code that runs after a process is started but before the actual handler for the process is used. It is an easy way to encapsulate logic which might be common in a group of functions. All frameworks in all languages will have a way to denote that middleware has completed execution and the request can be passed to the next middleware in the series or to the handler function. This comes in the form of a **”next”** function. If the middleware does not complete successfully, it is meant to send back an error response to the client.

This more or less covers everything you need to know as a web developer to get down and dirty with development.

## The `routes` package
Refer back to the 2nd article in the series for the file structure. We will place our **routes** in the `routes` package. Routes are separated by their concern. In our case, we have 2 concerns – authentication and user profile fetching. So, we will have 2 Go files in the package – `auth_routes.go` and `user_routes.go` in the project.

### Authentication Routes

```go
package routes

import (
	"github.com/abhik-99/passwordless-login/pkg/controllers"

	"github.com/gorilla/mux"
)

func RegisterAuthRoutes(r *mux.Router) {

	r.HandleFunc("/signup", controllers.Signup).Methods("POST")

	//This initiates the Email-based authentication,
	r.HandleFunc("/email/{emailId}", controllers.OTPViaEmail).Methods("GET")
	//the Email & OTP can be passed via REQ Body
	r.HandleFunc("/email", controllers.LoginViaEmail).Methods("POST")

	//This initiates the Phone-based authentication,
	r.HandleFunc("/phone/{phoneNo}", controllers.OTPViaPhone).Methods("GET")
	// the Phone number & OTP can be passed via REQ Body
	r.HandleFunc("/phone", controllers.LoginViaPhone).Methods("POST")

}

```

Given above is the code we will put inside the `auth_routes.go` file. As you can see, we are defining a function in the above code called `RegisterAuthRoutes()`. This function takes in a reference to a Gorilla Mux Router (or Subrouter) and then attaches to its controllers (also called handler functions) for each of the routes.
In our `main.go` we had created an authentication subrouter using Gorilla Mux. The subrouter was defined in such a way that any route that is defined upon it will have the `/auth` prefix. This allows for easy grouping of routes based on concern as discussed in previous articles in this series.

The `HandleFunc()` method on the subrouter takes in the path and the controller for that path. We also define the HTTP Method which needs to be used for the route for the controller to be invoked as shown in the code above. In our case, the authentication paths are as follows:

| Route Name            | HTTP Method | Controller Name | Concern                                           |
|------------------------|-------------|------------------|---------------------------------------------------|
| `/signup`              | POST        | `Signup`         | Creates a User Profile                            |
| `/email/{emailId}`     | GET         | `OTPViaEmail`    | Initiates Auth by sending OTP to email if the user with that email has signed up prior. |
| `/email`               | POST        | `LoginViaEmail`  | Takes in the OTP and email and then returns JWT token if auth is successful.               |
| `/phone/{phoneNo}`     | GET         | `OTPViaPhone`    | Initiates Auth by sending OTP to phone if the user with that mobile has signed up prior. |
| `/phone`               | POST        | `LoginViaPhone`  | Takes in the OTP and phone and then returns JWT token if auth is successful.               |

### User Routes
Next, we have the `user_routes.go` file. This is where we will define all the routes relating to user action. The code below shows the contents of the `user_routes.go` file.

```go
package routes

import (
	"github.com/abhik-99/passwordless-login/pkg/controllers"
	"github.com/abhik-99/passwordless-login/pkg/middleware"

	"github.com/gorilla/mux"
)

// Protected Routes
func RegisterUserRoutes(r *mux.Router) {
	r.Use(middleware.ValidateTokenMiddleware)
	r.HandleFunc("", controllers.GetPublicUsers).Methods("GET")
	r.HandleFunc("/{id}", controllers.GetPublicUserProfile).Methods("GET")
	r.HandleFunc("/profile", controllers.GetUserProfile).Methods("GET")
}


```

Like the previous file, we have a function that takes in a reference to a Gorilla Mux router (or subrouter) and then registers a few routes on it. However, these routes are slightly different from the above ones. Can you guess how? There are two points to consider here:
1.	Firstly, in line number 12, we have also attached a middleware. We say that every time a request is made to these routes, run the `ValidateTokenMiddleware()` middleware function before actually passing the request to the handler functions. We will discuss what this middle does in the section below.
2.	There is a route without any name at line 13. This essentially means that if the subrouter has a prefix of `/user` then the Router will consider the controller mentioned for any request coming to only the prefix `/user`. In our case, whenever someone makes a GET request to `/user` then the `GetPublicUser` handler function is used to resolve it.

We have two other routes that we attach to the subrouter passed to the `RegisterUserRoutes()` function. These are:

| Route Name                 | HTTP Method | Controller Name         | Concern                                                                                 |
|----------------------------|-------------|--------------------------|-----------------------------------------------------------------------------------------|
| `/`                        | GET         | `GetPublicUsers`         | Fetches all the public profiles of users.                                               |
| `/{id}`                    | GET         | `GetPublicUserProfile`   | Fetches a specific user profile whose ID is passed in as a parameter.                   |
| `/profile`                 | GET         | `GetUserProfile`         | Fetches the user profile of the user logged in (basically who JWT is passed in the auth header). |

## The `controllers` package
The `controllers` package is where the code for the handler functions will be kept. Keeping in line with our convention of dividing the files into packages based on their concern, we will be segregating the controllers into two files - `auth_controllers.go` and `user_controllers.go`. Needless to say, the former will host the code for authentication routes while the latter will have the code for the user-oriented protected routes

Thanks to our discussions of the `utils` package in the previous articles, the explanation in this section can be kept brief. Most of the heavy work is being done by the authentication-related route handlers so we will focus on them with a brief discussion on one of the more important user routes.

### Authentication-oriented Controllers
The following is the code in the `auth_controllers.go` file.

```go
package controllers

import (
	"fmt"
	"net/http"
	"net/mail"

	"github.com/abhik-99/passwordless-login/pkg/data"
	"github.com/abhik-99/passwordless-login/pkg/utils"
	"github.com/gorilla/mux"
)

func Signup(w http.ResponseWriter, r *http.Request) {
	var newUser data.CreateUserDTO
	err := utils.DecodeJSONRequest(r, &newUser)
	if err != nil {
		utils.EncodeJSONResponse(w, http.StatusBadRequest, struct {
			utils.GenericJsonResponseDTO
			Err string `json:"err"`
		}{
			GenericJsonResponseDTO: utils.GenericJsonResponseDTO{
				Message: "Invalid Request Body",
			},
			Err: err.Error(),
		})
		return
	}
	if _, err := data.CreateNewUser(newUser); err != nil {
		http.Error(w, "Error Occurred while Creating user", http.StatusInternalServerError)
		return
	} else {
		utils.EncodeJSONResponse(w, http.StatusOK, utils.GenericJsonResponseDTO{Message: "User Created Successfully"})
	}

}

func OTPViaEmail(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	if _, err := mail.ParseAddress(params["emailId"]); err != nil {
		http.Error(w, "Invalid Email", http.StatusBadRequest)
		return
	} else {
		if _, userID, lookUpErr := data.UserLookupViaEmail(params["emailId"]); lookUpErr != nil {
			http.Error(w, "User does not exist", http.StatusUnauthorized)
			return
		} else {
			otp, err := utils.OTPGenerator()
			if err != nil {
				http.Error(w, "Error Occurred while generating user OTP", http.StatusInternalServerError)
				fmt.Println("[ERROR] ", err)
				return
			}
			a := data.Auth{UserId: userID, Otp: otp}
			if err := a.SetOTPForUser(); err != nil {
				http.Error(w, "Error Occurred while setting user OTP", http.StatusInternalServerError)
				fmt.Println("[ERROR] ", err)
				return
			}
			if err := utils.SendOTPMail(params["emailId"], otp); err != nil {
				http.Error(w, "Error Occurred while setting user OTP", http.StatusInternalServerError)
				fmt.Println("[ERROR] ", err)
				return
			}

			utils.EncodeJSONResponse(w, http.StatusOK, utils.GenericJsonResponseDTO{Message: "User OTP sent"})

		}
	}
}

func OTPViaPhone(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	phoneNo := params["phoneNo"]
	if !utils.IsValidPhoneNumber(phoneNo) {
		http.Error(w, "Invalid Phone number", http.StatusBadRequest)
		return
	} else {
		if result, userID, lookUpErr := data.UserLookupViaPhone(phoneNo); lookUpErr != nil {
			http.Error(w, "Error Ocurred while Lookup", http.StatusInternalServerError)
			fmt.Println("[ERROR] ", lookUpErr)
			return
		} else if !result {
			http.Error(w, "User Not Found", http.StatusNotFound)
			return
		} else {
			otp, err := utils.OTPGenerator()
			if err != nil {
				http.Error(w, "Error Occurred while generating user OTP", http.StatusInternalServerError)
				fmt.Println("[ERROR] ", err)
				return
			}
			a := data.Auth{UserId: userID, Otp: otp}
			if err := a.SetOTPForUser(); err != nil {
				http.Error(w, "Error Occurred while setting user OTP", http.StatusInternalServerError)
				fmt.Println("[ERROR] ", err)
				return
			}
			if err := utils.SendOTPSms(phoneNo, otp); err != nil {
				http.Error(w, "Error Occurred while setting user OTP", http.StatusInternalServerError)
				fmt.Println("[ERROR] ", err)
				return
			}

			utils.EncodeJSONResponse(w, http.StatusOK, utils.GenericJsonResponseDTO{Message: "User OTP sent"})

		}
	}
}

func LoginViaEmail(w http.ResponseWriter, r *http.Request) {
	var dto data.LoginWithEmailDTO
	err := utils.DecodeJSONRequest(r, &dto)
	if err != nil {
		utils.EncodeJSONResponse(w, http.StatusBadRequest, struct {
			utils.GenericJsonResponseDTO
			Err string `json:"err"`
		}{
			GenericJsonResponseDTO: utils.GenericJsonResponseDTO{
				Message: "Invalid Request Body",
			},
			Err: err.Error(),
		})
		return
	}
	result, userID, lookUpErr := data.UserLookupViaEmail(dto.Email)

	if !result {
		http.Error(w, "User Does not Exist", http.StatusNotFound)
		return
	}
	if lookUpErr != nil {
		http.Error(w, "Error Ocurred while Lookup", http.StatusInternalServerError)
		fmt.Println("[ERROR] ", lookUpErr)
		return
	}
	a := data.Auth{UserId: userID, Otp: dto.Otp}
	if result, err := a.CheckOTP(); err != nil {
		http.Error(w, "Error Ocurred while verifying OTP", http.StatusInternalServerError)
		fmt.Println("[ERROR] ", err)
		return
	} else if !result {
		http.Error(w, "OTP Does not Match", http.StatusBadRequest)
		return
	}

	if result, err := utils.GenerateJWT(userID); err != nil {
		http.Error(w, "Error Occured during access token generation", http.StatusBadRequest)
		fmt.Println("[ERROR] ", err)
		return
	} else {
		utils.EncodeJSONResponse(w, http.StatusOK, data.AccessTokenDTO{AccessToken: result})
	}

}

func LoginViaPhone(w http.ResponseWriter, r *http.Request) {
	var dto data.LoginWithPhoneDTO
	err := utils.DecodeJSONRequest(r, &dto)
	if err != nil {
		utils.EncodeJSONResponse(w, http.StatusBadRequest, struct {
			utils.GenericJsonResponseDTO
			Err string `json:"err"`
		}{
			GenericJsonResponseDTO: utils.GenericJsonResponseDTO{
				Message: "Invalid Request Body",
			},
			Err: err.Error(),
		})
		return
	}
	_, userID, lookUpErr := data.UserLookupViaPhone(dto.Phone)

	if lookUpErr != nil {
		http.Error(w, "User Does not Exist", http.StatusUnauthorized)
		return
	}
	a := data.Auth{UserId: userID, Otp: dto.Otp}
	if result, err := a.CheckOTP(); err != nil {
		http.Error(w, "Error Ocurred while verifying OTP", http.StatusInternalServerError)
		fmt.Println("[ERROR] ", err)
		return
	} else if !result {
		http.Error(w, "OTP Does not Match", http.StatusBadRequest)
		return
	}

	if result, err := utils.GenerateJWT(userID); err != nil {
		http.Error(w, "Error Occured during access token generation", http.StatusBadRequest)
		fmt.Println("[ERROR] ", err)
		return
	} else {
		utils.EncodeJSONResponse(w, http.StatusOK, data.AccessTokenDTO{AccessToken: result})
	}
}


```

Let’s discuss the `Signup()` function first. In lines 14 and 15, we try to decode the request body into the `CreateUserDTO`. This simultaneously validates it. One of the upsides of using Go in backend web development as opposed to Express is that this validation comes super easy thanks to the Validator package. In case the request body cannot be decoded or unmarshaled into the DTO, we that the user is trying to be smart with us.

We check for any error during the JSON unmarshalling phase at line 16. Between lines 17 and 25, a few interesting things happen. First off, we use the `EncodeJSONResponse()` function to send in an HTTP Bad Request response to the client. We define an anonymous struct between lines 17 and 19 and then simultaneously initialize it in the next lines. Pay close attention to how we embed fields from `GenericJsonResponseDTO`. This syntax allows us to embed fields from other structs into new structs easily in Go. Notice the way we initialize it between lines 21 and 24.

Even though when initializing embedded struct fields in Go in such a way we need to initialize the embedded struct separately when encoded, the result has all the fields at the same level in a JSON and we won’t find a JSON property called `GenericJsonResponseDTO` in the response. Instead, we will find the `message` field at the same level as `err`. If there are no errors, we create the user and send back the successful response.

Next, we have the `OTPViaEmail()` function. Refer to the previous section to see the route it handles. You will notice a different syntax. We have used curly braces or second brackets. This signifies a route parameter. The name of the route parameter is defined within the curly braces. To retrieve the URL parameter, we need to `mux.Vars()` in Gorilla Mux. This returns a `map`. The value of the URL parameter will be stored with the name as the key in this map.

We retrieve the email from the URL parameter map and then verify if it is a valid email using the `ParseAddress()` of the `mail` module. The happy path here consists of looking up if any user with the email exists & retrieving the MongoDB ID of the collection at line 43. We then generate a pseudo-random OTP using the `OTPGenerator()` utility function, set the OTP in our Redis DB with the user ID as the key at line 54 with the `SetOTPForUser()` which we defined on the `Auth` struct (being used here as a model), send the mail at line 59 with the `SendOTPMail()` utility function, and tell the client that the OTP has been sent to the user’s email at line 65.

Let’s discuss `LoginViaEmail()`. It is the next logical step in the flow. In this function, we first decode the request body to the LoginWithEmailDTO struct having the `Email` and `Otp` fields. Again, the happy flow here will then consist of looking up a user with that Email at line 125 (kind of redundant but acts as another flimsy line of defence), retrieving the User’s MongoDB ID, and then verifying the OTP by querying our Redis DB at line 137 with the `CheckOTP()` function defined on the `Auth` data model struct. If the OTP checks out then at line 146 we generate a JWT for our user using the `GenerateJWT()` utility function. The JWT at line 151 by marshalling it using the `AccessTokenDTO` struct.

There are two other functions called `OTPViaPhone()` and `LoginViaPhone()` in the same file in the GitHub Repo. These have similar steps albeit with the user’s phone number.

### User-oriented Controllers
Next, let’s head over to the `user_controller.go` file. We will discuss the `GetUserProfile()` controller here. In almost all web development scenarios, you will need one way or another to retrieve the authenticated user’s profile. That’s what we are doing in the code below.

```go
package controllers

import (
	"log"
	"net/http"

	"github.com/abhik-99/passwordless-login/pkg/data"
	"github.com/abhik-99/passwordless-login/pkg/utils"
	"github.com/gorilla/mux"
)

func GetPublicUsers(w http.ResponseWriter, r *http.Request) {
	if userProfiles, err := data.GetAllPublicUserProfiles(); err != nil {
		http.Error(w, "Error Occurred while fetching users", http.StatusInternalServerError)
		log.Println("ERROR", err)
	} else {
		utils.EncodeJSONResponse(w, http.StatusOK, userProfiles)
	}
}

func GetPublicUserProfile(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	id := params["id"]
	if !utils.IsValidObjectID(id) {
		http.Error(w, "Invalid User ID", http.StatusBadRequest)
		return
	}
	user, err := data.GetUserProfileById(id)
	if err != nil {
		http.Error(w, "Invalid User ID", http.StatusBadRequest)
		log.Println("ERROR while user profile query", err)
		return
	}
	utils.EncodeJSONResponse(w, http.StatusOK, user)

}

func GetUserProfile(w http.ResponseWriter, r *http.Request) {
	userID := r.Header.Get("user")
	user, err := data.GetUserProfileById(userID)
	if err != nil {
		http.Error(w, "Invalid User ID", http.StatusBadRequest)
		log.Println("ERROR while user profile query", err)
		return
	}
	utils.EncodeJSONResponse(w, http.StatusOK, user)

}


```

At line 39, we have the get the MongoDB Collection ID for the authenticated user by retrieving the value of the `user` header. But where did we define any step for this? Where is it coming from? That’s going to be revealed in the next section. We then make a simple User ID-based lookup in our MongoDB user collection at line 40 and return the full user profile at line 46.

The other functions are simple so we will skip discussing them as well. But do glance over the code and try to understand what they do. 

## The `middlewares` package
Lastly, we arrive at the `middlewares` package. In our case, we only have one middleware. This we will place in the `auth_middleware.go` file. The code for the file is given below. Discussion follows.

```go
package middleware

import (
	"net/http"
	"strings"

	"github.com/abhik-99/passwordless-login/pkg/utils"
)

func ValidateTokenMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		tokenString := r.Header.Get("Authorization")
		if tokenString == "" {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		splitToken := strings.Split(tokenString, "Bearer ")
		if len(splitToken) != 2 {
			http.Error(w, "Invalid token format", http.StatusUnauthorized)
			return
		}

		tokenString = splitToken[1]

		tokenClaims, err := utils.ValidateJWT(tokenString)

		if err != nil {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		userId, _ := tokenClaims.GetSubject()
		r.Header.Set("user", userId)

		next.ServeHTTP(w, r)
	})
}


```

Notice the special syntax of the middleware. Unlike the handler function, it does not take in a `ResponseWriter` and a reference to the `Request`. Instead, it takes in the **“next”** handler. As explained in an earlier section, this next function is invoked if the middleware has successfully completed the processing of the request.

Also, pay close attention to how the `ValidateTokenMiddleware()` returns a handler function in the form of a Golang closure. This is the syntax in Gorilla Mux in Go. In ExpressJS, you might need to follow such conventions. Inside the handler function, we split the auth header and then took the JWT. We check for its validity at line 27 with the `ValidateJWT()` utility. If the JWT is valid, then invoking the `GetSubject()` function on the returned claims will give us the user ID as that’s how we created the JWT in the first place.

At line 34 we mutate our HTTP request. Many new web developers miss this step, they might take an alternative approach. But we need to consider these two things:
1.	Our HTTP request is like a car containing people (people being analogous to data) that passes through a pathway. This pathway is essentially the web server’s request-handling process up to the request handler function. So, it is not exposed to the outside world.
2.	It will get discarded once the request is handled.
So, we can easily use it as a carrier for some bytes of extra information to relieve us of extra computation. Here, setting the `user` header in the middleware helps us skip the JWT decoding step in the `GetUserProfile` handler function. 

At last, the `next.ServeHTTP(w, r)` at line 36 passed this request to the next middleware/handler in the series or in our case, the router handler functions defined on the user route subrouter.

With this discussion, the whole mini-project discussion can be concluded as we have explored all the important aspects of it throughout 5 articles.

## Conclusion
If you have stuck around till this part, then kudos to your patience and dedication. In this series, we explored the Go backend web development stack consisting of Gorilla Mux, MongoDB, and Redis by building a password-less authentication system in Go. While it is funny how the whole “passwordless” works in this paradigm of authentication, it is what it is. I guess I will go back to completing the Chainlink CCIP series which I had taken a hiatus from after this. So, until next time, keep building awesome things and WAGMI!