Building the FreeCodeCamp Voting App

Photo by Johannes Plenio on Unsplash - https://unsplash.com/photos/OwMTchwUTNw

In my opinion the voting app challenge on FreeCodeCamp was the first one, that was really hard in the curriculum. I just couldn't do it as easy as all the other challenges. Too much knowledge in too many fields is required. Also, I didn't find any tutorials or examples, that broke this challenge down with up-to-date tools. So I decided to document it my building process.

I will use:

  • MongoDB
  • Express
  • React + Redux
  • Node.js

also known as the "MERN-Stack".

  • Repository on Github is available here.
  • Live version of the result is available here.
  • Learnings and numbers are available here.

gif

📄 Table of contents


>"I fear not the man who has practiced 10 000 kicks once, but I fear the man who has practiced one kick 10 000 times." - Bruce Lee

What this article is about

In this article I will describe the process of building the voting app for the FreeCodeCamp Challenge.

This is not an optimized example for building the application. I am open for feedback of any kind. I am still a beginner and also left some things open. :)


This is not designed as a tutorial! It's simply a documentation I wrote while building the app.


Structure

To break down this application I will divide it into sections for backend, frontend, data visualization and the deployment process. The project will be available as open source code on Github, where you can follow up with commits and the end result.

Development Environment

  • Plain JavaScript
  • Node.js
  • Express (JS framework)
  • MongoDB (Database)
  • Yarn (package management)
  • Visual Studio Code as editor
  • Postman (testing APIs)
  • Robomongo / Robo 3T (working faster with MongoDB)

Packages / Features / Dependencies

General

  • (ES 6 (JS scripting-language specification))
  • eslint with Airbnb extension (for writing higher quality code)
  • nodemon (restarting server when changes occur)
  • Babel (javascript compiler)
  • Webpack (module bundler/builder)
  • dotenv (for configuring environment variables)
  • shortid (random ID generator)

Backend

  • Node.js (JS runtime environment for server-side)
  • MongoDB (document based database)
  • connect-mongo (for storing sessions in MongoDB)
  • body-parser (for parsing incoming requests)
  • express (to make the application run)
  • mongoose (object data modeling to simplify interactions with MongoDB)
  • morgan (HTTP request logger middleware)
  • passport (authentication middleware for Node.js)

Frontend

Visualization

Deployment / DevOps

  • Heroku (PaaS to run applications in the cloud)

(Unit)Testing

  • Not implemented in this app (but normally it should)

First things first

First I will set up my environment:

  • add git for version control
  • create your package management with yarn init
  • add express for a fast web development
  • add the nodemon package for restarting your server on changes
  • add eslint.rc for your eslint configuration
  • add babel and corresponding plugins for compiling JS

As additional integration I'll use:

  • Travis CI (for continuous integration)
  • Code Climate (for Code quality)
  • Assertible (Monitoring Web Services, especially checking deployment - Quality Assurance)

Check out my commit on Github after the setup.

Backend

I will start with the backend, since it's the most difficult in my opinion.

Set up Packages, Middleware and Mongoose

I will use:

  • body-parser for parsing request bodies
  • morgan for logging out HTTP requests
  • compression for compressing response bodies
  • helmet for setting basic security with HTTP headers
  • mongoose object modeling tool for asynchronous database connection

Next

  • create a constants file to set you different environment variables and corresponding settings
  • create a middleware file to pass in middleware to your app and differentiate for environments (especially use bodyparser and morgan packages here)
  • create a database file to set up the mongoDB connection
  • modularize your code and outsource your constants, middleware and database connection for keeping smaller files
  • don't forget to import everything in your app.js file, pass in the middleware function and test your setup with a simple http request

Check out my commit on Github after the setup.

Set up your routes

Revisit the User stories and lay out your routes accordingly

Following the CRUD principle:

As an unauthenticated user I want to

  • see all polls (R)
  • see individual polls (R)
  • vote on available polls (C)

As an authenticated user I want to

  • see/read all polls (R)
  • see individual polls (R)
  • vote on available polls (C)
  • create new polls (C)
  • create new options/votes (C)
  • delete polls (D)

Therefore:

  • set up error handling as middleware
  • set up your router object
  • create your GET, POST, DELETE routes and response with json objects
  • test the set up routes with postman (all should have a status code of 200)
  • connect your routes to your middleware and app.js

Set up Mongoose and your Schemas and connect everything to your routes

When setting up Schemas think about how you want to structure you documents that have to be stored in the database. In this example we need to store user for the authentication process and polls with answers.

For polls we need:

  • the question
  • answers/votes

  • create your mongoose schemas and models

  • connect to mlab to monitor your DB actions better

Be aware that mlab creates those "System Collections" that throw "duplicate key error index dup key: { : null }" error in postman, when creating new polls. Till now I didn't find a solution but deleting all collections allows to start again. ;)


  • use the dotenv package to store your credentials in the environment and add the .env file to .gitignore (if you make your project open source)
  • connect you routes with your mongoose model to handle the documents in MongoDB

BE SURE TO READ THE DOCS when you are stuck. This part is pretty hard when you haven't done a lot with mongoose and MongoDB!

Check out my commit on Github after these steps.

Establish authentication and authorization with Twitter

I want to use the twitter sign-on as an OAuth provider to authenticate. It provides better user experience and I also got to explore OAuth.

OAuth is a standard protocol that allows users to authorize API access to web and desktop or mobile applications. Once access has been granted, the authorized application can utilize the API on behalf of the user.

Of course I found the great article on how to set up the authentication process in Nodejs. After failing to implement it properly in my app (took me a whole day) I decided to dive straight into the documentation of passport!

I love the quote they put up there:

Despite the complexities involved in authentication, code does not have to be complicated.


⭐ Again, as a reminder: Read the Documentation!


  • first register your app on twitter apps and get your settings right (access level and the Callback URL have to be determined!)
  • add passport, passport-twitter and express-session packages to your application
  • create a file defining a passport strategy (twitter)
  • to support login session passport has to serialize and deserialize user
  • pass passport to your passport configuration and connect both passport.initialize and passport.session to your app as middleware (make sure that the express-session is used before!)
  • set up routes for authenticating and the callback

Check out my commit on Github after these steps.

After that, connect the authentication process to your database


⭐ Tip: Use for your callback and testing always http://127.0.0.1:3000/ instead of http://localhost:3000/, since it solves a lot of problems, that might occur using passport-twitter. 😉


  • create a mongoose Schema for your users (to track them in your database)
  • fill the callback function of your passport.js file when implementing the twitter strategy, with filtering your database for the user and creating a new one if a user is not existing
  • use the connect-mongo package to create a mongoStore and store your sessions in MongoDB
  • create a function to test if a user is authenticated and implement it in your desired routes (providing sufficient authorization)
  • the implementation can look like this:

    passport.use(
          new Strategy(constants.TWITTER_STRATEGY, (req, token, tokenSecret, profile, cb) => {
    process.nextTick(() => {
      if (!req.user) {
        User.findOne({ 'twitter.id': profile.id }, (err, user) => {
          if (err) return cb(err);
          if (user) {
            if (!user.twitter.token) {
              user.twitter.token = token;
              user.twitter.username = profile.username;
              user.twitter.displayName = profile.displayName;
              user.save(() => {
                if (err) return cb(err);
                return cb(null, user);
              });
            }
            return cb(null, user);
          }
    
                          // if no user is found create one
          const newUser = new User();
    
          newUser.twitter.id = profile.id;
          newUser.twitter.token = token;
          newUser.twitter.username = profile.username;
          newUser.twitter.displayName = profile.displayName;
    
          newUser.save(() => {
            if (err) return cb(err);
            return cb(null, newUser);
          });
        });
      } else {
                      // when user already exists and is logged in
        const user = req.user;
    
        user.twitter.id = profile.id;
        user.twitter.token = token;
        user.twitter.username = profile.username;
        user.twitter.displayName = profile.displayName;
    
        user.save((err) => {
          if (err) return cb(err);
          return cb(null, user);
        });
      }
    });
    }),
      );
    

After that your authentication and authorization with twitter is done.

Check out my commit on Github after these steps.

Establish local authentication and authorization

The next step is to authenticate locally. There is actually not much to it, since we have already set up the environment.

  • first, update your user schema for local (defining email and password)
  • add the bcrypt-nodejs package for securing passwords
  • add hashing and validating password methods to your Schema
  • then define the routes (this process always clarifies what I actually want to implement)
  • here happened actually a main issue, which I was only able to resolve after many, many hours of searching (here the example from the docs):
    app.get('/login', function(req, res, next) {
    passport.authenticate('local', function(err, user, info) {
      if (err) { return next(err); }
      if (!user) { return res.redirect('/login'); }
      req.logIn(user, function(err) {
        if (err) { return next(err); }
        return res.redirect('/users/' + user.username);
      });
    })(req, res, next);
    });
    

Only this way, passing in the authentication in the callback function, provided enough flexibility for displaying errors, but it's very important to create the session explicitly with logIn()!

  • make sure to differentiate in the routes between signup and login!
  • I installed EJS as view engine to actually being able to test my signup and login properly and efficient
  • create a logout route, that destroys your session

I set so many hours on that Error that I want to display it here again: MongooseError: Cast to ObjectId failed for value "favicon.ico" at path "_id"

I solved it through checking all middleware (there was a major error) and routes. Turned out that setting a route to ('/:pID') is not good when working in development. :D


Check out my commit on Github after the backend setup.

Of course at this point the backend is not perfect. But it's stable enough to go to the next step, the frontend.

Things to do:

  • use validation ( with joi)
  • write unit tests

Frontend

Think before you do!

First of all think about what you want to create. Draw out some sketches to visualize what you want to build. Then consider appropriate frameworks. I will choose React.js and the state management library Redux. The size of this application does NOT necessarily require the use of Redux, however since I want to build it as a single page experience, want to have scalability and like to practice the use of Redux, it's a good fit.

Start planning everything out thinking in React.

Necessary setup with Babel and Webpack

It's important to realize that Babel and Webpack are not too complicated to set it up yourself. There are so many tutorials for both that you can do it easily yourself.

  • add babel for react and es2015 with adding babel-preset-react babel-preset-es2015 to your dev dependencies (to compile JSX into JS and having all es6 features)
  • update your .babelrc file
  • update your webpack config and add the react-hot-loader package

At first I want to structure my frontend without the backend in order to connect the whole frontend with the backend at the end. The reason for that is because for now I don't know how my Redux implementation will look like and therefore progressively connecting to the backend wouldn't be efficient.

Therefore:

  • restructure your current app.js into an own folder
  • create a new app.js as entry point and provide the basic setup code for rendering a simple page
  • the get the setup working install the react-router, webpack-dev-server and of course react and react-dom packages
  • opening a page on the dev-server port should display your react component

➡️ Check out my commit on Github after the new setup ⬅️

Structure components

I sketched everything out on a paper and came to the conclusion to build 14 components:

  • the app component, that hosts everything
  • a header
  • a footer
  • a sidebar
  • a signup/login/social media component
  • a home screen
  • a list of all polls
  • the display of a single poll
  • a component for the poll and it's answers
  • the answers as a list
  • the chart
  • a 404 page

That layout was for the start and should provide an overview. It is very natural to adapt the component structure when the application is evolving.

Design and build components

  • first of all I lay out all components and simply style them with materialize css
  • remember that styling with React is more complicated than styling normal HTML elements. For simplicity reasons I fix everything with inline styling on the component itself

Tip: For 100vh on your main content use this inline style on a div, it fits perfectly into the materialize flexbox:

style={{
  display: 'flex',
  minHeight: '100vh',
  flexDirection: 'column',
}}
  • in the process of building components you will slowly get a feeling on how you need to structure your state management with React and Redux

➡️ Check out my commit on Github after the components are built and styled ⬅️

  • Now we have to set up React Router to get a basic functionality and feeling for the app
  • enable historyApiFallback: true on your webpack dev server to allow proper routing with react router
  • add state and it's management to the components
  • realize that Redux might be a good next step

Here is a list of painful learnings I had to undergo throughout this process:

  • for accessing object properties dynamically use bracket instead of dot notation (used here: javascript answers = answers.concat(this.refs[temp].value);)
  • importing everything as (`import as Polls from './ducks/polls';`) from ducks, because else it's not working
  • I have often read to not use the index of a map function as key value for a component. However, when rendering with onChange and generating an unique key, the input loses focus and is not working properly (const answerList = this.state.answers.map((answer, ind) => { return (<div className="input-field col s10" key={ind}>)
  • when iterating over an array of objects and you want to change properties on an object, you have to return an object, like: return{ answer: answ.answer, votes: 0}; (took me 4 hours to understand 😞)

Adding Redux

The Principles of Redux are:

  • Single source of truth
  • State is read-only
  • Changes are made with pure functions

Keep in mind, that local state doesn't need to take part in Redux when it's state isn't used by other components.

  • add the react-redux and redux packages
  • make use of the ducks structure to manage the redux files better
  • create a store in Redux and wrap your rendering app in a Provider tag from react-redux
  • connect state to your application with connect
  • add the Redux DevTool to debug faster

Now that State is available through Redux, it's time to create the event handlers and render everything accordingly. At least now you should validate your propTypes as well.

Visualization

For displaying the results I chose between:

Quickly skimming all docs and trying a few things out I ended up choosing React-Google-Charts. Google provides many options and the React wrapper makes it easy to implement in a React application.

With the React Wrapper (listed above) this step was super easy and fast.

That's basically it:

const resultChart = (props) => {
  basic = [['Answer', 'Votes']];
  (() => props.poll.answers.map(ans => basic.push([ans.answer, ans.votes])))();
  return (
    <Chart
      chartType="PieChart"
      data={basic}
      options={{
        title: `${props.poll.question}`,
        pieSliceText: 'label',
        slices: {
          1: { offset: 0.1 },
          2: { offset: 0.1 },
          3: { offset: 0.1 },
          4: { offset: 0.1 },
        },
        is3D: true,
        backgroundColor: '#616161',
      }}
      graph_id="PieChart"
      width="100%"
      height="400px"
      legend_toggle
    />
  );
};

Connect Frontend to the Express Backend with React Router

Rendering client- and server-side

As this was my first real full-stack app, connecting the frontend and backend was a mystery to me

I found a pretty good answer to my question on Stackoverflow.

To summarize and quote the answer of Stijn:

With client-side routing, which is what React-Router provides, things are less simple. At first, the client does not have any JS code loaded yet. So the very first request will always be to the server. That will then return a page that contains the needed script tags to load React and React Router etc. Only when those scripts have loaded does phase 2 start. In phase 2, when the user clicks on the 'About us' navigation link for example, the URL is changed locally only to http://example.com/about (made possible by the History API), but no request to the server is made. Instead, React Router does it's thing on the client side, determines which React view to render and renders it.

  • Combining server- and client-side routing

If you want the http://example.com/about URL to work on both the server- and the client-side, you need to set up routes for it on both the server- and the client side.

And this is where your choices begin. Solutions range from bypassing the problem altogether, via a catch-all route that returns the bootstrap HTML, to the full-on isomorphic approach where both the server and the client run the same JS code.

  • Bypassing the problem altogether: Hash History

With Hash History i.s.o Browser History, your URL for the about page would look something like this: http://example.com/#/about The part after the hash (#) symbol is not sent to the server. So the server only sees http://example.com/ and sends the index page as expected. React-Router will pick up the #/about part and show the correct page.

Downsides: 'ugly' URLs Server-side rendering is not possible with this approach. As far as SEO is concerned, your website consists of a single page with hardly any content on it.

  • Catch-all

With this approach you do use Browser History, but just set up a catch-all on the server that sends /* to index.html, effectively giving you much the same situation as with Hash History. You do have clean URLs however and you could improve upon this scheme later without having to invalidate all your user's favorites.

Downsides: More complex to set up Still no good SEO

  • Hybrid

In the hybrid approach you expand upon the catch-all scenario by adding specific scripts for specific routes. You could make some simple PHP scripts to return the most important pages of your site with content included, so Googlebot can at least see what's on your page.

Downsides: Even more complex to set up Only good SEO for those routes you give the special treatment Duplicating code for rendering content on server and client

  • Isomorphic

What if we use Node JS as our server so we can run the same JS code on both ends? Now, we have all our routes defined in a single react-router config and we don't need to duplicate our rendering code. This is 'the holy grail' so to speak. The server sends the exact same markup as we would end up with if the page transition had happened on the client. This solution is optimal in terms of SEO.

Downsides: Server must (be able to) run JS. I've experimented with Java i.c.w. Nashorn but it's not working for me. In practice it mostly means you must use a Node JS based server. Many tricky environmental issues (using window on server-side etc) Steep learning curve

In the end I was going with the catch-all solution: See in my routes.js file.

//routes.js
router.get('/*', (req, res) => {
  const options = {
    root: `${__dirname}/../../public/`,
    dotfiles: 'deny',
  };
  res.sendFile('index.html', options);
});

It was easy and fast to implement and covers the basic problems.

Serving everything all together

To understand that, the best way is to take a look at my package.json file.

The scripts say:

"scripts": {
        "start": "node src/serverSide/server.js",
        "serve": "babel-node src/serverSideES6/server.js",
        "dev": "npm-run-all --parallel dev:*",
        "dev:client": "webpack-dev-server --hot",
        "dev:server": "nodemon src/serverSide/server.js",
        "build": "npm-run-all --parallel build:*",
        "build:client": "webpack --progress",
        "build:server": "babel src/serverSideES6 --out-dir src/serverSide"
    },

The build script builds the files on the client and server side.

  • It compiles all my ES6 node.js code into ES5 (so Heroku can read it as well)
  • Webpack starts the bundeling and transpiling of the client side. (ES6 to ES5, JSX to JavaScript, etc. - see webpack config )

The dev script serves everything in a development environment and (hot) reloading, so that everything is as fast and smooth as possible, when changing the codebase.

The start script actually starts the backend server, which also consumes the built and bundled frontend HTML/CSS/JS, presenting the whole application.

Deployment

For deploying the app, Heroku once again had proven to be the way to go.

Using the Heroku CLI, the Heroku logs command helps a lot. I always had trouble setting up my app on the platform, but after simply solving all the errors the logs show, it becomes really easy.

Always important:

  • Be aware that devDependencies are not installed
  • Use the adequate buildpack (in this case for NodeJS)
  • Have a start script or define one in your Procfile
  • Be sure to push the right branch from the right repository

Conclusion

As you can see my documentation for this article gets worse and worse with the progress of the app. This is due to the fact, that I got completely overwhelmed with Redux. I did other projects on the side and wasn't able to keep track.

But don't worry! I tried to name my commits as clear as possible. So, you can traverse all commits for details in my Repository. See Commits here.

If you have questions feel free to ask :)

  • Repository on Github is available here.
  • Live version of the result is available here.
  • Learnings and numbers are available here.

gif


Many, many thanks to Edo Rivai, who gave very valuable tips along the way. :)


Thanks for reading my article! Feel free to leave any feedback!

results matching ""

    No results matching ""