Today we are going to start implementing authentication for the backend of our app "Gourmet".
In this post we will implement the sign up and verify endpoints.
- Backend - Project Setup
- Backend - Authentication
- Backend - Place order
- Backend - View orders list and view a specific order
- Backend - Update order
- Frontend - Authentication
- Frontend - Place order, view orders list, and view order details
For users to register on our app, we will require their first name, last name, phone number, address, and password. If the values provided are valid, we will send a OTP (One-Time-Password/Passcode) via SMS to their phone number which they can use to confirm their registration.
Following the TDD approach, we are first going to write our tests then we will implement validations, middlewares, routes, controllers and finally we will configure Sequelize to be able to save data in the database.
Our signup task is going to be made up of 2 sub-tasks, one for signing up and another for confirming the user's registration. Let's begin with the first one.
- Ensure you are on your
mainbranch then run
git pull origin mainto make sure your local branch is up to date with the remote branch
git checkout -b ft-authenticationto create a new branch for today's task
As we build our API there are things that we will need often and to avoid repeating ourselves it's good practice to structure our code for re-usability. That being said, create a new directory called
src. Create two new files
messages.js inside utils.
src/utils/statusCodes.jsand paste the following inside:
These are all the HTTP status codes that our API is going to use.
src/utils/messages.jsand paste the following inside:
This file will contain all the response messages that our API will be returning to the client apps on top of status codes and other data.
Now let's write our tests.
- Create a file called
authentication.test.jsin tests directory and paste the following inside:
In this file, we import our express app along with our assertion libraries (chai and chai-http) and our status codes and messages we defined above. We then define a base URL for our authentication routes and we initialize chai to be able to test http apps. Learn more about chai here.
We then define a
SIGN UP suite to hold our 5 test cases. In the first test case, we are testing for when a user submits an empty request (tries to signup without providing any data), what response he/she should get. Notice the use of one of our status code and messages we defined earlier.
In the second test case, we are testing for when a user submits an invalid phone number. Notice the missing
+ sign on the phone number. The phone number must be in a valid international format since we will use it to send the OTP.
In the third test case, we are testing for when a user submits any other value apart from the required ones (firstName, lastName, phoneNumber, address, and password). Notice the email property.
In the fourth test case, we are testing for when a user submits valid values conforming to validation rules that we will define next. In this case, we expect a successful response that contains a status code of
account created message, a JWT token that the user can use to authenticate for subsequent requests, and a data object containing details of the user. Notice how we expect the user's account status to be false since he/she has not yet verified it. Finally, we retrieve the token in a variable called
userToken that we will use in other test cases when verifying the user's account.
In the fifth test case, we are testing for when a user tries to signup more than once using the same phone number.
At this point if you run the tests they will fail apart from
Server initialization test which is exactly what we want.
Next up is to write code to make our tests pass.
Create the following directories
Create a new file called
authentication.jsinside validations directory and paste the following code inside:
We will use this file for authentication validation. In the code above, we start by importing a library called
We created a function
createErrorMessages to help us in - you guessed it - create validation error messages. The function takes
error type and
empty, min, max, and pattern custom messages as parameters and depending on the type of the error we assign a custom message. This function returns an object of error types and their messages.
We use the second function
signup to define a schema of values that we want users to submit when signing up. Notice the use of Regular Expressions to enforce validation rules. If you're familiar with RegEx, it's pretty straight forward as our use-case is not too complex.
Finally we call Joi's built-in method
validate on our schema and pass in a data object i.e. req.body and some options to return all errors at once and to prevent other values not defined in our schema. Check out Joi API for more details and advanced use-cases.
In case of errors, our signup validation function will return an
errors object containing a
details property. This details property is an array containing all the error messages. We need a way to extract and use the contents of this details property.
- Create a
helpersdirectory and paste the following code:
In this file we define 3 functions:
We will use
errorResponseto return success and error responses respectively.
returnErrorMessageschecks to see if the parameter
errorsis present then destructure its details property. We then format each message in our details array to make it more readable and then we use
errorResponsedefined above to return the result of these formatted messages.
If errors is null it means our validations are passing and we continue with the execution of the request. Think of
returnErrorMessages as a middleware.
Let's now use this
- Create a file
authentication.jsin middlewares directory and paste the following code:
Notice the use of
returnErrorMessages by giving it the error object returned by our signup validation function as a parameter.
Before we implement our controller, let's update
src/helpers/misc.js with the following:
Notice the additional functions:
We will use
generateToken to generate a JWT token based on the data passed in. Update your
.env file and include the
We will use
generateOTP to generate a random six digit code that we will send to a user.
generateHashedPassword will be used to take a plain-text password, encrypt it and return a hash string which we will store in our database. For security reasons, You should never store plain-text passwords in your database.
Okay, let's implement our controller.
- Create a
controllersdirectory and paste the following:
Our controller is where a request that has passed all validations and middlewares will end its journey. This where we will implement saving data in the database and sending OTP to users before returning a response to the user.
Let's implement our routes to see how it looks so far.
Create two files
index.jsin routes directory.
Paste the following inside
If you remember, in our tests we defined our base URL as
/api/auth/. This means we will be able to define
/api/auth/logout routes respectively.
Let's implement the parent
/api/auth/ route handler.
- Paste the following inside
Our endpoint is almost complete. We just need to let our express app know about it.
src/server.jsto look look like this:
- Run your tests again. This time around, some of them are passing.
Great Job if you managed to reach here! 🎉
Let's now implement sending OTP. When we finish, we will setup Sequelize in order to persist data in the database.
Starting with OTP implementation, we are going to use Twilio. Click here to create a Twilio trial account. After creating your account you should be given some credit you can use to buy numbers and send SMS in trial mode.
Trial accounts have some limitations, namely you cannot send SMS to unverified numbers. So in order to test this functionality, there are 2 options.
You can upgrade your account.
You can verify numbers you intend to use. Just remember to upgrade your account before going into production to allow everyone to signup.
We are going to use option 2 for now.
Login to your Twilio account. Click on the
#sign that says
Phone numberson the left panel. On the phone numbers page, click
Buy numberbutton and proceed to search for a number you want. Make sure to tick the SMS checkbox.
Verified Caller IDsthen click on the red plus button to add and verify a number. Make sure to provide a valid phone number that you have access to because Twilio will send a OTP to verify it.
Once done, head back to VS Code and add the following keys in your
Let's now install the Twilio library.
Open your terminal in your project's root dir and run
yarn add twilio
twilioConfig.jsfile in config directory and paste the following:
In this file we initialize a twilio client instance that we can use throughout our app to send SMS.
Let's now use this client in our code.
src/heplers/misc.jsto look like the following:
sendOTP function will take a phone number and a message and it will take care of sending our SMS. Let's now use this function in our controller.
Now run your tests again and you should get a OTP delivered to the number you specified in
TWILIO_CUSTOMER_NUMBER env variable.
Great! Let's now implement Sequelize and save data in our database.
Since we already installed all the required sequelize library and plugins, let's start using them.
- In your terminal, navigate to
npx sequelize-cli init. This command will create the following directories and files:
The models directory will contain our models. Think of models as tables in a database.
The migrations directory will contain migrations which are modifications made to our models. We use migrations to change the structure our 'tables'. We can do things like add/remove/rename columns, add/change constraints on columns, etc.
Note that each time we modify the structure of our models we need to run migrations for those changes to take effect. More on this later.
seeders directory will contain data that we want to inject in the database. Use-case: Imagine you want to test the
login functionality. since we have already implemented the signup tests and we know it works well, we can use the seeders to insert in the database valid records of users thus skipping the signup and verify tests which will make our tests run faster. We will use seeders later in this series.
config.json file will contain credentials to connect to our database. We will need to modify this file and make it dynamic to avoid exposing our database credentials. Let's do it right away.
Replace the contents inside with:
- Update your
.envfile and add the keys for development and test like below:
Notice the different database names for development and test.
Note that for now we don't need to provide credentials for production in our
.env file. The production credentials will be provided to us by heroku when we "provision" (setup) a production database.
src/database/models/index.jswith the following:
This file will allow us to import our models dynamically by doing something like:
import models from '../database/models' then destructure models to retrieve each model in models directory. This file also creates and exports a sequelize instance that we will be using to interact with the database.
Cool! Let's now use Sequelize to create our first model - User.
- In your terminal run
npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,phoneNumber:string,address:string
This command will create 2 new files:
user.js (our user model) and
**-create-user.js (our first migration) inside models and migrations directories respectively.
package.jsonto include commands to create and drop the database as well as run the migrations like:
Notice we didn't include the
pretest command on the
test command since our CI service does this automatically for each build.
If we were to run our migrations right now, our database would be created with just the 4 columns defined when creating our model above.
Let's update our model and add more columns and create a new migration to apply those changes.
- In your terminal run
npx sequelize-cli migration:generate --name add-password-otp-and-status-to-userto create a new migration that will apply the new columns we added to our model.
Tip: As migrations can become many as our app scales, it's good practice to name each migration with what it does. By looking at the name of the new migration, we would know that it add password, otp, and status columns to user model.
- Replace the contents of
src/database/migrations/**-add-password-otp-and-status-to-user.jswith the following:
Check out this link to learn more about creating models and migrations.
If we were to run our 2 migrations now, all the 7 columns would be added to our user table.
One of the things I like about Sequelize is its nice API that allows to interact with the database without writting SQL queries like "INSERT INTO tableName VALUES(....". Oh! This API allows also to write those queries in case you wish to use them. Nice, right!
We are almost done!
- Create a
services.jsfile inside services directory and paste the following:
We will be using this file to create functions that will use Sequelize API to CRUD the database.
saveData function receives a model name and obj as parameters then calls the Sequelize built-in method
create on the model and returns the data saved in the database.
Similarly we use
findByCondition function to find if a record exists in a table given a conditon. Check out this link to learn more about these built-in model methods.
As you might have guessed, we will use
findByCondition to check if a user exists in the database and
saveData to save the user.
Okay, let's update
src/middlewares/authentication.js to look like the following:
We need to run this function after the validations and before the controller.
src/routes/authRoutes.jsto look like:
- Lastly, let's update our controller to use the
saveDatafunction we defined in our services. Update
src/controllers/authentication.jsto look like the following:
In the code above we added the
saveData and lodash's
pick methods to choose which properties should be in the userData object returned in the response and token respectively.
That's it! Our signup endpoint is done!
Now if you run your tests, they should all pass! Nice, right!
In case you run into a timeout error, make sure to update your script's test command in
package.json by adding a timeout flag like below:
This allows to extend the default Mocha's timeout of 2 seconds for each test case to 8 seconds which will give enough time to our asynchronous functions to finish executing.
After users have registered and we have sent the OTP, we need a way to verify their phone number thus confirming their account registration.
We are going to implement verify endpoints, the first one will be to check if the OTP submitted by the user is correct. The second will be to re-send the OTP to the user in case there has been an issue and the user didn't receive the first OTP.
tests/authentication.jsand add the following:
In the code above, we have added test cases for the
SIGNUPtest suite, update
Valid signup should return 201test case like this:
src/utils/messages.jsand add the following messages:
src/validations/authentication.jsand add the following:
src/middlewares/authentication.jsand add the following:
validateVerifyOTPmiddleware will help us to use
verifyOTPfunction to validate the
otpsubmitted by the user.
checkUserTokenmiddleware will help us to check if a request contains the Authorization header then will try to decode the token to check if the who made the request exists in our database then returns the user's data or an error. This is how we will be able to link users with their requests.
checkOTPmiddleware will help us to check if the otp submitted by the user is the same as the one we sent to them via SMS.
src/services/services.jsand add the following:
src/controllers/authentication.jsand add the following:
src/routes/authRoutes.jsand add the following:
Now all our tests should be passing. Let's now update our travis config file and package.json file before we commit our changes to Github.
.travis.ymlfile to look like this:
We added the
services option and
before_script command which will tell Travis to create a postgres database called
gourmet_test before running our tests.
package.jsonto include a
As the name suggests, this command will run after each build. You can use it to run scripts that you want to execute before your app is deployed. Here we are using it to run our migrations automatically.
The last step is to make sure our CI service and production environments are up to date.
- Login on Travis then open our
gourmet-apirepo then click on settings to add environments variables. Make sure to add each env variable with its value.
- Head back to VS Code and commit our changes to github. Open a PR on github and wait for Travis to finish building. Both the branch and PR should show a successful build.
Before we merge this PR, let's create a production database on heroku.
On your app page on heroku, click on
Resourcestab then in the
Add-onssearch field type
Heroku Postgresand in the confirmation modal click
Submit order form. You should see a confirmation that the add-on
heroku-postgresqlhas been added. Check out the docs for more info.
Heroku Postgresto open it in a new tab then click on
Settingstab then click the
You should see the credentials of our database. When you provision a database on heroku like this, it adds the
DATABASE_URL env variable automatically on your app.
Let's now add the database credentials as env variables. Alternatively, you could use the
DATABASE_URL variable in the
On your main app's settings tab, click on
Reveal config varsbutton and add each credential key and its corresponding value from the database we've just created.
Don't forget our Twilio credentials and JWT_SECRET_KEY
Now it's time to merge our PR which will trigger a production build on heroku.
- Head over to github and merge the PR we created earlier.
Travis should build our merge commit successfully then Heroku should build successfully as well then run our migrations.
Now you could copy the URL of your app from heroku and test the endpoints we implemented with POSTMAN or Insomnia and everything should go smoothly. Check out the links to their docs below.
Today's task was huge because we covered a lot of things. But we have laid the foundation for Sequelize, validations, and middlewares. The next endpoints are going to be fairly straightforward.
In the next post, we will implement the
They are both great at designing and testing APIs and you can even do things like creating and hosting your API documentation.
Note: The endpoints we implemented in this post are a bit naive. For example, we are not checking if a user's account is verified before verifying it. We should also limit requests to the endpoints that uses external resources since the billing of these resources can become a lot. Check out this library to learn how to limit the number of requests. About the other issue of checking if a user's account is verified before verifying it, we can achieve this by using a simple middleware function.
Thanks for reading and or following along!
See you in the next one!
You can find the code in this post here