We’re underway with the development of our client side (front end) application with React.js after the last episode in this series covering the set up with Webpack. Now it’s time to create an application server with Express and structure the code to run as an isomorphic application. An isomorphic application is one where we share the application logic on both the client and the server. In this case that is the React.js components which will be rendered on the server using Express. The benefits of this reach beyond just code sharing as it helps us with Search Engine Optimisation, improving page load times and providing a richer user experience.

If you followed the first article in this series then you will already have Express installed. If not you should work through that article first or install it before continuing. Alternatively you can get up to speed by downloading the code for the last project. If doing this you will need to run npm install to get the dependencies installed.

We start by creating a new directory in the project folder named server. Add a new file inside the server directory named index.js with the following code.

Create an express server import express from 'express'; const app = express(); app.get('/', (request, response) => { response.send('Hello world from Express'); }); app.listen(3000, () => console.log('Server running')); 1 2 3 4 5 6 7 8 9 import express from 'express' ; const app = express ( ) ; app . get ( '/' , ( request , response ) = > { response . send ( 'Hello world from Express' ) ; } ) ; app . listen ( 3000 , ( ) = > console . log ( 'Server running' ) ) ;

Here we import express and call it to create a new application named app. Next we define a route for the application which is ‘/’. What this means is that when we visit the application without specifying a specific page or section the function we pass as the second parameter will be called to handle the request and provide an appropriate response, the text that is sent back to the client/browser. We use an arrow function that receives two arguments, the request object being received and the response object we will be sending back. For now we send back the message ‘Hello world from Express’.

Before we can run this file and test our server we must install the Babel CLI (the command line tools for Babel). We need to install these as we are writing the Node.js server code in the ES2015 syntax which, at the time of writing, is not fully supported in the most recent version of Node.js.

Install the babel-cli tools npm install babel-cli -g 1 npm install babel - cli - g

We install the babel-cli globally like we did with Webpack but the core babel library that the CLI tools will use was installed locally to our project in the last article. Once installed we need to create a configuration file for babel in ./.babelrc (note the file name starts with a dot). We do this to tell babel which presets we require to build our code and to ignore packages in the node_modules folder.

.babelrc { "presets": ["react", "es2015"], "ignore": "node_modules" } 1 2 3 4 { "presets" : [ "react" , "es2015" ] , "ignore" : "node_modules" }

Run the server from the root of the project directory using the babel-node command.

Run the server using babel-node babel-node server 1 babel - node server

This command will compile the ./server/index.js file using Babel and start the server. After a few seconds you should see the message ‘Server running’ in the terminal window. When you see this message visit http://localhost:3000 in your browser where you should be greeted with the message “Hello world f rom Express”.

We have a server running with very minimal effort thanks to Express! Kill the server now by hitting Ctrl-C in the terminal window where it is running.

Building React components for Express

Now that we have a server we need to look at how we will share our React.js components between the server and the client. There is going to be code outside of the components themselves that we won’t be sharing such as the code to fetch data as this will be handled differently in the two environments. What we do want to share is our React.js components and the data flow to update and render those components with Redux (we’ll be introducing Redux and RethinkDB in the next article).

Webpack enables us to build code for both the client and server environments. On the client we will build the entire application from the index.js entry point but for the server we will build the code in chunks from the top level (smart) components. We currently only have a single component ./client/components/app so lets set up Webpack to build this chunk for the server. Make the following changes to the existing webpack.config.js file.

Webpack split configuration for client and server const path = require('path'); const CLIENT_DIR = path.resolve(__dirname, 'client'); const SERVER_DIR = path.resolve(__dirname, 'server/generated'); const DIST_DIR = path.resolve(__dirname, 'dist'); const loaders = [{ test: /\.js$/, include: CLIENT_DIR, loader: 'babel-loader', query: { presets: ['es2015', 'react'] } }, { test: /\.less$/, loader: 'style-loader!css-loader!less-loader' } ]; module.exports = [{ name: 'client', target: 'web', context: CLIENT_DIR, entry: './index.js', output: { path: DIST_DIR, filename: 'bundle.js' }, module: { loaders: loaders }, resolve: { alias: { components: path.resolve(CLIENT_DIR, 'components') } } }, { name: 'server', target: 'node', context: CLIENT_DIR, entry: { app: 'components/app/index.js' }, output: { path: SERVER_DIR, filename: '[name].js', libraryTarget: 'commonjs2' }, externals: /^[a-z\-0-9]+$/, module: { loaders: loaders }, resolve: { alias: { components: path.resolve(CLIENT_DIR, 'components') } } }]; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 const path = require ( 'path' ) ; const CLIENT_DIR = path . resolve ( __dirname , 'client' ) ; const SERVER_DIR = path . resolve ( __dirname , 'server/generated' ) ; const DIST_DIR = path . resolve ( __dirname , 'dist' ) ; const loaders = [ { test : / \ . js $ / , include : CLIENT_DIR , loader : 'babel-loader' , query : { presets : [ 'es2015' , 'react' ] } } , { test : / \ . less $ /, loader: 'style-loader!css-loader!less-loader' } ]; module.exports = [{ name: 'client', target: 'web', context: CLIENT_DIR, entry: './i ndex . js ', output: { path: DIST_DIR, filename: ' bundle . js ' }, module: { loaders: loaders }, resolve: { alias: { components: path.resolve(CLIENT_DIR, ' components ') } } }, { name: ' server ', target: ' node ', context: CLIENT_DIR, entry: { app: ' components /app/i ndex . js ' }, output: { path: SERVER_DIR, filename: ' [ name ] . js ', libraryTarget: ' commonjs2 ' }, externals: /^[a-z\-0-9]+$/, module: { loaders: loaders }, resolve: { alias: { components: path.resolve(CLIENT_DIR, ' components ' ) } } } ] ;

Added here is a second set of configuration that targets node. This means that the code to be built is for use with Node.js. We define the entry point in the server configuration as an object which contains the names of the chunks we want to build. For now this just contains the app component. In the output section we specify that files should be saved in the directory ./server/generated and that each file be named according to the name given in the entry section using the ‘[name].js’ placeholder. We also specify that the output library type of the files should be commonjs2 the module system used in Node.js.

Running webpack now will show the build status for both sets of configuration. The client code is now saved into the file ./dist/bundle.js so you can delete the old ./bundle.js file from the previous tutorials if it exists. The server code is saved into the ./server/generated/app.js file ready for use in our Express server. We have an issue though, if we try to import and use the App component as it is now then we will receive an error. This error is due to the import of ‘./style.less’ which makes no sense to be importing in server side code. To deal with this issue we will extract the generated CSS into a separate file so that it can be included from our index.html page. To do this we install and configure the the extract-text-webpack-plugin to remove the output of the style-loader and save it into an external CSS file.

npm install the extract text plugin npm install extract-text-webpack-plugin --save-dev 1 npm install extract - text - webpack - plugin -- save - dev

Add the extract text plugin for CSS const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const CLIENT_DIR = path.resolve(__dirname, 'client'); const SERVER_DIR = path.resolve(__dirname, 'server/generated'); const DIST_DIR = path.resolve(__dirname, 'dist'); const loaders = [{ test: /\.js$/, include: CLIENT_DIR, loader: 'babel-loader', query: { presets: ['es2015', 'react'] } }, { test: /\.less$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader') } ]; module.exports = [{ name: 'client', target: 'web', context: CLIENT_DIR, entry: './index.js', output: { path: DIST_DIR, filename: 'bundle.js' }, module: { loaders: loaders }, resolve: { alias: { components: path.resolve(CLIENT_DIR, 'components') } }, plugins: [ new ExtractTextPlugin('bundle.css', {allChunks: true}) ] }, { name: 'server', target: 'node', context: CLIENT_DIR, entry: { app: 'components/app/index.js' }, output: { path: SERVER_DIR, filename: '[name].js', libraryTarget: 'commonjs2' }, externals: /^[a-z\-0-9]+$/, module: { loaders: loaders }, resolve: { alias: { components: path.resolve(CLIENT_DIR, 'components') } }, plugins: [ new ExtractTextPlugin('[name].css') ] }]; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 const path = require ( 'path' ) ; const ExtractTextPlugin = require ( 'extract-text-webpack-plugin' ) ; const CLIENT_DIR = path . resolve ( __dirname , 'client' ) ; const SERVER_DIR = path . resolve ( __dirname , 'server/generated' ) ; const DIST_DIR = path . resolve ( __dirname , 'dist' ) ; const loaders = [ { test : / \ . js $ / , include : CLIENT_DIR , loader : 'babel-loader' , query : { presets : [ 'es2015' , 'react' ] } } , { test : / \ . less $ / , loader : ExtractTextPlugin . extract ( 'style-loader' , 'css-loader!less-loader' ) } ] ; module . exports = [ { name : 'client' , target : 'web' , context : CLIENT_DIR , entry : './index.js' , output : { path : DIST_DIR , filename : 'bundle.js' } , module : { loaders : loaders } , resolve : { alias : { components : path . resolve ( CLIENT_DIR , 'components' ) } } , plugins : [ new ExtractTextPlugin ( 'bundle.css' , { allChunks : true } ) ] } , { name : 'server' , target : 'node' , context : CLIENT_DIR , entry : { app : 'components/app/index.js' } , output : { path : SERVER_DIR , filename : '[name].js' , libraryTarget : 'commonjs2' } , externals : /^ [ a - z \ - 0 - 9 ] + $ / , module : { loaders : loaders } , resolve : { alias : { components : path . resolve ( CLIENT_DIR , 'components' ) } } , plugins : [ new ExtractTextPlugin ( '[name].css' ) ] } ] ;

Run webpack again. We now have ./dist/bundle.css alongside ./dist/bundle.js and ./server/generated/app.css alongside ./server/generated/app.js with all CSS extracted from our JavaScript code for both environments.

Using React.js components on the server

Now we are able to import and use our App component in our Express application.

Include the App component from express import express from 'express'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import App from './generated/app'; const app = express(); app.get('/', (request, response) => { response.send(ReactDOMServer.renderToString(<App />)); }); app.listen(3000, () => console.log('Server running')); 1 2 3 4 5 6 7 8 9 10 11 12 import express from 'express' ; import React from 'react' ; import ReactDOMServer from 'react-dom/server' ; import App from './generated/app' ; const app = express ( ) ; app . get ( '/' , ( request , response ) = > { response . send ( ReactDOMServer . renderToString ( < App / > ) ) ; } ) ; app . listen ( 3000 , ( ) = > console . log ( 'Server running' ) ) ;

Start the express server again.

babel-node server 1 babel - node server

Open the page once again in the web browser at http://localhost:3000 and you should see the message “Hello world from a React component”. Congratulations! You just created the first part or an isomorphic application with Node.js. Now lets get the application back to the same state as it was at the end of the last article with the CSS and index.html page.

Adding templates to Express

Currently we are just sending the string rendered by the App component as the response to the request we receive from browser but we need to send our index.html file with the component string inserted within the main tag. To do this we are going to install and use a template library, in this case we will be using the Handlebars library for Express. Install and add it to the application now.

Install handlebars for express npm install express-handlebars --save 1 npm install express - handlebars -- save

Edit ./server/index.js to register Handlebars as the view engine for our application. Doing this tells express how we want to render output for our responses.

Add handlebars to express import express from 'express'; import handlebars from 'express-handlebars'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import App from './generated/app'; const app = express(); app.engine('handlebars', handlebars({defaultLayout: 'main'})); app.set('view engine', 'handlebars'); app.get('/', (request, response) => { response.render('app', { app: ReactDOMServer.renderToString(<App />) }); }); app.listen(3000, () => console.log('Server running')); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import express from 'express' ; import handlebars from 'express-handlebars' ; import React from 'react' ; import ReactDOMServer from 'react-dom/server' ; import App from './generated/app' ; const app = express ( ) ; app . engine ( 'handlebars' , handlebars ( { defaultLayout : 'main' } ) ) ; app . set ( 'view engine' , 'handlebars' ) ; app . get ( '/' , ( request , response ) = > { response . render ( 'app' , { app : ReactDOMServer . renderToString ( < App / > ) } ) ; } ) ; app . listen ( 3000 , ( ) = > console . log ( 'Server running' ) ) ;

We start by adding Handlebars as a template engine using the engine method on the app object before telling the app to use this engine as the view engine. In doing this we set the default layout to be main which is going to be the main parts of the index.html file created in the previous articles. Layouts are used to provide a HTML structure that is common to a number of pages. Create the directory ./server/views/layouts and move ./index.html to become ./server/views/layouts/main.handlebars. Edit main.handlebars so that it now looks like the below.

Main handlebars layout <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <title>React isomorphic</title> <link rel="stylesheet" href="bundle.css"> </head> <body> {{{body}}} <script src="bundle.js" async></script> </body> </html> 1 2 3 4 5 6 7 8 9 10 11 12 13 < ! DOCTYPE html > < html > < head > < meta charset = "UTF-8" > < meta name = "viewport" content = "width=device-width, initial-scale=1, user-scalable=no" > < title > React isomorphic < / title > < link rel = "stylesheet" href = "bundle.css" > < / head > < body > { { { body } } } <script src = "bundle.js" async > </script> < / body > < / html >

The {{{body}}} placeholder is where the contents of each view will be rendered. So far we only have a single view which is the result of rendering our App component but as the application grows we will add additional routes with additional views. Add the app view in the file ./server/views/app.handlebars and copy the below into it.

The app view <main id="app">{{{app}}}</main> 1 < main id = "app" > { { { app } } } < / main >

Save the file and run the application but this time run the application from inside of the server directory. Change the current directory before trying to run the server.

Run express from the server directory cd server babel-node ./index.js 1 2 cd server babel - node . / index .js

As before you should see the rendered text “Hello world from a React component” in the browser but there is a problem. If you look in the network tab of your browsers development tools you will see that both bundle.js and bundle.css return a 404 not found. This is because express is not yet configured to serve these static assets. Let’s configure express.

Conifgure express to serve static assets import path from 'path'; import express from 'express'; import handlebars from 'express-handlebars'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import App from './generated/app'; const app = express(); // View templates app.engine('handlebars', handlebars({defaultLayout: 'main'})); app.set('view engine', 'handlebars'); // Static assets app.use(express.static(path.resolve(__dirname, '../dist'))); // Routes app.get('/', (request, response) => { response.render('app', { app: ReactDOMServer.renderToString(<App />) }); }); app.listen(3000, () => console.log('Server running')); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import path from 'path' ; import express from 'express' ; import handlebars from 'express-handlebars' ; import React from 'react' ; import ReactDOMServer from 'react-dom/server' ; import App from './generated/app' ; const app = express ( ) ; // View templates app . engine ( 'handlebars' , handlebars ( { defaultLayout : 'main' } ) ) ; app . set ( 'view engine' , 'handlebars' ) ; // Static assets app . use ( express . static ( path . resolve ( __dirname , '../dist' ) ) ) ; // Routes app . get ( '/' , ( request , response ) = > { response . render ( 'app' , { app : ReactDOMServer . renderToString ( < App / > ) } ) ; } ) ; app . listen ( 3000 , ( ) = > console . log ( 'Server running' ) ) ;

We tell express to use the static assets from the ./dist folder generated by Webpack and add some comments as our code is now starting to grow. At a later stage we will split the code out so that it is more easily understood and maintainable.

Stop the server if it is already running, ensure both bundle.js and bundle.css are built by running Webpack then restart the server. Opening the page in the browser now should show the rendered component with the styling applied.

If you’ve enjoyed the series so far why not sign up for free and receive updates about new article as we release them.