Loopback and create-react-app Developer Tutorial

Recommended snack and song:

Have a lovely bowl of rice pudding while listening to Auto!Automatic!!

Loopback is amazing at REST API Service, and create-react-app is a neat little CLI that generates a solid starting point for a SPA (single page application). But putting them together can be… tricky!

Nevertheless, we’ll give it a shot.

tl;dr: Go clone the repo and change to full-example, it has all of the code in there.

 

What you’ll need to follow along

Let’s clone the repo and install dependencies, plus the create-react-app package:

$ npm install -g create-react-app
$ git clone https://github.com/danazkari/take-note.git
$ cd take-note
$ npm install
$ npm start

The server should now be running at http://localhost:3000 and should have a couple of notes already in there for us to play with. (You can go ahead now and stop the server with Ctrl + C).

 

SPA supporting mods to our Loopback instance

First, go ahead and open the server/config.json file, and add indexFile key to the root of the config like so:

{
  "indexFile": "client/build/index.html",
  ...
}

When you run the production build, that’s where our front-end app will live as a production-ready app. We want a reference to that. Trust me.

Next up, let’s edit the server/server.js file:

'use strict';

var loopback = require('loopback');
var boot = require('loopback-boot');
// 1. Include 'path' package
var path = require('path');

var app = module.exports = loopback();

app.start = function() {
  // 2. Get the FQPN of the index file in client
  var staticFolder = path.dirname(
    path.resolve(__dirname, '..', app.get('indexFile'))
  );
  // 3. Set staticFolder as static in the server
  app.use(loopback.static(staticFolder));
  // start the web server
  return app.listen(function() {
    app.emit('started');
    var baseUrl = app.get('url').replace(/\/$/, '');
    console.log('Web server listening at: %s', baseUrl);
    if (app.get('loopback-component-explorer')) {
      var explorerPath = app.get('loopback-component-explorer').mountPath;
      console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
    }
  });
};

// Bootstrap the application, configure models, datasources and middleware.
// Sub-apps like REST API are mounted via boot scripts.
boot(app, __dirname, function(err) {
  if (err) throw err;

  // start the server if `$ node server.js`
  if (require.main === module)
    app.start();
});

So what we’ve done is included the path package and used it to serve our indexFile config path as a static folder. Pretty simple, right? Next.

For our next modification, we need to tell our client that every request that comes along through /, should resolve to indexFile. We do that through editing server/boot/root.js like this:

'use strict';

// 1. Import the 'path' package
var path = require('path');

module.exports = function(server) {
  // 2. Move server status to '/status'
  // Install a `/status` route that returns server status
  var router = server.loopback.Router();
  router.get('/status', server.loopback.status());

  // 3. Configure '/' to serve the static content
  router.get('/', function(req, res) {
    var indexFile = path.resolve(__dirname, '../..', server.get('indexFile'));
    res.sendFile(indexFile);
  });
  server.use(router);
};

In order for us to run both the back-end and front-end in parallel, we need to install a package called concurrently, which will make our lives a lot easier.

$ npm install --save concurrently

And our last bit of work on the back-end side is to add some scripts to our package.json file like so:

{
	...
	"scripts": {
	    "lint": "eslint .",
	    "prestart": "npm run --prefix client build",
	    "start": "node .",
	    "start-dev": "concurrently \"nodemon .\" \"npm start --prefix client\"",
	    "postinstall": "npm install --prefix client",
	    "posttest": "npm run lint && nsp check"
	}
}

First up, we added a prestart step because, before our app runs, we want to make sure we have a client folder to serve as static, so we run a build command on behalf of our react app (I know… we haven’t set up the front-end yet, bear with me); then, we have the start-dev script which runs both our back-end and front-end dev servers at the same time; there’s also the postinstall step, so we also run the install script on behalf of our front-end app when installing our dependencies.

And that’s it for our back-end mods! Easy, right?

 

Let’s create and configure our React app!

Remove the client/README.md file, and call the create-react-app generator on the client directory:

$ rm client/README.md
$ create-react-app client/

Before running our newly created front-end app, we need to make a minor change and configure it to run in port 3001 instead of 3000, because it would collide with our back-end app.

To do so, we need to add a client/.env file:

PORT=3001

One last thing: I encountered a problem with the service worker, where it would take over the routing completely (once the app is served for production/staging environment) and not allow you to query the back-end server. I solved this problem temporarily by commenting out the lines where it’s imported and then registering it in client/src/index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(, document.getElementById('root'));
// registerServiceWorker();

Not entirely happy with this, but for now, it gets the job done.

And that should be it!

 

Playtime!

Just to make sure everything is working, execute:

$ npm install

This should install our dependencies and then try to install the front-end dependencies as well.

Now let’s run the dev servers:

$ npm run start-dev

This should launch our back-end server at http://localhost:3000 and should have opened up your browser at http://localhost:3001.

Let’s test if everything connects correctly by modifying the client/src/App.js like so:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      notes: [],
    };
  }

  componentWillMount() {
    fetch('http://localhost:3000/api/notes')
      .then(response => response.text())
      .then(JSON.parse)
      .then(notes => this.setState({ notes }));
  }

  render() {
    const { notes } = this.state;
    return (
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      notes: [],
    };
  }

  componentWillMount() {
    fetch('http://localhost:3000/api/notes')
      .then(response => response.text())
      .then(JSON.parse)
      .then(notes => this.setState({ notes }));
  }

  render() {
    const { notes } = this.state;
    return (
      
logo

Welcome to React

To get started, edit src/App.js and save to reload.

    {notes.map(({ id, title, text }) =>
  • {title} - {text}
  • )}
); } } export default App;

 

Yes, I agree: the UI looks terrible like that. But don’t concentrate on that; look at what we’ve accomplished! We have a fully capable React application running inside an instance of another fully capable REST API service.

Pat yourself on the back, go get yourself another bowl of rice pudding and keep learning! You deserve it!

 

Subscribe to our Blog

Daniel Prado
Daniel Prado
Daniel Prado is a Web Development Practice Lead for Gorilla Logic. He previously taught frontend technologies and data structures at some of Costa Rica’s most prestigious universities. Daniel enjoys open water swimming, go-cart racing, and listen to music - especially 80s heavy metal bands.

Deliver off-the-chart results.

WordPress Video Lightbox Plugin