Sunday, 23 December 2018

Deploy React + Express to Heroku

You’ve got a React app, and an API server written in Express or something else. Now – how do you deploy them both to a server?

This article will cover how to keep them together. We’ll build the Express server to serve React’s static files in addition to providing an API, and then deploy it to Heroku. Heroku is easy to deploy to and free to get started with.


heroku logs

remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Node.js app detected
remote:
remote: -----> Creating runtime environment
remote:
remote:        NPM_CONFIG_LOGLEVEL=error
remote:        NODE_ENV=production
remote:        NODE_MODULES_CACHE=true
remote:        NODE_VERBOSE=false
remote:
remote: -----> Installing binaries
remote:        engines.node (package.json):  unspecified
remote:        engines.npm (package.json):   unspecified (use default)
remote:
remote:        Resolving node version 10.x...
remote:        Downloading and installing node 10.14.2...
remote:        Using default npm version: 6.4.1
remote:
remote: -----> Building dependencies
remote:        Installing node modules (package.json + package-lock)
remote:        added 51 packages from 39 contributors and audited 124 packages in 2.565s
remote:        found 0 vulnerabilities
remote:
remote:        Running heroku-postbuild
remote:
remote:        > react-express-heroku@1.0.0 heroku-postbuild /tmp/build_8618b4496c28b4d8453989494ad86267
remote:        > cd client && npm install && npm run build
remote:
remote:        added 1725 packages from 667 contributors and audited 35703 packages in 36.53s
remote:        found 0 vulnerabilities
remote:
remote:
remote:        > client@0.1.0 build /tmp/build_8618b4496c28b4d8453989494ad86267/client
remote:        > react-scripts build
remote:
remote:        Creating an optimized production build...
remote:        Compiled with warnings.
remote:
remote:        ./src/index.js
remote:          Line 5:  'serviceWorker' is defined but never used  no-unused-vars
remote:
remote:        Search for the keywords to learn more about each warning.
remote:        To ignore, add // eslint-disable-next-line to the line before.
remote:
remote:        File sizes after gzip:
remote:
remote:          34.7 KB  build/static/js/1.4a34e45d.chunk.js
remote:          763 B    build/static/js/runtime~main.229c360f.js
remote:          749 B    build/static/js/main.12ccf6aa.chunk.js
remote:          613 B    build/static/css/main.9a92f39c.chunk.css
remote:
remote:        The project was built assuming it is hosted at the server root.
remote:        You can control this with the homepage field in your package.json.
remote:        For example, add this to build it for GitHub Pages:
remote:
remote:          "homepage" : "http://myname.github.io/myapp",
remote:
remote:        The build folder is ready to be deployed.
remote:        You may serve it with a static server:
remote:
remote:          npm install -g serve
remote:          serve -s build
remote:
remote:        Find out more about deployment here:
remote:
remote:          http://bit.ly/CRA-deploy
remote:
remote:
remote: -----> Caching build
remote:        - node_modules
remote:
remote: -----> Pruning devDependencies
remote:        audited 124 packages in 0.853s
remote:        found 0 vulnerabilities
remote:
remote:
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote:        Procfile declares types     -> (none)
remote:        Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote:        Done: 69.7M
remote: -----> Launching...
remote:        Released v3
remote:        https://chuanshuoge1-react-express.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.

-----------------------------------------------

package.json

{
  "name": "react-express-heroku",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js",
    "heroku-postbuild": "cd client && npm install && npm run build"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.4",
    "password-generator": "^2.2.0"
  }
}

-----------------------------------------------------

client/package.json

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.7.0",
    "react-dom": "^16.7.0",
    "react-scripts": "2.1.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ],
//for testing, ignored by heroku
  "proxy": "http://localhost:5000"
}

-------------------------------------------

index.js

const express = require('express');
const path = require('path');
const generatePassword = require('password-generator');

const app = express();

// Serve static files from the React app
app.use(express.static(path.join(__dirname, 'client/build')));

// Put all API endpoints under '/api'
app.get('/api/passwords', (req, res) => {
  const count = 5;

  // Generate some passwords
  const passwords = Array.from(Array(count).keys()).map(i =>
    generatePassword(12, false)
  )

  // Return them as json
  res.json(passwords);

  console.log(`Sent ${count} passwords`);
});

// The "catchall" handler: for any request that doesn't
// match one above, send back React's index.html file.
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname+'/client/build/index.html'));
});

const port = process.env.PORT || 5000;
app.listen(port);

console.log(`Password generator listening on ${port}`);

----------------------------------------------

client/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
//serviceWorker.unregister();

---------------------------------------------

client/app.js

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

class App extends Component {
  // Initialize state
  state = { passwords: [] }

  // Fetch passwords after first mount
  componentDidMount() {
    this.getPasswords();
  }

  getPasswords = () => {
    // Get the passwords and store them in state
    fetch('/api/passwords')
      .then(res => res.json())
      .then(passwords => this.setState({ passwords }));
  }

  render() {
    const { passwords } = this.state;

    return (
      <div className="App">
        {/* Render the passwords if we have them */}
        {passwords.length ? (
          <div>
            <h1>5 Passwords.</h1>
            <ul className="passwords">
              {/*
                Generally it's bad to use "index" as a key.
                It's ok for this example because there will always
                be the same number of passwords, and they never
                change positions in the array.
              */}
              {passwords.map((password, index) =>
                <li key={index}>
                  {password}
                </li>
              )}
            </ul>
            <button
              className="more"
              onClick={this.getPasswords}>
              Get More
            </button>
          </div>
        ) : (
          // Render a helpful message otherwise
          <div>
            <h1>No passwords :(</h1>
            <button
              className="more"
              onClick={this.getPasswords}>
              Try Again?
            </button>
          </div>
        )}
      </div>
    );
  }
}

export default App;

------------------------------------------

reference:
https://daveceddia.com/deploy-react-express-app-heroku/

No comments:

Post a Comment