With virtually unlimited food options in NYC, I have always had a hard time deciding what to eat. So as my quest to learn more about web development, I thought it would be a good idea to create a web app that allows my friends to suggest and vote for what I should eat for dinner.
The web app is created using Node.js with Express Framework. User authentication is also implemented using passport local-strategy. MongoDB is used to store results, votes, and logins information. Chart.js is used to display the results in a donut shaped graph.
There are 13 route handlers in total, 8 for
POST requests and 5 for authentication.
The index.js file is created for set ups, such as installing Handlebar as the view engine and importing the authentication module.In order to support login sessions, I have to serialize and deserialize user instances. I have two middleware which helped me do this:
On all the route handlers, I also pass in a special function called
requireAuth that is passed as a second argument to every function on the page. The third function is the callback function that gets executed after
requireAuth passes the test. If the user has not logged in and tries to go on a page other than index or login, they will get redirected to the login page.
User type in the suggestion box and presses the find button. Behind the scene: req.body.recipeName is grabbed from the post form, converted to the applicable format, and is appended to the url. A Request function with the URL is called to retrieve food from the API. API returns food titles and images, which are then stored in the req.session.foodArray. The page then redirects to the get handler of the same page
router.get(‘/search’, requireAuth, function(req,res)
on this page, user gets to suggest food options that i should include in the vote page. Two food options with highest suggestions are displayed on the vote page. This route handler checks whether food is found or not using the values saved in session from the post handler (if req.session.found == true). If found, it saves the information in context objects (ie. variables) which are then passed into the handlebar templating engine to display.
router.get(‘/vote’, requireAuth, function(req,res))
Gets called when user visits vote page. Query the votes collections in the database to retrieve all the vote count in decescending order, the top two are displayed side by side for user to vote.
router. post(‘/vote’, function(req,res))
Gets called when user presses image under donut (ajax request). Inside this function, i retrieve the req.body.name and req.body.foodTitle from the Ajax. Then, query the Profile collections in the db by username to find out whether they have voted today or not (person.voted = true or false). If the person has not voted, I go into the votes collections to increment one point to that food, then the document gets resaved. Person.voted then gets marked true, so they cannot vote again.
Page then redirects back to
Returns documents from votes collection sorted in descending order.
I import mongoose and passport-local-mongoose
Passport-local-mongoose is a plugin that simplifies building username and password login with Passport. First, I added Passport-local-mongoose into my schema
(UserSchema.plugin(passportLocalMongoose) It adds a username, hash and salt field to store the username, the hashed password and the salt value. Then connect to the database and defines our schema.
The Sshema maps to a MongoDB collections and defines the shape of documents within the collection. I have four schemas:
- Users schema, to store username and password - Local strategy by default, expects to find credentials in parameters named username and password. So it finds username in our database collection “users”.
- Radar schema, which stores the count for my radar chart
- Profile schema, which stores username, lastname, email, and users’ corresponding votes, and a boolean of whether they have voted yet
- Vote schema = used to count vote for pie chart
To use our schema definition, we have to convert it into a model. Models are constructors compiled from our schema definitions and instances of these models represent documents which can be saved and retrieved
In this class, i import in mongoose, passport, LocalStrategy and User model and performed necessary set up with passport by letting it know that I am using local strategy:
Let passport know how to serialize and deserialize user - this has to do with sessions. SerializeUser determines which data of user object should be stored in the session. Deserialize helps match the key is matched with the in memory array/database or any data source.
The frontend was developed entirely in HTML/CSS and Vanilla JS.
Search.js (corresponds to search routes in the backend)
There’s a hidden foodArrayTemp context variable inside a div in search.hbs that stores the length of array images, use for display purposes. All images gets registered a click event. When a click occurs, use ajax to go to send a request to search/vote with the title of the image and the source
Vote.js (corresponds to the vote routes in the backend)
The page page initially shows a donut shaped graph with a plus button below in. The plus button has an event ‘click’ registered to it. Whenever the button is pressed, the anonymous callback function gets called. This function keeps count how many times the plus button has been clicked. The first time it is clicked, the two images of food that corresponds to the donut graph gets displayed. It was initially hidden using CSS (display: none). The two images that appears below the donut graph (after the first click) have handleClick event registered to them. When the user click (event occurs), ajax is used to send the id of the image to the server, so the server side can tally up count.
More speficially, the XMLhttpRequest object is created, configured and then makes an asynchronous request to the server. The new values are used to update the graph (another call using ajax to the server to get the values. Returned as JSON, then we use that result to display)
I used chart.js, a client side library, to render the donut() function and create my donut graph
I implemented in
register_validate.js page. No fields can be black, username and password need to have length greater than 5 and email in the email box and confirm email box need to be the same.
I integrated an external api to allow users to search and suggest food. The API is called food2fork. The response from API is returned as a JSON.
Custom API endpoints
I created a set of services that I exposed via HTTP to allow access to my Express app and data in my database, for example,
http://api.adifoodapp. This made sense since my application is client-side heavy and most of my data is stored in the database. I employed RESTful framework and treated all data points on my web app as resources. For instance, /foods returns a list of the foods. Below are some of the use cases of my custom API endpoints.
When user visits the vote page (donut shaped graph), my program makes a GET request to the API to get list of vote objects in JSON and displays top two votes. The exchange of information is done through AJAX. On the suggestions page, user can search for the food they want to suggest for me. User enters his/her search query into the search bar, which fires a request to the external API. The external API returns a json array, and if the user selects any of them, the response is from the frontend back as a POST request to my API.