NodeJS-React-Redux Tutorial - Part 3: NodeJS REST API Server + CORS

Now we are going to begin building the NodeJS REST API for our news

1.5 Node server and news REST API

REST stands for Representational State Transfer. In simple terms it is a resource orientated architecture style.

The fundamental concept within REST is that of a resource. A resource is the object we are operating on. For each resource we define what methods can operate on that resource. Methods such as GET, POST, UPDATE, DELETE.

To begin with our REST API will have a single resource, that of news, with 2 routes on the GET method

To start , on the terminal in our reactnodedemo root folder, enter the following

mkdir server
cd server
npm init

Hit enter to use all the default options, except for ‘entry point’, on this enter app.js

Node

Next lets install express and nodemon

npm install express --save
npm install -g nodemon

As with the React client app, lets add the server node_modules folder to the root .gitignore file

# dependencies
/client/node_modules
/server/node_modules

Now create an app.js file in the root of our server folder with the following:

const express = require('express');
const routes = require('./routes/index');

let app = express();
const PORT = process.env.PORT || 5000;

app.use('/', routes);

app.listen(PORT, function () {
    console.log(`Listening on port ${PORT}`);
});

Create a folder /routes within the server folder and within there create a file called index with the following content:

const express = require('express');
const router = express.Router();

router.get('/', function(req, res, next) {
  res.send('now then');
});

module.exports = router;

Run the following in your server directory

nodemon

The server should now be up and running , returning back a greeting to the browser

Step 1 with our server is going to be to create a simple API that will return back to the browser hardcoded news stories

In our routes folder lets create the file, news.js

const express = require('express')
const router = express.Router()

const fakeNews = [{
    id: '1',
    title: 'Mad owl chases car',
    teaser: 'Mad owl seen tormenting drivers in Morecambe',
    body: 'This is some body text regarding the news story of the mad owl tormenting drivers in Morecambe'
}, {
    id: '2',
    title: 'Owl stowaway',
    teaser: 'Despicable owl impersonates passenger to board flight to Luton',
    body: 'This is some body text regarding the news story of the owl making its way onto a domestic flight to luton'
}, {
    id: '3',
    title: 'Owl steals pork pie',
    teaser: 'This morning a rogue owl stole a pork pie from a shop in Swindon.',
    body: 'This is some body text regarding the news story of the owl stealing a pork pie from a shop in swindon'
}];

router.get('/', function(req, res, next) {
    res.status(200).send({
      data: fakeNews
    })
})

router.get('/:id', function(req, res, next){
    const id = req.params.id

    const picked = fakeNews.find(o => o.id === id);

    res.status(200).send({
        data: picked
    })

})

module.exports = router

Next in app.js, add in the news route

...
const newsRoute = require('./routes/news');

let app = express();
const PORT = process.env.PORT || 5000;

app.use('/', routes);

app.use('/news', newsRoute);
...

All requests to /news will now be routed into our news route. Requests to /news/ will return all news and requests with the pattern /news/:id will return the news story with that id.

As this code is temporary we don’t need to worry about error handling incorrect id’s, for now we will assume the id’s will always be correct.

With this simple server created we can now connect up our React client

Editing actions.js, we will add in a fetch request to get our news and for now just output what is returned

export function fetchNews(fakeNews){
    console.log('presend')
    return dispatch => {
        return fetch(`http://localhost:5000/news`)
        .then( (response) =>{
            console.log(response);
        });
    }    
}

When this runs you will see in developer console something similar to the following:

Failed to load http://localhost:5000/news: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

This is because our client is running on port 3000, yet our server is on port 5000. As our API is not on the same address as the client, the browser blocks the request for security reasons.

Currently this is due to our development setup, however in production our server and client will be operating from the same URL and therefore we won’t have a problem. In development we can proxy requests from 3000 to 5000.

If we were intending to have our API on a different server than our client we need to use CORS. Incase we change to this setup - lets install cors

1.6 CORS

Cors is ‘Cross Origin Resource Sharing’, this allows the browser to make cross domain requests.

There are two types of CORS request - Simple and Preflighted

In a simple GET request it works as follows:

The client browser makes an HTTP request, sending an Origin header which contains the URL of where the client is located, in our instance http://localhost:3000 .

The CORS enabled server checks the Origin request header and if the origin is allowed access, it responds including the passed in Origin in the Access-Control-Allow-Origin header.

On receiving the response the client then checks this Access-Control-Allow-Origin header value to see if it matches the sent origin and if not it blocks the response.

A preflighted CORS request is where the browser first sends a ‘preflight’ OPTIONS request to the server, passing in the request method and headers it wishes to use. If the server allows the proposed method, it responds back stating which methods and headers are allowed. The browser then sends the original request passing along the Origin header just as with a simple request.

Whether the browser makes a simple request or a preflighted request depends on the method and headers that are going to be used.

To use CORS lets first install the package. In your server directory run

npm install cors --save

Next we will require cors in our server app.js and allow requests from any address using ‘*’

...
const cors = require('cors');

let app = express();
const PORT = process.env.PORT || 5000;

app.use(cors());
app.options('*', cors());

app.use('/', routes);
...

Switching back to the client, if we were to use a different server for our API we would alter our action.js to use cors as follows: (We won’t actually using the code below however)

export function fetchNews(fakeNews){
    console.log('presend')
    return dispatch => {
        return fetch(`http://[INSERT SERVER ADDRESS]/news`, {method: 'GET', mode: 'cors'})
        .then( (response) =>{
            console.log(response);
        });
    }    
}

As mentioned above, in production our API and client will be on the same server. We can therefore just proxy our requests in development to 3000 to 5000 by adding the following to package.json in the client folder.

....
    "eject": "react-scripts eject"
  },
  "proxy": "http://localhost:5000"
}

(Some people find that adding this proxy entry causes no effect. If you get this, try killing all running instances of Node , start the client and server again and then try again)

We will then alter action.js, removing the host and port

export function fetchNews(){
    return dispatch => {
        return fetch(`/news`)
        .then( (response) => response.json() )
        .then( (data) => console.log(data))
        .catch( (e) => console.log(e) );
    }    
}

In the developer console we should now see the news data

Devconsole

Next we need to update our fetch methods to process the returned data.

export function fetchNews(){
    return dispatch => {
        return fetch(`/news`)
        .then( (response) => response.json() )
        .then( (data) => dispatch(newsReceived(data.data)))
        .catch( (e) => console.log(e) );
    }    
}

export function fetchNewsItem(id){
    return dispatch => {
        return fetch(`/news/${id}`)
        .then( (response) => response.json() )
        .then( (data) => dispatch(newsItemReceived(data.data)))
        .catch( (e) => console.log(e) );
    }    
}

Finally we need to alter our /containers/News.js and /containers/NewsArticle.js components

News.js

...
    componentDidMount(){
        this.props.dispatch(fetchNews());
    }
...

NewsArticle.js

...
    componentDidMount(){
        this.props.dispatch(fetchNewsItem(this.props.match.params.id));
    }
...

NewsArticle will now use the id parameter passed in by the router and call the API requesting the news story data.

Trying this out in the browser you may notice that is you flick between news stories that there is a brief moment when the news story displays in which it shows the prior news story viewed. This is happening because the newsItem store data still contains the data from the the last request, until the results of the /news/{id} API call overwrite that data.

What we need to do is implement an interim loading message.

First add a new constant for our action

actionTypes.js

export default {
    NEWS_RECEIVED: 'NEWS_RECEIVED',
    NEWSITEM_RECEIVED: 'NEWSITEM_RECEIVED',
    NEWSITEM_LOADING: 'NEWSITEM_LOADING'
}

Now add a new action creator for the added constant

function newsItemLoading(){
    return {
        type: actionTypes.NEWSITEM_LOADING
    }
}

In the reducer, we’ll add a new store element to keep the state of whether the news item is loading or not. We’ll then add a new case statement to handle the action for setting the newsItemLoading to true, and on receiving a NewsItem we’ll set it to false.

import constants from '../constants/actionTypes'

var initialState = {
    news: [],
    newsItem: {},
    newsItemLoading: true
}

export default (state = initialState, action) => {

  var updated = Object.assign({}, state);

  switch(action.type) {

    case constants.NEWS_RECEIVED:
      updated['news'] = action.news;
      return updated;

    case constants.NEWSITEM_RECEIVED:
      updated['newsItem'] = action.newsItem;
      updated['newsItemLoading'] = false;
      return updated;

    case constants.NEWSITEM_LOADING:
      updated['newsItemLoading'] = true;
      return updated

    default:
      return state;
    }
}

To connect up the new state variable to the interface lets amend the following in NewsArticle.js

...
    render(){

        return (
            <div>
                <h2>News Story</h2>
                <ul>
                    { !this.props.newsItemLoading ? <NewsItemDetail data={this.props.newsItem} /> : <div>Loading</div>}
                </ul>
            </div>
        )
    }
}

const mapStateToProps = state => {
    return {
        newsItem: state.news.newsItem,
        newsItemLoading: state.news.newsItemLoading
    }
}
...

Next we’ll go back to the server and have the data pulled from a database instead of hardcoded by integrating MongoDB…

[ To Part 4 ] »