Set up a React app with a Node.js server proxy

Phil Nash - Oct 10 '18 - - Dev Community

Create React App is a great tool for getting a React application up and running. It's a little less clear when you're building or prototyping an application that requires a server side component, like generating access tokens for Twilio Video or Chat, though. I've found it easiest to work with a server within the same project so that you can start everything up with one command.

By the end of this post you will learn how to set up an Express server that runs alongside a React app. If you can't wait then you can jump straight into the starter project on GitHub.

How it works

There is an option that you can set in Create React App's package.json that proxies non text/html requests through to an alternative back end. You can use this feature to proxy to applications running elsewhere, but today we want to be able to run a server within the React project itself.

We'll pull together a few npm modules that will make it possible to run one command to run our React app and an Express server at the same time so we can proxy to it.

Getting started

To follow along with the rest of this post, you will need Node.js and npm installed.

Start by creating a new React app with Create React App. Did you know, you don't have to globally install the create-react-app package to do this? Instead, you can run:

npm init react-app MyNewApp
cd MyNewApp
Enter fullscreen mode Exit fullscreen mode

Under the hood, npm init takes an initializer name, prepends create- to it and uses npx to install and run the command.

Run the new React application to make sure it was generated properly.

npm start
Enter fullscreen mode Exit fullscreen mode

If you see a spinning React logo, then we're good to go.

Adding a server

We'll add our server dependencies to the devDependencies of our React app as they aren't part of building the front end.

Stop the server with Cmd/Ctrl + C and use npm to install Express and Body Parser:

npm install express body-parser --save-dev
Enter fullscreen mode Exit fullscreen mode

Add the following dependencies to help us run the front end and server together:

npm install node-env-run nodemon npm-run-all express-pino-logger pino-colada --save-dev
Enter fullscreen mode Exit fullscreen mode

Create a file called .env in the project directory to store our environment variables. We don't need to add anything to it just yet, but it will be useful later for including any credentials we need for the server, like API keys.

Next, in the project directory create a new directory called server and a server/index.jsfile. We'll create a small application that we can test with. Add the following code in server/index.js:

const express = require('express');
const bodyParser = require('body-parser');
const pino = require('express-pino-logger')();

const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(pino);

app.get('/api/greeting', (req, res) => {
  const name = req.query.name || 'World';
  res.setHeader('Content-Type', 'application/json');
  res.send(JSON.stringify({ greeting: `Hello ${name}!` }));
});

app.listen(3001, () =>
  console.log('Express server is running on localhost:3001')
);
Enter fullscreen mode Exit fullscreen mode

Open package.json and in the "scripts" object add a new script to run the server using node-env-run and nodemon:

"scripts": {
    // other scripts
    "server": "node-env-run server --exec nodemon | pino-colada"
  },
Enter fullscreen mode Exit fullscreen mode

Test that the server is running correctly by running the script:

npm run server
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3001/api/greeting to test. You should see a JSON response with a "Hello World!" greeting. Try adding a query parameter called name to the URL and see what you get.

Running the server and the React app

To run both the server and React application at the same time we need to add a couple more things to package.json.

First, we are going to set up the proxy to our server. Add the "proxy" key to package.json. We've already set our server to run on port 3001, so point the proxy at localhost:3001.

"proxy": "http://localhost:3001"
Enter fullscreen mode Exit fullscreen mode

We need a script to run both the server and the front end at the same time. We will use npm-run-all for this. Since we are going to be running two scripts at the same time we want to use the parallel mode. npm-run-all gives us a handy shortcut for this with the run-p command.

Add the following to the "scripts" section in package.json:

"scripts": {
    // other scripts
    "server": "node-env-run server --exec nodemon",
    "dev": "run-p server start"
  },
Enter fullscreen mode Exit fullscreen mode

Run npm run dev and both the React application and the server will start up. However, we now can't load localhost:3000/api/greeting in the browser because the Create React App proxy will just respond with the base HTML.

Let's test it from within a component in our React app instead.

Using the proxied server from React

We're going to add a form to the App component that will use the /api/greeting component to form a greeting and show it on the page. Add the following constructor and functions to the App component in src/App.js:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: '',
      greeting: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({ name: event.target.value });
  }

  handleSubmit(event) {
    event.preventDefault();
    fetch(`/api/greeting?name=${encodeURIComponent(this.state.name)}`)
      .then(response => response.json())
      .then(state => this.setState(state));
  }
Enter fullscreen mode Exit fullscreen mode

And add this form to the JSX in the render function:

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <form onSubmit={this.handleSubmit}>
            <label htmlFor="name">Enter your name: </label>
            <input
              id="name"
              type="text"
              value={this.state.name}
              onChange={this.handleChange}
            />
            <button type="submit">Submit</button>
          </form>
          <p>{this.state.greeting}</p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
Enter fullscreen mode Exit fullscreen mode

Open the React app in the browser, fill in your name and submit. The greeting shows that your React app is now talking to your proxied server.

This is only the start

Create React App does a great job of getting a React application started, but if you need a server side component too, it can be fiddly. In this post you've seen how to use the proxy option and run an Express server alongside using tools like npm-run-all.

You can check out all the code from this post in this GitHub repo and use it as a jumping off point if you want to build a React app with an Express API. As a bonus, if you want to create a Twilio Video or Twilio Chat application with React, the Twilio branch is set to return access tokens for either. Just follow the instructions in the README.

Using this template, building React applications backed by an Express server is quicker and easier. I hope it gives you a good platform for building your own ideas; I can't wait to see what you build!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .