Table of contents
This article is a follow-up to its long-lost brother which discussed what a password-less login method in Go might look like. In this one, we actually make one. As discussed in the previous article, we will have a server written in Go communicating with a MongoDB instance and storing OTP in a Redis container.
Golang is one of those languages which I just seemingly keep encountering again and again. I must say that I like to use it and feel that it is one of the more productive languages currently in the industry. It’s easier as well. But all that is just me ranting about my preferences. In this article, we will mainly discuss the modules that we will use along with some house-keeping code and Golang Project Structure. I will try to impose a 1k-ish work limit on my articles from now on just like one of those devs who arbitrarily decided 100-lines as the mark for starting code segregation. I wonder how long I will be able to stick to this limit though…
Full Disclosure
Before we begin, there are some things that we need to get out of the way. These are:
We will be using Gorilla Mux. As per their last update, they have a new group of maintainers, and their repos have shown activity to confirm that. The tutorial can be easily replicated in any other framework or library as well. So, while we will be using Gorilla Mux, you can try to replicate it in Gin or Fiber as well.
At this time, Twillio has a verification service that provides an SDK and offloads the responsibility of creating, sending, and maintaining OTPs from the project to Twillio. Using this service would mean we won’t need Redis. So, we will not be using this service. We will be using two of Twillio’s other services – Phone SMS Sending and Email Sending services. This will be more work but then again, I find it to be fun. Perhaps we will explore the verification service when I do this mini-project again but in Rust.
We will have our Utility for encoding JSON response. We will be using this for all Success responses but only for those error responses that require custom communication with the user. For any other errors, we will be using Go’s standard library’s
http.Error()
.We will be using Docker Compose for MongoDB and Redis instances. So, make sure to have that installed beforehand.
It will be a beginner-oriented tutorial so if you have some expertise, it might seem slow-paced.
In this tutorial, we will not create a full-fledged system. The server will cover the authentication part and basic user profile-related read operations. The code has been open-sourced in the repo below and CRUD operation utilities are written so that readers can extend the functionality in terms of routes and controllers as an exercise.
Housekeeping
So, before we begin, let’s have the project setup done. If you are new to Go, then you need to run go mod init github.com/<your username>/passwordless-in-go
. This initializes a Go Project and the go.mod
file will be created after this. If you have any experience in Backend development in Node, then this is the Go counterpart of npm init
. We will be using the following Go modules for our little project:
Gorilla Mux as our Backend framework.
Godotenv for managing our environment variables
Mongo Driver for Go for communicating with Mongo DB Docker Container.
Go Playgroun’s Validator for validating our DTOs.
Go Redis’ Module for communicating with Redis. Please go ahead and add these to the project by using
go get
command. We will be having a project organization that reflects the purpose (as shown in the image below). I am not saying this is the “ideal” Go project structure. All I am saying that is this one makes a bit more sense to me. So please feel free to have your project structure if you feel like it.
The cmd
folder contains our main.go
. This is where the execution starts. The pkg
folder contains packages associated with the project, namely, -
config
- This folder will house our configuration-related code. This is mainly context and database connections. These DB connections will be used throughout the project.controllers
- This folder will have the code for our route controllers. It will not directly use anything from the above package. It will mainly interact with the model part of the project (residing in our folder below) and contain business logic.data
- This folder will contain the structure of our entities and data transfer objects. Both will be represented bystruct
. This package will utilize theconfig
package and form a wrapper around the DB entities.middlewares
- This folder will harbour the logic for handling what comes from the user before it is passed onto our controllers. Mainly used to mutate the request and response bodies and have reusable route-related codes.routes
- We will define our Subrouters in this folder. This package will sit on top of thecontrollers
package. The actual router will be defined in themain
package and this package will register routes and route groups (groups of routes using similar logic for eg. protected routes which will use a JWT verification middleware fall under the same route group and will be defined on the same subrouter).utils
- Any reusable and shared code will be defined here. These range from wrapper for accessing Environment Variables to functions for JWT manipulation.
Last but not least, let’s have a moment to discuss the Docker Compose file that we will be using for this project. As mentioned before, we will not have hosted solutions for MongoDB and Redis. We will have our own Mongo and Redis instances running in docker containers.
version: '3'
services:
redis:
image: "redis:latest"
environment:
- REDIS_PASSWORD=${REDISPASS}
ports:
- "6379:6379"
networks:
- backend_network
mongodb:
image: "mongo:latest"
environment:
- MONGO_INITDB_ROOT_USERNAME=${DBUSER}
- MONGO_INITDB_ROOT_PASSWORD=${DBPASS}
ports:
- "27017:27017"
networks:
- backend_network
networks:
backend_network:
The docker-compose file above is really simple if you think about it. In the above docker compose file we defined two services – redis
and mongodb
. We tell docker to pick the latest Redis and Mongo images for the services respectively. After that, we define the Redis Password (REDIS_PASSWORD
) to be picked automatically from the .env
file’s REDISPASS
environment variable. For MongoDB, we define the username and password that will be used to authenticate connection using the MONGO_INITDB_ROOT_USERNAME
and MONGO_INITDB_ROOT_PASSWORD
respectively. These will also be picked from the respective environment variables. For Redis, we expose port 6379 while for Mongo we expose port 27017. We create a network called backend_network
and tell docker daemon that both services will be a part of this network.
You can look into the .env
file in the repo above to get a full list of environment variables. I would recommend creating a .env.local
file out of it by copying and renaming it. You can then run docker compose –env-file .env.local up
to start the databases.
Conclusion
This concludes this short article. In this article, we mainly discussed the housekeeping aspects of our mini-project. In the next one, we will start to get down and dirty with Golang code and test out the server we create and then some. The second part in a series is almost always disappointing in some aspect so I guess I might have ticked the wrong box for some dev readers with this article. But I keep my promises and so this one was 1k-ish.
Until the next one, keep building awesome things and WAGMI!