Build a RESTful API using Golang and Gin
Time to read: 8 minutes
Go has risen to be one of the most in-demand programming languages - despite being relatively young. For example, it's been used to build a wide range of applications including CLIs, compilers, APIs, and desktop apps. What's more, it has a gentler learning curve and faster execution time than most modern languages. So you'd be hard-pressed to go wrong by using Go for your next project.
This is the first in a series of articles where I will show you how to build, test, dockerize, and deploy an application written in Go. In this article, you will build a RESTful API using Golang and Gin. If you're not familiar with it, Gin is an HTTP web framework written in Go. The application will have a database powered by PostgreSQL.
What you will build
You will build an API for a personal diary app which will have the following features:
- Registration with a username and password
- Login with a username and password
- Create a new diary entry
- Retrieve all diary entries
Prerequisites
To follow this tutorial, you need the following:
Get started
To begin, in your development folder create a new folder for the project named diary_api, and navigate into it by running the following commands:
Next, initialize a Go module within the project by issuing the following command.
This creates a go.mod file in the top-level directory of the project, where your project’s dependencies will be tracked.
Install the project’s dependencies
As mentioned, this project uses the Gin framework. Run the following command from the top-level directory of your project to install the latest version of Gin, along with the other required dependencies.
Once the installation process is completed, you'll have Gin and the following packages available to your application:
- Go Cryptography: This provides supplementary Go cryptography libraries.
- GoDotEnv: This will help with managing environment variables.
- GORM: This is an ORM (Object Relational Mapper) for Golang. In addition to the library, the GORM dialect (driver) for Postgres is installed to enable connections to PostgreSQL databases.
- JWT-Go: A Go implementation of JSON Web Tokens.
Prepare the database and environment variables
Before writing any code, create a new PostgreSQL database named diary_app
, by running the following psql command, after replacing the three placeholders.
When prompted, provide the password associated with DB_USER
.
Alternatively, create the database using your preferred tool of choice.
Next, in the diary_api folder, create a new file named .env and add the following to it.
In addition to placeholders for database parameters, two variables: TOKEN_TTL
and JWT_PRIVATE_KEY
, are also declared. They will be used in generating a signed JWT later on.
Next, make a copy of .env named .env.local using the following command.
Replace the placeholder values in .env.local with the respective details for your PostgreSQL database.
Prepare models
Next, you'll create the two models for the application: User and Entry. To do this, start by creating a new folder named model. In that directory, create a new file named user.go, and then add the following code to the newly created file.
The above struct is composed of the Gorm Model struct, a string for the user’s username, another string for the password, and a slice of Entry items. In this way, you're specifying a one-to-many relationship between the User struct and the Entry structs.
Next, create a new file named entry.go and add the following code to it.
With the models in place, create a helper function to connect to the database, by creating a new folder named database, and in it a new file named database.go. Then, add the following code to the new file.
The Connect()
function retrieves the environment variables required to set up a database connection and then opens the connection using the GORM PostgreSQL driver.
Next, create a new file that will be used as the entry point of the application at the root of the folder named main.go and add the following code to it.
In the main()
function, the environment variables are loaded and the connection is established with the database. If the connection was opened successfully, the AutoMigrate()
function is called to create the relevant tables and columns for the User and Entry structs (if they don’t already exist).
Next, start the application by running the following command
After the application starts, you will see the following message.
If you look in your database, you will see that it now contains two new tables, as shown below.
Implement registration and login
Before creating the API endpoints, create a model of what is expected for authentication requests. In the model folder, create a new file called authenticationInput.go and add the following code to it.
Registration
Next, add the following methods to the User
struct in model/user.go.
The Save()
function adds a new user to the database (in the absence of any errors). Before saving, any whitespace in the provided username is trimmed out and the provided password is hashed for security purposes.
Remember to update your import
statement to match the code below.
Next, create a new folder named controller. Then, in the newly created folder, create a new file named authentication.go, and add the following code to it.
The Register()
method validates the JSON request, creates a new user, and returns the details of the saved user as a JSON response.
Login
Next, add another method, ValidatePassword()
, to the User
struct, which will be used to validate a provided password for a given user. Open model/user.go and add the following code.
Using the bcrypt library, a hash is generated for the provided plaintext password and compared with the hash of the user’s password. An error is returned if they do not match.
The FindUserByUsername()
function is also declared. It takes a username and queries the database to find the corresponding user.
Next, create a new folder named helper. In this folder, create a new file named jwt.go. In it, add the following code.
This function takes a user model and generates a JWT containing the user’s id (id
), the time at which the token was issued (iat
), and the expiry date of the token (eat
). Using the JWT_PRIVATE_KEY
environment variable, a signed JWT is returned as a string.
Next, in controller/authentication.go, add the following function.
Then, update the import list at the top of the file, to match the following:
Next, update main.go to match the following code.
In addition to loading the environment variables and database connection, you are now creating a Gin router and declaring two routes for registration and login respectively.
To test the registration and login functionality, stop the application using CTRL + c and restart it with:
Use curl to test the authentication part of the application by issuing the following command from a new terminal session, after replacing the placeholders with your username and password, respectively:
You will see an output similar to this:
To log in, use curl again, as shown below, after replacing the placeholders with your username and password, respectively.
You should see output, similar to the example below, that returns the JWT.
Implement middleware to handle requests to authenticated endpoints
The next endpoints you will implement require that the user be authenticated. In other words, requests to these endpoints will require a bearer token in the request header. If none is found, an error response should be returned.
To do this, you will implement middleware. This middleware will intercept requests and ensure that a valid bearer token is present in the request before the appropriate handler is called.
Before building the middleware, you’ll need to add some helper functions to make the process of extracting and validating JWTs easier. Add the following functions to helper/jwt.go.
Then, ensure that the import
statement looks like the following:
The getTokenFromRequest()
function retrieves the bearer token from the request. Bearer tokens come in the format bearer <JWT>
, hence the retrieved string is split and the JWT string is returned.
The getToken()
function uses the returned token string to parse the JWT, using the private key specified in .env.local.
Using both functions, the ValidateJWT()
function ensures that the incoming request contains a valid token in the request header. This function will be used by the middleware to ensure that only authenticated requests are allowed past the middleware.
The CurrentUser()
function will be used to get the user associated with the provided JWT by retrieving the id key from the parsed JWT and retrieve the corresponding user from the database. It makes reference to the FindUserById()
function which is currently undefined, so go ahead and add that.
In the model/user.go function, add the following code.
In addition to retrieving the user, the entries associated with the user are eagerly loaded - thus populating the Entries
slice in the User
struct.
To create the middleware, create a new folder named middleware in the root of the project and in it, a file named jwtAuth.go. In the newly created file, add the following code.
The JWTAuthMiddleware
returns a Gin HandlerFunc
function. This function expects a context for which it tries to validate the JWT in the header. If it is invalid, an error response is returned. If not, the Next()
function on the context is called. In our case, the controller function for the protected route is called.
Implement a feature to add a new entry
Before declaring the route or controller for this endpoint, add a method to the Entry
struct which will allow you to save a new entry. In the model/entry.go file, add the following code.
Then, update the import statement to match the following code.
Next, in the controller folder, create a new file named entry.go and add the following code to it.
The AddEntry()
function marshals the body of the request into an Entry
struct after which it gets the currently authenticated user from the request header. Next, it sets the associated user ID for the entry and saves it. The saved details are then returned in a JSON response.
Implement a feature to get all entries for the authenticated user
In the controller/entry.go file, add the following function.
This function retrieves the current user and returns the entries associated with them.
Add protected routes
In the main.go file, update the serveApplication()
function to match the following.
Finally, update the import
statement to match the following code.
Your API is now ready for consumption!
Run your application using the go run main.go
command and try adding a few entries to your diary.
Create a new entry
To create an entry, log in as shown in the previous section and then copy the JWT returned and replace the <<JWT>>
placeholder in the following example with it.
You will see output similar to this:
Retrieve the list of entries
Use curl again to retrieve the list of created entries by replacing the <<JWT>>
placeholder with yours
You should see output similar to the following
Conclusion
In this article, you learned how to build an API using Gin. Gin makes the process of request parsing and validation a breeze. It also provided you with the ability to group endpoints and if needed apply different middleware to the different groups in an intuitive manner.
The entire codebase for this tutorial is available on GitHub. Feel free to explore further. Happy coding!
Oluyemi is a tech enthusiast with a background in Telecommunication Engineering. With a keen interest to solve day-to-day problems encountered by users, he ventured into programming and has since directed his problem-solving skills at building software for both web and mobile.
A full-stack software engineer with a passion for sharing knowledge, Oluyemi has published a good number of technical articles and content on several blogs on the internet. Being tech-savvy, his hobbies include trying out new programming languages and frameworks.
- Twitter: https://twitter.com/yemiwebby
- GitHub: https://github.com/yemiwebby
- Website: https://yemiwebby.com.ng/
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.