Creating A Rest Service With Node, React, And Webpack

By: Stephen Patrick | 19 Oct 2016 | Category: Node Rest

Creating A Rest Service With Nodejs

Nodejs provides mechanisms for building Rest Services. One advantage of Node is that it provides an API for creating and configuring a Web Server out of the box. In this tutorial we will build a simple Rest Service using Node. We will also use Webpack to package our code.

Project Setup

We will start by creating the project folder.

mkdir nodejs-webpack

Next the project must be initialized.

npm init

The init command creates a file named package.json. This file contains configuration data for the the node project. It contains information such as dependencies, commands for executing different activities etc. As part of this step, the process will ask for input. However, at this point there is no need to enter specific information, press enter and accept the defaults at each each step.

Webpack

Webpack is a packaging tool that simplifies developing JavaScript applications. Webpack can be used to bundle web resources, such as JavaScript, CSS, etc. Webpack can also execute different activities. One such activity, is transforming JSX to JavaScript code, another is modularizing JavaScript code. Webpack can be installed with Node Package Manager.

npm install webpack --save-dev

Babel

Babel is a tool that provides a number of features that aid the development process. It allows us to write modern ES6 JavaScript code. It also provides a number of transformers, for transforming code written in a different language / version, syntax etc. A transformer will take a piece of code and transform it into a form suitable for display in older browsers. For example, it can transform JSX code to JavaScript, ES6 code to ES5 etc.

npm install babel-loader babel-core --save-dev

Above we install as a development dependency, babel and the babel loader for use with Webpack. With the loader configured, the next step is to configure Babel. Babel relies on a number of plug-ins. To make it easier to configure Babel, we can use Babel presets. A preset can consist of a number of plug-ins and you are free to create your own plug-ins. We will use NPM to install the presets for react and ES2015.

npm install babel-preset-es2015 babel-preset-react --save-dev

To configure Babel a file named .babelrc is required. This file is added to the project root.

{
   "presets": [
       "es2015",
       "react",
   ]
}

Express

Next Express will be installed. Express is a node module that provides an API for building and configuring a Web Server.

npm install express --save

Next we will create the server runtime using the express API. Create a new file called server.js and add it to the server folder.

var express = require('express');
var path = require('path');


var APP = express();


var PATH_HTML = path.resolve(__dirname, '../public/html');
var PATH_RESOURCES = path.resolve(__dirname, '../public/app');

APP.use('/public', express.static(PATH_RESOURCES));
APP.use('/', express.static(PATH_HTML));


var server = APP.listen(process.env.PORT || 3000, function() {
   var port = server.address().port;
   console.log('Server is listening at %s', port);
});

React

Next Reactjs will be installed.

npm i react react-dom --save

Webpack Configuration

To configure Webpack a configuration file is required. Create a file named webpack.config.js

module.exports = {
 entry: './src/client/app/js/app.js',
 output: {
   path: './src/public/app/js',
   filename: 'bundle.js'
 },
 module: {
   loaders: [
     {
       test: /\.js$/,
       exclude: /node_modules/,
       loader: 'babel-loader'
     }
   ]
 },
 resolve: {
   extensions: ['', '.js', '.json']
 }
};

We also need to create a file named app.js in the directory /src/client/app/js/. This file is required by Webpack when building the project. Moreover, it is also the entry point to the client code.

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
 <h1>Hello, world!</h1>, document.getElementById('app')
);

Next a file named: index.html will be added to public/html. This file will be served by the express web server from the context root: /.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello, world</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="public/js/bundle.js" charset="utf-8"></script>
</body>
</html>

We will also add the NPM module `concurrently’ to watch our resources and trigger Webpack to rebuild the project bundle.

npm install concurrently --save-dev

Node Configuration

Next, Node will be configured. Add the following to package.json.

"scripts": {
   "webpack-build": "webpack -w",
   "server": "node app/server/server.js",
   "dev": "concurrently --kill-others \"npm run webpack-build\" \"npm run server\"",
   "test": "echo \"Error: no test specified\" && exit 1"
},

The application can now be executed from the command line.

npm run dev

REST API

Next we will build a simple REST API. The REST API will expose an array of users to the client.

var users = [
  {
     "id": 1,
     "firstName" : "John",
     "lastName" : "Doe",
     "role" : "Admin",
  },
  {
     "id": 1,
     "firstName" : "Mary",
     "lastName" : "Jane",
     "role" : "Manager"
  }
];

APP.get('/users', function (req, res) {
  res.end(JSON.stringify(users));
});

Above server.js is updated to handle requests to /users returning an array of user data. Next the client will be updated to call the users REST service and display the data. JQuery will be added to make the server request.

npm i jquery --save

With JQuery installed app.js will be updated.

import React from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';


var Table = React.createClass({propTypes: {
       "headings": React.PropTypes.array,
       "dataset": React.PropTypes.array
   },

   getInitialState: function() {
       return {users: []};
   },
   componentWillMount: function() {
       var usersApi = `/users`;
       $.get(usersApi)
       .then(this._updateState)
       .catch(function (ex) {
           console.log('Request failed', ex)
       });
   },

   _updateState(users){
       this.setState({
           users: JSON.parse(users)
       });
   },
   render: function () {

       var headings = this.props.headings.map(function (header, index) {
           return <th key={index}>{header}</th>
       });

       var tableRows = this.state.users.map(function (row, index) {
           return <tr key={index}>
           <td>{row.id}</td>
           <td>{row.firstName}</td>
           <td>{row.lastName}</td>
           <td>{row.role}</td>
       </tr>
       });

       return <table className="table">
           <thead>
               <tr>{headings}</tr>
           </thead>
           <tbody>
               {tableRows}
           </tbody>
       </table>

   }});

var headings = ["ID", "First Name", "Last Name", "Role"];
var dataset = [];

ReactDOM.render(<Table headings={headings} dataset={dataset} />, document.getElementById('app'));

Above we create a simple React Table component. This component calls the REST API from the componentWillMount callback, and displays the data in a HTML table.

Adding CSS

Finally some CSS will be added to style the table. To do this Webpack will be configured with a CSS loader.

npm install css-loader style-loader --save-dev

We also need to add the loader to the Webpack configuration file.

 const path = require('path');

const PATHS = {
    entry: path.join(__dirname, 'src/client/app/js/app.js'),
    css: path.join(__dirname, 'src/client/app/css'),
    build: path.join(__dirname, 'src/public/app/js')
};

module.exports = {
 entry: PATHS.entry,
 output: {
   path: PATHS.build,
   filename: 'bundle.js'
 },
 module: {
   loaders: [
     {
       test: /\.css$/,
       loaders: ['style', 'css'],
       include: PATHS.css
     },
     {
       test: /\.js$/,
       exclude: /node_modules/,
       loader: 'babel-loader'
     }
   ]
 },
 resolve: {
   extensions: ['', '.js', '.json']
 }
};

We will add the file app.css to src/client/app/css

body {
   padding: 1.25em;
}



.table {
 max-width: 100%;
 background-color: transparent;
 border-collapse: collapse;
 border-spacing: 0;
 width: 100%;
 max-width: 100%;
}

.table th,
.table td {
 padding: 0.95em 0.5em;
 text-align: left;
 vertical-align: top;
}

.table thead th {
 font-weight: bold;
 vertical-align: center;
}

.table-bordered {
 border-collapse: separate;
 *border-collapse: collapse;
 -webkit-border-radius: 4px;
 -moz-border-radius: 4px;
  border-radius: 4px;
}

.table-bordered-horizontal, table-bordered-vertical {
   border-left: 0;
}


.table-bordered thead:first-child tr:first-child th,
.table-bordered tbody:first-child tr:first-child th,
.table-bordered tbody:first-child tr:first-child td {
 border-top: 0;
}

.table-bordered thead:first-child tr:first-child > th:first-child,
.table-bordered tbody:first-child tr:first-child > td:first-child {
 -webkit-border-top-left-radius: 4px;
  border-top-left-radius: 4px;
 -moz-border-radius-topleft: 4px;
}

.table-bordered thead:first-child tr:first-child > th:last-child,
.table-bordered tbody:first-child tr:first-child > td:last-child {
 -webkit-border-top-right-radius: 4px;
  border-top-right-radius: 4px;
 -moz-border-radius-topright: 4px;
}

.table-bordered thead:last-child tr:last-child > th:first-child,
.table-bordered tbody:last-child tr:last-child > td:first-child,
.table-bordered tfoot:last-child tr:last-child > td:first-child {
 -webkit-border-bottom-left-radius: 4px;
  border-bottom-left-radius: 4px;
 -moz-border-radius-bottomleft: 4px;
}

.table-bordered thead:last-child tr:last-child > th:last-child,
.table-bordered tbody:last-child tr:last-child > td:last-child,
.table-bordered tfoot:last-child tr:last-child > td:last-child {
 -webkit-border-bottom-right-radius: 4px;
  border-bottom-right-radius: 4px;
 -moz-border-radius-bottomright: 4px;
}

.table-bordered tfoot + tbody:last-child tr:last-child td:first-child {
 -webkit-border-bottom-left-radius: 0;
  border-bottom-left-radius: 0;
 -moz-border-radius-bottomleft: 0;
}

.table-bordered tfoot + tbody:last-child tr:last-child td:last-child {
 -webkit-border-bottom-right-radius: 0;
  border-bottom-right-radius: 0;
 -moz-border-radius-bottomright: 0;
}


.table {
 background-color: #ffffff;
}


.table thead th {
   background-color: #f6f7f8;
   background-image: linear-gradient(#fff, #efefef);
}

.table-bordered {
 border: 1px solid #dddddd;
}


.table-bordered-horizontal td, .table-bordered-horizontal th,
.table-bordered-vertical td, .table-bordered-vertical th {
  border-top: 1px solid #dddddd;
}

.table-bordered-horizontal th,
.table-bordered-horizontal td {
    border-left: 1px solid #dddddd;
}

Finally the CSS file is included within app.js.

import appCss from '../css/app.css';

The application can be started from the command line.

npm run dev