Compare commits
1 Commits
package-lo
...
heroku-pos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12bdcdd296 |
47
README.md
47
README.md
@@ -1,27 +1,27 @@
|
||||
# create-react-app with a Node server on Heroku
|
||||
|
||||
A minimal example of using a Node backend (server for API, proxy, & routing) with a [React frontend](https://github.com/facebookincubator/create-react-app).
|
||||
A minimal example of using a Node backend (API, proxy, & routing) with a [React app](https://github.com/facebookincubator/create-react-app) and an [Express server](http://expressjs.com).
|
||||
|
||||
To deploy a frontend-only React app, use the static-site optimized
|
||||
To deploy a frontend-only React app, use the static-site optimized
|
||||
▶️ [create-react-app-buildpack](https://github.com/mars/create-react-app-buildpack)
|
||||
|
||||
⤵️ [Switching from create-react-app-buildpack](#switching-from-create-react-app-buildpack)?
|
||||
|
||||
|
||||
## Design Points
|
||||
|
||||
A combo of two npm projects, the backend server and the frontend UI. So there are two `package.json` configs.
|
||||
A combo of two npm projects, the backend and the frontend. So there are two `package.json` configs.
|
||||
|
||||
1. [`package.json`](package.json) for [Node server](server/) & [Heroku deploy](https://devcenter.heroku.com/categories/deployment)
|
||||
* `heroku-postbuild` script compiles the webpack bundle during deploy
|
||||
* `cacheDirectories` includes `react-ui/node_modules/` to optimize build time
|
||||
* `postinstall` script builds the React UI webpack bundle
|
||||
* `cacheDirectories` includes `react-ui/node_modules/` to optimize deploy to Heroku
|
||||
2. [`react-ui/package.json`](react-ui/package.json) for [React web UI](react-ui/)
|
||||
* generated by [create-react-app](https://github.com/facebookincubator/create-react-app)
|
||||
|
||||
|
||||
## Demo
|
||||
## Punchline
|
||||
|
||||
[Demo deployment](https://cra-node.herokuapp.com/): example API call from the React UI is [fetched with a relative URL](react-ui/src/App.js#L16) that is served by an Express handler in the Node server.
|
||||
[Demo deployment](https://cra-node.herokuapp.com/).
|
||||
|
||||
Example API call is [fetched from relative URL](react-ui/src/App.js#L16).
|
||||
|
||||
|
||||
## Deploy to Heroku
|
||||
@@ -38,7 +38,7 @@ This deployment will automatically:
|
||||
* detect [Node buildpack](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-nodejs)
|
||||
* build the app with
|
||||
* `npm install` for the Node server
|
||||
* `heroku-postbuild` for create-react-app
|
||||
* `postinstall` to build the webpack bundle from create-react-app
|
||||
* launch the web process with `npm start`
|
||||
* serves `../react-ui/build/` as static files
|
||||
* customize by adding API, proxy, or route handlers/redirectors
|
||||
@@ -46,33 +46,6 @@ This deployment will automatically:
|
||||
👓 More about [deploying to Heroku](https://devcenter.heroku.com/categories/deployment).
|
||||
|
||||
|
||||
## Switching from create-react-app-buildpack
|
||||
|
||||
If an app was previously deployed with [create-react-app-buildpack](https://github.com/mars/create-react-app-buildpack), then a few steps are required to migrate the app to this architecture:
|
||||
|
||||
1. Remove **create-react-app-buildpack** from the app; [heroku/nodejs buildpack](https://devcenter.heroku.com/articles/nodejs-support#activation) will be automatically activated
|
||||
|
||||
```bash
|
||||
heroku buildpacks:clear
|
||||
```
|
||||
1. Move the root React app files (including dotfiles) into a `react-ui/` subdirectory
|
||||
|
||||
```bash
|
||||
mkdir react-ui
|
||||
git mv [!react-ui]* react-ui/
|
||||
# You'll see "fatal: Not a git repository"; let's fix that error
|
||||
mv react-ui/.git ./
|
||||
```
|
||||
1. Create a root [`package.json`](package.json), [`server/`](server/), & [`.gitignore`](.gitignore) modeled after the code in this repo
|
||||
1. Commit and deploy ♻️
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m 'Migrate from create-react-app-buildpack to Node server'
|
||||
git push heroku master
|
||||
```
|
||||
|
||||
|
||||
## Local Development
|
||||
|
||||
### Run the API Server
|
||||
|
||||
217
package-lock.json
generated
217
package-lock.json
generated
@@ -1,217 +0,0 @@
|
||||
{
|
||||
"name": "heroku-cra-node",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
|
||||
"integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo="
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz",
|
||||
"integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
|
||||
"integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4="
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz",
|
||||
"integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
|
||||
"integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz",
|
||||
"integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.15.3",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz",
|
||||
"integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI="
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz",
|
||||
"integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk="
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz",
|
||||
"integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz",
|
||||
"integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz",
|
||||
"integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz",
|
||||
"integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
|
||||
"integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
|
||||
"integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.15",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz",
|
||||
"integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
|
||||
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc="
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz",
|
||||
"integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz",
|
||||
"integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
|
||||
"integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
||||
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz",
|
||||
"integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk="
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz",
|
||||
"integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI="
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
|
||||
"integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
|
||||
"integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.15",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz",
|
||||
"integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA="
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz",
|
||||
"integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz",
|
||||
"integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@
|
||||
"version": "1.0.0",
|
||||
"description": "How to use create-react-app with a custom Node API on Heroku",
|
||||
"engines": {
|
||||
"node": "6.11.x"
|
||||
"node": "6.9.x"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node server",
|
||||
"heroku-postbuild": "cd react-ui/ && npm install && npm install --only=dev --no-shrinkwrap && npm run build"
|
||||
"heroku-postbuild": "cd react-ui/ && npm install --only=dev && npm install && npm run build"
|
||||
},
|
||||
"cacheDirectories": [
|
||||
"node_modules",
|
||||
|
||||
5938
react-ui/package-lock.json
generated
5938
react-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,12 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"react-scripts": "0.9.5"
|
||||
"react-scripts": "0.8.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2"
|
||||
"react-dom": "^15.4.2",
|
||||
"whatwg-fetch": "^2.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
||||
3
react-ui/src/App.js
vendored
3
react-ui/src/App.js
vendored
@@ -1,11 +1,12 @@
|
||||
import React, { Component } from 'react';
|
||||
import logo from './logo.svg';
|
||||
import 'whatwg-fetch';
|
||||
import './App.css';
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
this.state = {
|
||||
message: null,
|
||||
fetching: true
|
||||
};
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 5000;
|
||||
|
||||
// Priority serve any static files.
|
||||
app.use(express.static(path.resolve(__dirname, '../react-ui/build')));
|
||||
app.use(express.static(__dirname + '/../react-ui/build'));
|
||||
|
||||
// Answer API requests.
|
||||
app.get('/api', function (req, res) {
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send('{"message":"Hello from the custom server!"}');
|
||||
});
|
||||
|
||||
// All remaining requests return the React app, so it can handle routing.
|
||||
app.get('*', function(request, response) {
|
||||
response.sendFile(path.resolve(__dirname, '../react-ui/build', 'index.html'));
|
||||
});
|
||||
|
||||
app.listen(PORT, function () {
|
||||
console.log(`Listening on port ${PORT}`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user