Hi, Welcome back!
In today's post we are going to implement the place order functionality, the admin account, and a sample version of our restaurant's menu. At the end of this post, a customer should be able to successfully place an order.
Project steps
- 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
Let's begin with creating the admin account. The admin account will have access to functionalities such as accepting orders placed by customers, blacklisting/whitelisting users, creating staff accounts, and creating menus among other things. Since this account will have access to sensitive information, we cannot just create an endpoint for it. We need to create a script that will create this account by skipping the signup process.
We also need a way to differentiate the users of our app by their roles namely customer, admin, and staff.
Customer refers to a user who will download our app from the Google play store to place orders.
Admin refers to the owner or manager of Gourmet restaurant. He/she should be able to create menus of dishes, create and remove staff accounts, and manage orders.
Staff refers to an employee in the restaurant who will be created by the admin or manager. In case the manager is not there, the staff account should also be able to manage orders as the staff on duty.
Let's begin by creating the roles. We will need to modify a little bit the sign up process we created in the previous posts to make sure a user who signs up is identified as a customer by default.
Create a new branch called
ft-place-order
off our main branch.Create a
src/utils/roles.js
file and paste the following code inside:
- Update
Valid signup should return 201
test case intest/authentication.js
to check if a signed-up user is a customer like this:
- Update
Valid login should return 200
test case intest/authentication_login.js
to check if a logged-in user is a customer like this:
- Update
src/database/models/User.js
and add the role field like this:
- Create a new migration to add the role field on the User model by running the following command in your terminal
npx sequelize-cli migration:generate --name add-role-to-user
- Update the newly created
src/database/migrations/**-add-role-to-user.js
file to look like this:
- Update
src/controllers/authentication.js
to add the role of customer on sign up like this:
Now run your tests and they should all pass. If you were to inspect the user created in the database, you should see that the user has a role of customer. This means that every user who signs up will be given a role of customer automatically. Awesome!
Admin account
Let's now create our admin account starting with the tests.
- Create a
tests/authentication_admin.js
file and paste the following inside:
In the test case above we are checking if given the correct admin credentials, the admin can login successfully.
At this point this test case should fail because we haven't yet created the admin account.
Let's now create a script that will create the admin account and make the test case above pass.
- Create a
src/database/scripts/adminScript.js
file and paste the following code inside:
In the code above we created a function called createAdmin
that will first hash our plain-text admin password then call the findOrCreate
method on the User model. findOrCreate
method as the name suggests will first try to find if a record exists in the database, if it is found it will return its instance, if it doesn't exist then it creates a new record. we used this method because we want to be running our script automatically after each production build. If we were to use the create
method, it would create the record the first time but would throw an error the second time as we would be trying to create a record that already exists.
Lastly we call createAdmin
function and export it so that when we will execute this file, it will call this createAdmin function. Cool!
- Update
.env
file and add theADMIN_PHONE
andADMIN_PASSWORD
environment variables. Let's now create a command to run our script. - Update
package.json
and include the script to create the admin account inpretest
andheroku-postbuild
commands. This way our admin account will be created before running our tests and after the production build respectively.
Now run your tests again and they should all pass. Great!
Place order
At this point we need to start thinking about what kind of information we should show to customers and what information to expect when they place orders.
We are going to create 4 additional models namely: Menu, Item, Order, and Contents.
Menu will refer to a category such as Breakfast, Lunch, Dinner, Drinks, etc.
Item will refer to the actual dish or drink such as Cheese Burger, Coke Diet, Orange Juice, etc.
Order will refer to the orders placed by customers and will contain details such as the total amount, order status, user Id, etc.
Lastly, Contents will contain each item details for a specific order.
Regarding model relations or associations, we need to link Order model with User model by adding a foreign key of userId to the Order model. We also need to link Order model with Contents model by adding a foreign key of orderId to the Contents model. And lastly we need to link Menu model with Item model by adding a foreign key of MenuId to the Item model.
Great! Now that we have an idea of the structure of our new models and associations, let's start implementing the place order feature.
As always, we will begin by writing our tests.
- Create a
tests/orders.test.js
file and paste the following code:
- Update
src/utils/messages.js
and add the new messages:
- Create a new model called Menu with the following command
npx sequelize-cli model:generate --name Menu --attributes name:string
- Create another model called Item with
npx sequelize-cli model:generate --name Item --attributes name:string,description:string,cost:decimal,size:string,image:string
- Create a new migration to add the menuId field to our Item model by running
npx sequelize-cli migration:generate --name add-menuId-to-item
- Update the newly created
src/database/migrations/**-add-menuId-to-item.js
migration to look like the following:
- Update
src/database/models/item.js
to add the relation/association betweenItem
andMenu
:
- Update
src/database/models/menu.js
to add the One-to-many association betweenItem
andMenu
:
- Create another model called Order with
npx sequelize-cli model:generate --name Order --attributes total:decimal,status:string,paymentId:string
- Create another model called Contents with
npx sequelize-cli model:generate --name Contents --attributes itemId:integer,itemName:string,cost:decimal,quantity:integer
- Create a new migration to add the orderId field to our Contents model by running
npx sequelize-cli migration:generate --name add-orderId-to-contents
- Update the newly created
src/database/migrations/**-add-orderId-to-contents.js
migration to look like the following:
- Create a new migration to add the userId field to our Order model by running
npx sequelize-cli migration:generate --name add-userId-to-order
- Update the newly created
src/database/migrations/**-add-userId-to-order.js
migration to look like the following:
- Update
src/database/models/order.js
to add the association betweenOrder
andContents
and betweenOrder
andUser
:
- Update
src/database/models/user.js
to add the one-to-many association betweenUser
andOrder
:
Let us now create our validations for place order.
- Create a
src/validations/orders.js
file and paste the following inside:
Do not forget to export the createErrorMessages
function from src/validations/authentication.js
- Create a new
src/middlewares/orders.js
file and paste the following inside:
Before we create the controller and route for placing orders, let's think about how a customer will place an order.
In the Gourmet mobile app, the customer will be presented with a menu that has a list of items to choose from. When the customer taps on the add button, the item's id, name, cost, and quantity will be added to their cart. Subsequent addition of the same item will increase the item's quantity and cost. On checkout we will use the cart's items to calculate the total amount of the order and when the customer pays for the order, we will include the paymentId for reference.
The following image shows a sample of the request's body which will be send to the server when a customer places an order:
The order is for one double cheese burger and two diet cokes.
The items inside the contents array is what we will save in our Contents model. And if we remember, we defined an association that will ensure an item has an orderId. We need a way to add an orderId to each item in the order's contents.
Let us create a function that will take our contents array and an orderId then add that orderId to each item inside the contents array.
- Update
src/helpers/misc.js
and add theparseOrderContents
function:
- Update
src/services/services.js
and add thesaveManyRows
function:
The bulkCreate
method unlike create
, allows us to create multiple records at the same time.
We are now ready to create the controller and use these functions we created above.
- Create a new
src/controllers/orders.js
file and paste the following:
In the placeOrder
method we destructure the request's body to reveal total, contents, and paymentId. Then, we create our order Object will will have the total, paymentId, a default status of pending, and the userId. The userId's value is handed to us by the authentication middleware function checkUserToken
through req.userData.id
. we then save our order record then use the returned record's id to add it to each item in the contents array by calling the parseOrderContents
helper function. We then call saveManyRows
function to save each item in the Contents model.
Let us now create the place order route and use the controller we just created.
- Create a
src/routes/ordersRoutes.js
file and paste the following inside:
- Update a
src/routes/index.js
file and add the orders router:
Now run your tests and they should all pass.
And if you check the records in Orders and Contents tables in the database, you should see that our data is saved.
Gourmet Menu
One way to create the menu of our restaurant would be to create admin endpoints for creating, viewing, updating, and deleting the menu but for the sake of keeping things simple we will not do that. Instead, we are going to create organized data of our menu that we will insert directly in the database (seeds). When we are done with this series you can implement the above endpoints for managing the menu as the admin on your own since we will have covered all the concepts to do it.
Cool, let us create our seeds.
We are going to create a seed for creating 3 menus, namely Breakfast, Lunch/Dinner, and Drinks. We are going to create another seed for creating items in each menu.
- Run
npx sequelize-cli seed:generate --name menus
command in your project root - Update the newly created
src/database/seeders/**-menus.js
to look like this:
- Run
npx sequelize-cli seed:generate --name items
command in your project root - Update the newly created
src/database/seeders/**-items.js
to look like this:
Now let us update the scripts section in package.json
to create a command that we will use to create the seeds.
- Update scripts in
package.json
to add theseed
command and to include the seed command on thepretest
command:
Now we can add our new environments variables ADMIN_PHONE
and ADMIN_PASSWORD
to Travis and Heroku environments then commit our changes to GitHub, open a PR and merge it as we have done before.
And that's it for today!
In the next post we are going to look at how to fetch the list of orders and how to fetch a specific order's details. We will do this from the perspective of both the admin and the customer.
Thank you for reading!
See you in the next one!
The code in this post can be found here
Top comments (0)