Free eBook: Build Your First Node App

Retrogames Library with Node, React, and Redux 1: Server API and React Frontend

Samuele Zaza
👁️ 27,171 views
💬 comments

2017/03/29 Update: Fixed the versions of react-router and react-hot-loader. 2017/03/20 Update: Webpack 2 configuration.

Introduction

The Javascript stacks, MEAN and MERN on top, are definitely the hottest tech in the web development community nowadays. In fact all the Javascript ecosystem is continuosly expanding with updates and new packages on a daily basis. Finding the direction may be seen as an overwhelming tasks sometimes, especially for beginners, but luckily communites like Scotch.io strive to provide the right direction with always up-to-date tutorials and articles.

Table of Contents

    In this tutorial we are going to write an archive for retrogames by using Javascript in both backend and frontend:

    We will combine express built on top of Node.js with React and Redux to demonstrate how easy is to write a single page app.

    To persist data we are using Mongo.db which integrates pretty well with Node.js thanks to its Mongoose ODM.

    In addition, to upload pictures with no hassle we are gonna integrate Filestack in our app which returns a CDN url for us:

    Filestack hosts our pictures saving the burden to make sure our machines has the space to store them as well as avoid us all the uploading related security.

    Last but not least, the free account is enough to implement all the functionalities we need for the app.

    The App

    Our archive allows users to view, create and delete games of the past that made history. I am a huge fan of games like Super Mario Bros., Street Fighter, Sonic, King of Fighters so I really enjoyed writing this app, I hope you too!

    I separated the tutorials in different parts to make it easier to digest all the information. In this very first tutorial we are going to setup the Node.js, connect it to Mongo.db, write the games API and test it with postman. Then, we will write it using React and serve it with webpack-dev-server. In the second part of the tutorial we are going to include Redux state container and Redux-saga to perform asynchronous HTTP requests to our real server. Finally, I may add a third part, a bonus one, to show some simple authentication and improve the UI.

    I suggest to follow the tutorial and build the app step-by-step, however you can also find the app on github:

    Once you cloned/forked the repo, just checkout to tutorial/part1 branch.

    git checkout tutoral/part1

    Prerequisites

    • In general it would be better to have basic knowledege of the technologies discussed throughout the tutorial (Node.js, React, Mongo.db, Webpack2...).
    • ES6 syntax, guys we are at the end of 2016 so let's start using it!
    • Yarn, the new package manager is out and I fell in love with it. I especially like the intrinsic reliability guaranteed by yarn.lock, this makes our install works on different systems. For the record, Scotch.io released a very cool tutorial for Yarn.

    Table of Contents

    Folder Structure

     --app
     ----models
     ------game.js
     ----routes
     ------game.js
     --client
     ----dist
     ------css
     --------style.css
     ------fonts
     --------PressStart2p.ttf
     ------index.html
     ------bundle.js
     ----src
     ------components
     --------About.jsx
     --------Archive.jsx
     --------Contact.jsx
     --------Form.jsx
     --------Game.jsx
     --------GamesListManager.jsx
     --------Home.jsx
     --------index.js
     --------Modal.jsx
     --------Welcome.jsx
     ------containers
     --------AddGameContainer.jsx
     --------GamesContainer.jsx
     ------index.js
     ------routes.js
     --.babelrc
     --package.json
     --server.js
     --webpack-loaders.js
     --webpack-paths.js
     --webpack.config.js
     --yarn.lock

    Notice the two files /client/src/components/index.js and /client/src/containers/index.js:

    I am using them to export all the components and containers in a single file to write the import more easily. Take a look at this example:

    import c1 from './c1.jsx';
    import c2 from './c2.jsx';
    import c3 from './c3.jsx';
    
    export { c1, c2, c3 };

    And then we can include them within a single line:

    import { c1, c2, c3 } from '../components';

    The Server

    Routes Table

    So we are going to write our server API! let's define the routes first:

    Routes Table
    GET /games Get all the games.
    POST /games Create a game.
    GET /games/:id Get a single game.
    DELETE /games/:id Delete a game.

    Nothing exotic up here, we just defined some common routes to edit our archive. Before we start creating the project, you should have already asked yourself where are we going to save the data... Are we gonna persist it? Yes, we are gonna use Mongoose ODM to persist data on a Mongo database.

    Setup

    In the newly created project folder we first initialize the package.json:

    yarn init

    So now let's start adding our dependencies. For the server part of the project we just need a few, Express (definitely), Mongoose, Body-parser, Morgan and Babel transpiler to use ES6 syntax throughout our app.

    NB: Babel is great but not suggested for production as it slows down the server while transpiling from ES6 to ES5.

    We need to run two commands as babel is a dev-dependency:

    yarn add express mongoose morgan body-parser
    yarn add babel-core babel-cli babel-preset-es2015 --dev

    Now we are able to run our server with babel-node server.js. however It's good practice to create a specific command inside the package.json under "scripts". So, open the package.json file and add

    "scripts": {
        "api": "babel-node server.js"
      }

    So now we can just run

    yarn api

    At the end your package.json file should be similar to mine:

    {
      "name": "tutorial",
      "version": "1.0.0",
      "main": "server.js",
      "author": "Sam",
      "license": "MIT",
      "scripts": {
        "api": "babel-node server.js"
      },
      "dependencies": {
        "body-parser": "^1.15.2",
        "express": "^4.14.0",
        "mongoose": "^4.7.2",
        "morgan": "^1.7.0"
      },
      "devDependencies": {
        "babel-cli": "^6.18.0",
        "babel-core": "^6.20.0",
        "babel-preset-es2015": "^6.18.0"
      }
    }

    In addition to this, to effectively take advantage of Babel transpiler we have to create a file .babelrc in the root folder, then paste the following code:

    {
      "presets": ["es2015"]
    }

    NB: According to the documentation you can specify your config within the package.json file too.

    Server.js

    At this point it's really time to code! We need a server file where we configure our express server, connect the body-parser and morgan middlewares as well as mongoose, write our routes and so on. Create the server.js file in the root folder and past the following code:

    import express from 'express';
    import bodyParser from 'body-parser';
    import mongoose from 'mongoose';
    import morgan from 'morgan';
    
    // We gotta import our models and routes
    import Game from './app/models/game';
    import { getGames, getGame, postGame, deleteGame } from './app/routes/game';
    
    const app = express(); // Our express server!
    const port = process.env.PORT || 8080;
    
    // DB connection through Mongoose
    const options = {
      server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } },
      replset: { socketOptions: { keepAlive: 1, connectTimeoutMS : 30000 } }
    }; // Just a bunch of options for the db connection
    mongoose.Promise = global.Promise;
    // Don't forget to substitute it with your connection string
    mongoose.connect('YOUR_MONGO_CONNECTION', options);
    
    const db = mongoose.connection;
    db.on('error', console.error.bind(console, 'connection error:'));
    
    // Body parser and Morgan middleware
    app.use(bodyParser.urlencoded({ extended: true}));
    app.use(bodyParser.json());
    app.use(morgan('dev'));
    
    // We tell express where to find static assets
    app.use(express.static(__dirname + '/client/dist'));
    
    // Enable CORS so that we can make HTTP request from webpack-dev-server
    app.use((req, res, next) => {
      res.header("Access-Control-Allow-Origin", "*");
      res.header('Access-Control-Allow-Methods', 'GET,POST,DELETE');
      res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
      next();
    });
    
    // API routes
    app.route('/games')
      // create a game
      .post(postGame)
      // get all the games
      .get(getGames);
    app.route('/games/:id')
      // get a single game
      .get(getGame)
      // delete a single game
      .delete(deleteGame);
    
    // ...For all the other requests just sends back the Homepage
    app.route("*").get((req, res) => {
      res.sendFile('client/dist/index.html', { root: __dirname });
    });
    
    app.listen(port);
    
    console.log(`listening on port ${port}`);

    The code is pretty straightforward:

    • We connect to our Mongo.db database through Mongoose.
    • We set Body-parser and Morgan middlewares for parsing request bodies and output useful logs in the console.
    • We enable CORS to allow HTTP requests from the webpack-dev-server on the same machine, this makes our development easier later on, it won't be necessary once we serve our client from node.
    • ...And then our routes with a specific callback function.

    Before writing the functions in /app/routes/game.js let's define our Game model!

    Game Model

    The Game model is very simple, we need the game name, a description, the year it was published and a picture. Plus, postDate to track the time it was created.

    Paste the following code in /app/models/game.js:

    // Dependencies
    import mongoose from 'mongoose';
    const Schema = mongoose.Schema;
    
    // Our schema definition
    const gameSchema = new Schema(
        {
            name: String,
            year: Number,
            description: String,
            picture: String,
            postDate : { type: Date, default: Date.now } // Timestamp
    
        }
    );
    
    // We export the schema to use it anywhere else
    export default mongoose.model('Game', gameSchema);

    Notice I did not mark any field as required:

    Although I recommend to do it in your personal projects, for the purpose of the tutorial I wanted to be as concise as possible.

    Next, let's create the callback functions to handle the requests and responses and we can test our server.

    Routes Callbacks

    Create the game.js file in /client/app/routes and paste the following code:

    // We import our game schema
    import Game from '../models/game';
    
    // Get all the games sorted by postDate
    const getGames = (req, res) => {
        // Query the db, if no errors send all the games to the client
        Game.find(null, null, { sort: { postDate : 1 } }, (err, games) => {
            if (err) {
                res.send(err);
            }
            res.json(games); // Games sent as json
        });
    }
    
    // Get a single game filtered by ID
    const getGame = (req, res) => {
        const { id } = req.params;
        // Query the db for a single game, if no errors send it to the client
        Game.findById(id, (err, game) => {
            if (err) {
                res.send(err);
            }
            res.json(game); // Game sent as json
        });
    }
    
    // Get the body data and create a new Game
    const postGame = (req, res) => {
      // We assign the game info to a empty game and send a message back if no errors
      let game = Object.assign(new Game(), req.body);
      // ...Then we save it into the db
      game.save(err => {
        if (err) {
          res.send(err);
        }
        res.json({ message: 'game created' }); // A simple JSON answer to inform the client
      });
    };
    
    // Delete a game by the given ID
    const deleteGame = (req, res) => {
    // We remove the game by the given id and send a message back if no errors
      Game.remove(
        { _id: req.params.id },
        err => {
          if (err) {
            res.send(err);
          }
          res.json({ message: 'successfully deleted' }); // A simple JSON answer to inform the client
        }
      );
    };
    
    // We export our functions to be used in the server routes
    export { getGames, getGame, postGame, deleteGame };
    

    The four functions take care of the user requests: They all gonna communicate with the database through the Game model and return a defined response to the client.

    Our server is complete, let's give it a try!

    Postman Simple Testing

    For this simple test we first create a new game and doublecheck whether it gets really added to the archive. We consequently gonna delete it and make sure it really disappeared from the archive! By doing so we test all the routes we previously defined.

    I personally use Postman browser extension to achieve this result but feel free to use your favorite tools.

    Let's start our server with

    yarn api

    /GET games

    My database is already populated with a few games, here is the result:

    /POST games

    Let's try to add a game with random information since we are going to delete it soon!

    As counterproof let's make another GET request to /games and see whether it was really added to the archive.

    Cool it was really added!

    /GET games/:id

    Let's try to filter the games by id, trivial test but we want to cover all the endpoints.

    Seems to be working smoothly.

    /DELETE games/:id

    Now, given the id, let's try to delete it:

    Finally, let's doublecheck if it was really deleted:

    Awesome the server is ready, time to work on the client-side!

    The Client

    All of you familiar with React already know we need a few steps before really coding the client. While there are some solutions like react-create-app which aims to avoid the initial configuration hassle, I still prefer to manually install all the packages which also has an educational value for the tutorial. For anyone interested in digging into Webpack, I suggest to take a look at survive.js. It's a valuable resource for learning Webpack and React, plus the e-books can be read online for free.

    Let's start installing some packages now:

    yarn add webpack webpack-dev-server webpack-merge --dev

    We are obviously installing webpack to help us create the bundle along with webpack-dev-server for serving us the client during development.

    Perhaps not everyone is familiar with the latter one: webpack-merge helps to merge pieces of configurations together.

    Other than this, we need a few loaders:

    yarn add babel-preset-react babel-loader react-hot-loader@next style-loader css-loader file-loader --dev

    These helps in severals tasks like transpiling (guess which one!), include the css and the fonts in our bundle as well as avoid to refresh the page while changing the code in React. As there are continuous updates on these packages, make sure that react-hot-loader is up-to-date because the syntax to include it in .babelrc changed, I am currently using the version 3.0.0-beta.6 thanks to code>@next. In case you receive a webpack error stating that it cannot find load the plugin, just run

    yarn add react-hot-loader@3.0.0-beta.6 --dev

    To organize our code for better readability, webpack-config.js will require some data from other files.

    Let's create webpack-paths.js and paste the following code:

    "use strict";
    
    const path = require('path');
    // We define some paths to be used throughout the webpack config
    module.exports = {
      src: path.join(__dirname, 'client/src'),
      dist: path.join(__dirname, 'client/dist'),
      css: path.join(__dirname, 'client/dist/css')
    };

    As you can see we want to export some paths we are using inside the webpack configuration. Let's move on and create the webpack.config.js file and paste the following code:

    "use strict";
    
    const merge = require('webpack-merge');
    
    const PATHS = require('./webpack-paths');
    const loaders = require('./webpack-loaders');
    
    const common = {
        entry: { // The entry file is index.js in /client/src
            app: PATHS.src 
        },
        output: { // The output defines where the bundle output gets created
            path: PATHS.dist,
            filename: 'bundle.js'
        },
        module: { 
            rules: [
              loaders.babel, // Transpiler
              loaders.css, // Our bundle will contain the css 
              loaders.font, // Load fonts
            ]
        },
        resolve: {
            extensions: ['.js', '.jsx'] // the extensions to resolve
        }
    };
    
    let config;
    // The switch defines the different configuration as development requires webpack-dev-server
    switch(process.env.NODE_ENV) {
        case 'build':
            config = merge(
                common,
                { devtool: 'source-map' } // SourceMaps on separate file
             );
            break;
        case 'development':
            config = merge(
                common,
                { devtool: 'eval-source-map' }, // Default value
                loaders.devServer({
                    host: process.env.host,
                    port: 3000
                })
            );
    }
    
    // We export the config
    module.exports = config;
    
    • I created a common configuration, common, where I define the common properties for both development (using webpack-dev-server) and build (the bundle is served by Node.js).
    • As you may have noticed, the entry point, the output, the loaders and resolve are common among the configurations.
    • The switch discriminates between the two configurations, the main difference is that in development we run webpack-dev-server and get better debug informations inside our sourceMap thanks to eval-source-map. Finally, the merged configuration goes through the validate function and gets exported.

    The loaders are imported from another file, webpack-loaders.js. Let's create it and paste the following code:

    "use strict";
    
    const webpack = require('webpack');
    const PATHS = require('./webpack-paths');
    
    exports.devServer = function(options) {
        return {
            devServer:{
                historyApiFallback: true,
                hot: true, // Enable hot module
                inline: true,
                stats: 'errors-only',
                host: options.host, // http://localhost
                port: options.port, // 3000
                contentBase: './client/dist',
            },
            // Enable multi-pass compilation for enhanced performance
            plugins: [ // Hot module
                new webpack.HotModuleReplacementPlugin({
                    multistep: true
                })
            ]
        };
    }
    // the css loader
    exports.css = {
      test: /\.css$/,
      use: ['style-loader', 'css-loader'],
      include: PATHS.css
    }
    // The file loader
    exports.font = {
      test: /\.ttf$/,
      use: ['file-loader']
    }
    // Babel loader
    exports.babel = {
      test: /\.jsx?$/,
      exclude: /node_modules/,
      use: ['babel-loader']
    };
    

    The file just exports the loaders and webpack-dev-server with the hot-reload plugin.

    We also have to edit .babelrc:

    {
      "presets": [
        "es2015",
        "react"
      ],
      "plugins": [
        "react-hot-loader/babel"
      ]
    }
    

    We added the preset for react and react-hot-loader plugin.

    Finally, we also gotta edit package.json to include new scripts commands:

        "start": "NODE_ENV=development webpack-dev-server",
        "build": "NODE_ENV=build webpack"

    We set the NODE_ENV variable to switch between the two configurations we defined before in webpack.config.js.

    NB: If you are a windows user you may add a & to concatenate the commands:

        "start": "NODE_ENV=development & webpack-dev-server",
        "build": "NODE_ENV=build & webpack"

    Assets

    First, let's create the index file served by the server which includes all our assets, bundle.js included. In /client/dist create a file index.html and paste the following code:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Retrogames Archive</title>
        <link rel="icon" href="https://cdn.filestackcontent.com/S0zeyXxRem6pL6tHq9pz">
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
      </head>
      <body>
        <div id="content"></div>
        <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
        <script src="https://api.filestackapi.com/filestack.js"></script>
        <script src="./bundle.js"></script>
      </body>
    </html>
    • Notice the div with id content, this is where ReactDOM renders our components
    • In this tutorial we won't use packages like react-bootsrap but just include the CDN url for both the css and javascript sources.
    • Finally we included Filestack!

    CSS

    Regarding the css, I have just customized two templates that you can find on the boostrap website and included some cool fonts I found. Just take copy them from my project.

    Filestack

    Before diggin' into the React components it's better to setup a Filestack account, so on their website just click on the try it free button and follow the instructions:

    Once in the developer portal you are immediately proposed to add their snippet in the project which is great because it would save us time but we want to customize the uploader right? So just skip it and instead grab the API key (click on "New application" in case) as we are needing it later!

    React

    It's finally time to write our components! Our archive will welcome user with a nice UI:

    We provide the user some simple informations about us as well as the app in three different views. Once they click on Browse! they are redirected to the real archive list where they can add new games as well as view the details and delete them:

    Our routes configuration is composed by two main routes with their children routes:

    1. The Homepage is a route with three children routes in charge to render the components related to home, features and contacts links.
    2. The Games route handles the children routes to list the games and add a new one.

    Notice I named them Homepage and Games to help you guys understand the structure but in the code they actually don't carry any name themselves.

    Let's install a few packages:

    yarn add react react-dom react-router@3.0.0

    NB: I am using the version 3 of react-router in the tutorial.

    We can start by creating a file index.js in /client/src and past the following code:

    import '../dist/css/style.css';
    import React from 'react';
    import ReactDOM from 'react-dom';
    import Routes from './routes';
    
    // Don't forget to add your API key
    filepicker.setKey("YOUR_API_KEY");
    
    // Our views are rendered inside the #content div
    ReactDOM.render(
      Routes,
      document.getElementById('content')
    );
    

    We included react-dom so we can render the routes in the div element with id content! Also, this is where you should set Filestack's API key.

    Routes

    Well, our routes configuration is in the same folder so create routes.js in /client/src and paste the following code:

    import React from 'react';
    import { Router, Route, hashHistory, IndexRoute } from 'react-router';
    import { Home, Welcome, About, Contact } from './components';
    
    // Use hashHistory for easier development
    const routes = (
      <Router history={hashHistory}>
        <Route path="/" component={Home}>
          <IndexRoute component={Welcome} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Route>
      </Router>
    );
    
    export default routes;
    

    We imported a few components from react-router and defined our first URL paths structure:

    Url Component
    / Home -> Welcome
    /about Home -> About
    /contact Home -> Contact

    We are using hashHistory so we don't need any server configuration in case of page refresh. Moreover, notice the four components we are going to write are stateless, they are just presentational components that are not going to touch the state so they are very easy to write. Let's do it!

    NB: React 15.3.0 introduced PureComponent to replace pure-render-mixin which does not work with ES6 classes so we can actually extends it for our stateless components.

    Home.jsx

    This component is basically the skeleton for the others, in /client/src/components create a file Home.jsx and paste the following code:

    import React, { PureComponent } from 'react';
    import { Link } from 'react-router';
    
    export default class Home extends PureComponent {
      active (path) {
        // Returns active when the path is equal to the current location
        if (this.props.location.pathname === path) {
          return 'active';
        }
      }
      render () {
        return (
          <div className="main">
            <div className="site-wrapper">
              <div className="site-wrapper-inner">
                <div className="cover-container">
                  <div className="masthead clearfix">
                    <div className="inner">
                      <nav>
                        <img className="header-logo" src="https://cdn.filestackcontent.com/nLnmrZQaRpeythR4ezUo"/>
                        <ul className="nav masthead-nav">
                          <li className={this.active('/')}><Link to="/">Home</Link></li>
                          <li className={this.active('/about')}><Link to="/about">About</Link></li>
                          <li className={this.active('/contact')}><Link to="/contact">Contact</Link></li>
                        </ul>
                      </nav>
                    </div>
                  </div>
                  {this.props.children}
                </div>
              </div>
            </div>
          </div>
        );
      }
    }
    
    • {this.props.children} is where we render the three children components.
    • We need to change the class to the li element when clicked, this is easy to achieve through the active() function which checks the pathname against the path parameter we pass. If we had to change the class of the Link component we wouldn't need any function but unfortunately the theme I grabbed from Bootstrap applies "active" on the li element instead.

    Welcome.jsx

    This component welcomes our user and provides the link to navigate to the games archive, create the file Welcome.jsx in /client/src/component and paste the following code:

    import React, { PureComponent } from 'react';
    import { Link } from 'react-router';
    
    export default class Welcome extends PureComponent {
      render () {
        return (
          <div className="inner cover">
            <h1 className="cover-heading">Welcome</h1>
            <p className="lead">Click on browse to start your journey into the wiki of games that made history.</p>
            <p className="lead">
              <Link className="btn btn-lg" to="/games">Browse!</Link>
            </p>
          </div>
        );
      }
    }
    

    It doesn't require any explanation, just a welcome message and the browse! link to view the games archive.

    About.jsx

    Create About.jsx in /client/src/components and paste the following code:

    import React, { PureComponent } from 'react';
    
    export default class About extends PureComponent {
      render () {
        return (
          <div className="inner cover">
            <h1 className="cover-heading">Javascript Everywhere</h1>
            <p className="lead">This archive is made with Node.js and React. The two communicate through async HTTP requests handled by Redux-saga... Yes we love Redux here!</p>
          </div>
        );
      }
    }
    

    Even simplier, just a simple explanation on how we wrote the app!

    Contact.jsx

    Create Contact.jsx in /client/src/components and paste the following code:

    import React, { PureComponent } from 'react';
    
    export default class About extends PureComponent {
      render () {
        return (
          <div className="inner cover">
            <h1 className="cover-heading">Any Questions?</h1>
            <p className="lead">Don't hesitate to contact me: zaza.samuele@gmail.com</p>
          </div>
        );
      }
    }
    

    Feel free to change the text!

    /components/index.js

    We need this file to export all the components, let's create it and paste the following code:

    import About from './About';
    import Contact from './Contact';
    import Home from './Home';
    import Welcome from './Welcome';
    
    // We export all the components at once
    export { About, Contact, Home, Welcome };

    At this point we can already see the client in action, just run

    yarn start

    and open http://localhost:3000 in the browser. Though we haven't completed the app we can already see the welcome page as well as the other links on the top-right of the page.

    Let's now work on the interactive pages.

    Update Routes.js

    We already discussed about the url, we need to update our route configuration: We have other two views, one for the games list and one which is basically the form users upload games.

    Open routes.js and replace the code with the following:

    import React from 'react';
    import { Router, Route, hashHistory, IndexRoute } from 'react-router';
    import { AddGameContainer, GamesContainer } from './containers';
    import { Home, Archive, Welcome, About, Contact } from './components';
    
    const routes = (
      <Router history={hashHistory}>
        <Route path="/" component={Home}>
          <IndexRoute component={Welcome} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Route>
        <Route path="/games" component={Archive}>
          <IndexRoute component={GamesContainer} />
          <Route path="add" component={AddGameContainer} />
        </Route>
      </Router>
    );
    
    export default routes;
    

    Let's take a look at the definitive configuration:

    Url Component
    / Home -> Welcome
    /about Home -> About
    /contact Home -> Contact
    /games Archive -> GamesContainer
    /games/add Archive -> AddGameContainer

    /containers/index.js

    As we did for the components we create an index file to export the containers. Create it in /client/src/containers and paste the following code:

    import AddGameContainer from './AddGameContainer';
    import GamesContainer from './GamesContainer';
    
    // We export all the containers at once
    export {
      AddGameContainer,
      GamesContainer
    };
    

    Archive.jsx

    as Home component, it just provides the layour and render the children. Create Archive.jsx in /client/src/components and paste the following code:

    import React, { PureComponent } from 'react';
    import { Link } from 'react-router';
    
    export default class Layout extends PureComponent {
      render () {
        return (
          <div className="view">
            <nav className="navbar navbar-inverse">
              <div className="container">
                <div className="navbar-header">
                  <button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span className="sr-only">Toggle navigation</span>
                    <span className="icon-bar" />
                    <span className="icon-bar" />
                    <span className="icon-bar" />
                  </button>
                  <Link className="navbar-brand" to="/">
                    <img src="https://cdn.filestackcontent.com/nLnmrZQaRpeythR4ezUo" className="header-logo" />
                  </Link>
                </div>
              </div>
            </nav>
            {this.props.children}
            <footer className="text-center">
              <p>© 2016 Samuele Zaza</p>
            </footer>
          </div>
        );
      }
    }
    

    GamesContainer.jsx

    This is the container for the archive list where we are writing all the functions to manipulate the state. Let's first create the file and then comment it! Create GamesContainer.jsx in /client/src/containers and paste the following code:

    import React, { Component } from 'react';
    import { Modal, GamesListManager } from '../components';
    
    export default class GamesContainer extends Component {
      constructor (props) {
        super(props);
        // The initial state
        this.state = { games: [], selectedGame: {}, searchBar: '' };
        // Bind the functions to this (context) 
        this.toggleModal = this.toggleModal.bind(this);
        this.deleteGame = this.deleteGame.bind(this);
        this.setSearchBar = this.setSearchBar.bind(this);
      }
    
      // Once the component mounted it fetches the data from the server
      componentDidMount () {
        this.getGames();
      }
    
      toggleModal (index) {
        this.setState({ selectedGame: this.state.games[index] });
        // Since we included bootstrap we can show our modal through its syntax
        $('#game-modal').modal();
      }
    
      getGames () {
        fetch('http://localhost:8080/games', {
          headers: new Headers({
            'Content-Type': 'application/json'
          })
        })
        .then(response => response.json()) // The json response to object literal
        .then(data => this.setState({ games: data }));
      }
    
      deleteGame (id) {
        fetch(`http://localhost:8080/games/${id}`, {
          headers: new Headers({
            'Content-Type': 'application/json',
          }),
          method: 'DELETE',
        })
        .then(response => response.json())
        .then(response => {
          // The game is also removed from the state thanks to the filter function
          this.setState({ games: this.state.games.filter(game => game._id !== id) }); 
          console.log(response.message);
        });
      }
    
      setSearchBar (event) { 
        // Super still filters super mario thanks to toLowerCase
        this.setState({ searchBar: event.target.value.toLowerCase() });
      }
    
      render () {
        const { games, selectedGame, searchBar } = this.state;
        return (
          <div>
            <Modal game={selectedGame} />
            <GamesListManager
              games={games}
              searchBar={searchBar}
              setSearchBar={this.setSearchBar}
              toggleModal={this.toggleModal}
              deleteGame={this.deleteGame}
            />
          </div>
        );
      }
    }
    
    • In the constructor we defined an inital state with an empty array of games that will be soon populated. selectedGame is the specific game to show in the bootstrap modal and searchBar is the search keyword to filter the games.
    • In componentDidMount() we call game() which make an HTTP call for the games and set them into the state. Notice the new fetch() function.
    • toggleModal() is passed as props to the GamesListManager component to set the current game in the state and toggle the modal.
    • setSearchBar() updates the state with the current keyword. toLowerCase() guarantees our search is not case-sensitive.
    • Finally, we render Modal and GamesListManager components.

    NB: At the present time thinking about refactoring isn't necessary as our code will substantially change with Redux. In fact we should be just focusing on making things work now!

    This is another stateless component, just create it in /client/src/components and paste the following code:

    import React, { PureComponent } from 'react';
    
    export default class Modal extends PureComponent {
      render () {
        const { _id, img, name, description, year, picture } = this.props.game;
        return(
          <div className="modal fade" id="game-modal" tabIndex="-1" role="dialog" aria-labelledby="myModalLabel">
            <div className="modal-dialog" role="document">
              <div className="modal-content">
                <div className="modal-header">
                  <button type="button" className="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">×</span>
                  </button>
                  <h4 className="modal-title" id="myModalLabel">{`${name} (${year})`}</h4>
                </div>
                <div className="modal-body">
                  <div>
                    <img src={picture} className="img-responsive img-big" />
                  </div>
                  <hr />
                  <p>{description}</p>
                </div>
                <div className="modal-footer">
                  <button type="button" className="btn btn-warning" data-dismiss="modal">Close</button>
                </div>
              </div>
            </div>
          </div>
        );
      }
    }
    

    There is nothing special here, we simply shows the game information in a fancy modal.

    GamesListManager.jsx

    Though stateless it is a more meaningful component. Create it in /client/src/components and paste the following code:

    import React, { PureComponent } from 'react';
    import { Link } from 'react-router';
    import Game from './Game';
    
    export default class GamesListManager extends PureComponent {
      render () {
        const { games, searchBar, setSearchBar, toggleModal, deleteGame } = this.props;
        return (
    
          <div className="container scrollable">
            <div className="row text-left">
              <Link to="/games/add" className="btn btn-danger">Add a new Game!</Link>
            </div>
            <div className="row">
              <input
                type="search" placeholder="Search by Name" className="form-control search-bar" onKeyUp={setSearchBar} />
            </div>
            <div className="row">
            {
        // A Game is only shown if its name contains the string from the searchBar
              games
                .filter(game => game.name.toLowerCase().includes(searchBar))
                .map((game, i) => {
                  return (
                    <Game  {...game}
                      key={game._id}
                      i={i}
                      toggleModal={toggleModal}
                      deleteGame={deleteGame}
                    />
                  );
                })
            }
            </div>
            <hr />
          </div>
    
        );
      }
    }
    
    • We could actually move out the search bar and create another component for it, however we won't use it anywhere else so there is no reusability involved.
    • In the render function we map the games to a Game component and we do some basic filtering: We make sure the game name contains the keyword from the search bar

    Game.jsx

    The game container is pretty immediate as well, create it into /client/src/components and paste the following code:

    import React, { PureComponent } from 'react';
    import { Link } from 'react-router';
    
    export default class Game extends PureComponent {
      render () {
        const { _id, i, name, description, picture, toggleModal, deleteGame } = this.props;
        return (
          <div className="col-md-4">
            <div className="thumbnail">
              <div className="thumbnail-frame">
                <img src={picture} alt="..." className="img-responsive thumbnail-pic" />
              </div>
              <div className="caption">
                <h5>{name}</h5>
                <p className="description-thumbnail">{`${description.substring(0, 150)}...`}</p>
                <div className="btn-group" role="group" aria-label="...">
                  <button className="btn btn-success" role="button" onClick={() => toggleModal(i)}>View</button>
                  <button className="btn btn-danger" role="button" onClick={() => deleteGame(_id)}>Delete</button>
                </div>
              </div>
            </div>
          </div>
        );
      }
    }
    

    The buttons triggers the functions we wrote in GamesContainer: These were passed as props from GamesContainer to GamesListManager and finally to Game.

    AddGameContainer.jsx

    The container is gonna render a form where our users can create games for the archive. Create the AddGameContainer.jsx in /client/src/containers and paste the following code:

    import React, { Component } from 'react';
    import { hashHistory } from 'react-router';
    import { Form } from '../components';
    
    export default class AddGameContainer extends Component {
      constructor (props) {
        super(props);
        // Initial state
        this.state = { newGame: {}};
        // Bind this (context) to the functions to be passed down to the children components
        this.submit = this.submit.bind(this);
        this.uploadPicture = this.uploadPicture.bind(this);
        this.setGame = this.setGame.bind(this);
      }
      submit () {
        // We create the newGame object to be posted to the server
        const newGame = Object.assign({}, { picture: $('#picture').attr('src') }, this.state.newGame);
        fetch('http://localhost:8080/games', {
          headers: new Headers({
            'Content-Type': 'application/json'
          }),
          method: 'POST',
          body: JSON.stringify(newGame)
        })
        .then(response => response.json())
        .then(data => {
          console.log(data.message);
          // We go back to the games list view
          hashHistory.push('/games');
        });
      }
      uploadPicture () {
        filepicker.pick (
          {
            mimetype: 'image/*', // Cannot upload other files but images
            container: 'modal',
            services: ['COMPUTER', 'FACEBOOK', 'INSTAGRAM', 'URL', 'IMGUR', 'PICASA'],
            openTo: 'COMPUTER' // First choice to upload files from
          },
          function (Blob) {
            console.log(JSON.stringify(Blob));
            $('#picture').attr('src', Blob.url);
          },
          function (FPError) {
            console.log(FPError.toString());
          }
        );
      }
      // We make sure to keep the state up-to-date to the latest input values
      setGame () {
        const newGame = {
          name: document.getElementById('name').value,
          description: document.getElementById('description').value,
          year: document.getElementById('year').value,
          picture: $('#picture').attr('src')
        };
        this.setState({ newGame });
      }
      render () {
        return <Form submit={this.submit} uploadPicture={this.uploadPicture} setGame={this.setGame} />
      }
    }
    
    • In the constructor we define an empty new game in the state. Thanks to setGame() we create its values whenever the user edit one of the inputs from the form (you will see it later).
    • submit() sends the new game to the server through POST request.

    What about the upload() function?

    Inside we run the pick() function from Filestack which prompts a modal a picture. If you take a look a the documentation for the function, we may have noticed that the first parameter is an option object for customizing our uploader: For example, if you don't want users to upload non-image files, well Filestack allows you to restrict the mimetype! I love the fact I can create in few minutes my uploader with custom options to fit my needs. For the current tutorial, I defined the option objects as following:

    • The mimetype equal to image/* limits the upload to image files.
    • We can choose to show either a modal or dialog uploading interfaces, I personally prefer the modal but you guys could try to customize it the way you like!
    • What are the sources to upload from? Not just the user's device but there are plenty of other choices. In our case we define an array of all the allowed sources.
    • Finally, among these choices above, we choose the computer as the default one.

    Finally, there are two functions, one for onSuccess and one for onError. Notice the Blob object parameter on onSuccess: This is returned by Filestack, it contains a bunch of information among which the image url!

    Let me show you an example:

    {
    "url":"https://cdn.filestackcontent.com/CLGctDtSZiFbm4AKYTSX",
    "filename":"background.jpg",
    "mimetype":"image/jpeg",
    "size":609038,
    "id":1,
    "key":"w53urmDSga10ndZsOiE5_background.jpg",
    "container":"filestack-website-uploads",
    "client":"computer",
    "isWriteable":true
    }

    For more information don't hesitate to take a look at the documentation, the guys made a big effort to write very clear instructions.

    Form.jsx

    Our last component is Form, let's create it in /client/src/components (used to it yet?!) and paste the following code:

    import React, { PureComponent } from 'react';
    import { Link } from 'react-router';
    
    export default class Form extends PureComponent {
      render () {
        return (
          <div className="row scrollable">
        <div className="col-md-offset-2 col-md-8">
            <div className="text-left">
            <Link to="/games" className="btn btn-info">Back</Link>
            </div>
            <div className="panel panel-default">
                <div className="panel-heading">
                    <h2 className="panel-title text-center">
                    Add a Game!
                    </h2>
                </div>
                <div className="panel-body">
                    <form name="product-form" action="" onSubmit={() => this.props.submit()} noValidate>
                    <div className="form-group text-left">
                          <label htmlFor="caption">Name</label>
                          <input id="name" type="text" className="form-control" placeholder="Enter the title" onChange={() => this.props.setGame()} />
                    </div>
                    <div className="form-group text-left">
                          <label htmlFor="description">Description</label>
                          <textarea id="description" type="text" className="form-control" placeholder="Enter the description" rows="5" onChange={() => this.props.setGame()} ></textarea>
                    </div>
                    <div className="form-group text-left">
                        <label htmlFor="price">Year</label>
                        <input id="year" type="number" className="form-control" placeholder="Enter the year" onChange={() => this.props.setGame()} />
                    </div>
                    <div className="form-group text-left">
                        <label htmlFor="picture">Picture</label>
                           <div className="text-center dropup">
                                <button id="button-upload" type="button" className="btn btn-danger" onClick={() => this.props.uploadPicture()}>
                                  Upload <span className="caret" />
                                </button>
                            </div>
                    </div>
                    <div className="form-group text-center">
                        <img id="picture" className="img-responsive img-upload" />
                    </div>
                    <button type="submit" className="btn btn-submit btn-block">Submit</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
        );
      }
    }

    Pretty straightforward! Whenever a users edit any form input, the onChange function update the state.

    Update /components/index.js

    The components were all created but we have to update /client/src/components/index.js to export them all. Replace its code with the following:

    import About from './About';
    import Contact from './Contact';
    import Form from './Form';
    import Game from './Game';
    import GamesListManager from './GamesListManager';
    import Home from './Home';
    import Archive from './Archive';
    import Modal from './Modal';
    import Welcome from './Welcome';
    
    // We export all the components at once
    export {
      About,
      Contact,
      Form,
      Game,
      GamesListManager,
      Home,
      Archive,
      Modal,
      Welcome
    };

    And now we can run the app! We first start the api server:

    yarn api

    And if you haven't, webpack-dev-server:

    yarn start

    This should work smoothly however we are still not serving the bundle from Node.js. We need to run another command:

    yarn build

    This will create the bundle.js in the /dist folder... Now connect to http://localhost:8080 and the client is served from our real server instead.

    Congratulations for finishing the first part of the tutorial!

    Conclusions

    In this first part of the tutorial we went through the initial project configuration.

    We first built the backend of the app, an API server with Node.js and Express. We also made a preliminary test with postman to doublecheck that everything works as expected. For a real-world app this is not exhaustive, if you a curious about testing, take a look at my previous post on testing Node.js with Mocha and Chai here on Scotch!

    Then we spent some time configuring Webpack to include javascript and css inside the same bundle file. Eventually we wrote React components to see the app in action.

    In the next tutorial we are going to include Redux and related packages, we will see how easily we can manage the state if we separate it into a container. We will include lots of new packages, not just redux but redux-saga, redux-form... We will work with immutable data structure as well.

    Stay tuned!

    Samuele Zaza

    7 posts

    I am a full-stack web developer working for Taroko Software as front-end web developer and Filestack Tech Evangelist. When not coding I may be spotted in a gym lifting or planning to conquer the world LOL.