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

Related Course

Build Your First Node.js Website

Node is a powerful tool to get JavaScript on the server. Use Node to build a great website.

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.

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, Webpack...).
  • 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/modes/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 webpack-validator --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 ones: webpack-merge helps to merge pieces of configurations together while webpack-validator provides better outpus in the terminal.

Other than this, we need a few loaders:

yarn add babel-preset-react babel-loader react-hot-loader 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. 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 validate = require('webpack-validator');

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: { 
        loaders: [
          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 = validate(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$/,
  loaders: ['style', 'css'],
  include: PATHS.css
}
// The file loader
exports.font = {
  test: /\.ttf$/,
  loaders: ['file']
}
// Babel loader
exports.babel = {
  test: /\.jsx?$/,
  exclude: /node_modules/,
  loaders: ['babel']
};

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.

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

and 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

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.