Skip to content
This repository has been archived by the owner on Oct 11, 2021. It is now read-only.
/ crak-buildpack Public archive

βš›οΈπŸ’ Heroku Buildpack for create-react-app: host React.js web apps with a Kong API gateway

License

Notifications You must be signed in to change notification settings

mars/crak-buildpack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Heroku Buildpack for create-react-app with Kong gateway

⛔️ This is an old experiment, now archived. Feel free to reference it, but it is very outdated and probably full of issues.


⭐️ A new version of create-react-app-buildpack that replaces the basic Nginx server with a Kong gateway to support sophisticated access control, backend proxies, and more.

Deploy React.js web apps generated with create-react-app. Automates deployment with the built-in bundler and serves it up via Kong, which is fundamentally the Nginx web server.


Purpose

This buildpack deploys a React UI as a static web site. Kong serves the high-performance static site and provides dynamic proxy/gateway capabilities. See Architecture for details.

If your goal is to combine React UI + API (Node, Ruby, Python…) into a single app, then this buildpack is not the answer. The simplest combined solution is all javascript:

▢️ create-react-app + Node.js server on Heroku

Combination with other languages is possible too, like create-react-app + Rails 5 server.

Requires

Quick Start

Ensure requirements are met, then execute the following in a terminal.

✏️ Replace $APP_NAME with the name for your unique app.

npx create-react-app@2.x $APP_NAME
cd $APP_NAME
git init
heroku create $APP_NAME --buildpack mars/crak
heroku addons:create heroku-postgresql:hobby-dev
git add .
git commit -m "Start with create-react-app"
git push heroku master
heroku open

Then, continue development 🌱

For explanation about these steps, continue reading the next section.

Usage

Generate a React app

✏️ Replace $APP_NAME with the name for your unique app.

npx create-react-app@2.x $APP_NAME
cd $APP_NAME

Make it a git repo

git init

At this point, this new repo is local, only on your computer. Eventually, you may want to push to Github.

Create the Heroku app

✏️ Replace $APP_NAME with the name for your unique app.

heroku create $APP_NAME --buildpack mars/crak

This command:

  • sets the app name & its default URL https://$APP_NAME.herokuapp.com
  • sets the app to use this buildpack
  • configures the heroku git remote in the local repo, so git push heroku master will push to this new Heroku app.

Create the database for Kong

The web server is a Kong gateway that uses Heroku Postgre to store configuration of services, route, and plugins.

heroku addons:create heroku-postgresql:hobby-dev

Commit & deploy ♻️

git add .
git commit -m "Start with create-react-app"
git push heroku master

…or if you are ever working on a branch other than master:

✏️ Replace $BRANCH_NAME with the name for the current branch.

git push heroku $BRANCH_NAME:master

Visit the app's public URL in your browser

heroku open

Visit the Heroku Dashboard for the app

Find the app on your dashboard.

Continue Development

Work with your app locally using npm start. See: create-react-app docs

Then, commit & deploy ♻️

Push to Github

Eventually, to share, collaborate, or simply back-up your code, create an empty repo at Github, and then follow the instructions shown on the repo to push an existing repository from the command line.

Testing

Use create-react-app's built-in Jest testing or whatever testing library you prefer.

Heroku CI is supported with minimal configuration. The CI integration is compatible with npm & yarn (see bin/test).

Minimal app.json

Heroku CI uses app.json to provision test apps. To support Heroku CI, commit this minimal example app.json:

{
  "buildpacks": [
    {
      "url": "mars/crak"
    }
  ]
}

Customization

Procfile

Heroku apps may declare what processes are launched for a successful deployment by way of the Procfile. This buildpack's default process comes from heroku-community/kong buildpack. (See: πŸ™ Architecture).

The implicit Procfile for this buildpack is:

web: bin/heroku-buildpack-kong-web
release: bin/heroku-buildpack-crak-release

To customize an app's processes, commit a Procfile and deploy. Include the web & release processes as shown above to keep the default behaviors. Additional process types may be added to run any number of dynos with whatever arbitrary commands you want, and scale each independently.

Web server

The web server may be configured via Kong's nginx template. Simply copy the template file from this buildpack into your own app as config/nginx.template, and commit your own edits to the file.

πŸ‘“ See Nginx HTTP core docs.

Changing the root

If a different web server "root" is required, such as with a highly customized, ejected create-react-app project, then:

Routing

πŸš₯ This buildpack automatically configures Kong to serve the React app from the root. Client-side routing is supported by default. Any server request that would result in 404 Not Found returns the React app.

Create a routes.tf file to configure services, routes, & plugins with the Kong Terraform provider.

Default routes.tf contains:

resource "kong_service" "react" {
  name     = "create-react-app"
  protocol = "http"
  host     = "127.0.0.1"
  port     = 3000
}

resource "kong_route" "web_root" {
  protocols  = ["https", "http"]
  paths      = ["/"]
  service_id = "${kong_service.react.id}"
}

✏️ When creating a custom routes.tf, keep these react & web_root resources to preserve the original routing behavior.

πŸ”Œ Kong plugins may be used to provide access control and more. Configure them through the Kong Terraform provider.

HTTPS-only

Setup secure routes using Kong Route protocols.

Example HTTPS-only route defined in routes.tf:

resource "kong_route" "web_root" {
  protocols  = ["https"]
  paths      = ["/"]
  service_id = "${kong_service.react.id}"
}

Authentication

Password-protect the app by adding the basic-auth plugin to the / root route.

Example basic auth config, appended to routes.tf:

provider "random" {
  version = "~> 2.0"
}

resource "random_id" "private_access_password" {
  byte_length = 32
}

output "private_access_password" {
  value = "${random_id.private_access_password.b64_url}"
}

resource "kong_plugin" "react_basic_auth" {
  name        = "basic-auth"
  service_id  = "${kong_service.react.id}"

  config = {
    hide_credentials = "true"
  }
}

resource "kong_consumer" "private_access" {
  username = "private"
}

resource "kong_consumer_plugin_config" "private_access_credentials" {
  consumer_id = "${kong_consumer.private_access.username}"
  plugin_name = "basic-auth"

  config = {
    username = "private"
    password = "${random_id.private_access_password.b64_url}"
  }
}

Output the generated password with:

heroku run terraform output private_access_password

⚠️ create-react-app's default ServiceWorker config may allow the site to reload without authentication. Unregister the ServiceWorker, if password should be required for every page load. Depending on the version of create-react-app used to generate the app, the ServiceWorker may or may not be enabled by default.

πŸ”Œ Kong plugins may be used to provide other types of authentication. Configure them through the Kong Terraform provider.

Proxy

Proxy XHR requests from the React UI in the browser to API backends. Use to prevent same-origin errors when CORS is not supported on the backend.

Proxy path prefix

To make calls through the proxy, use relative URL's in the React app which will be proxied to the configured target URL.

Using the Kong gateway included in this buildpack, here's how the proxy can rewrite requests:

/api/search-results
  β†’ https://search.example.com/results
  
/api/accounts/users/me
  β†’ https://accounts.example.com/users/me

Proxy for deployment

The heroku-community/kong buildpack (see: πŸ™ Architecture) provides dynamic routing & plugin configuration to utilize Nginx for high-performance proxies in production.

Define proxy config in routes.tf using the Kong Terraform provider to create a service & route.

For example, to configure this proxy:

/api/search-results
  β†’ https://search.example.com/results

…use this config:

resource "kong_service" "search_api" {
  name     = "search"
  protocol = "https"
  host     = "search.example.com"
  port     = 443
  path     = "/results"
}

resource "kong_route" "search_api" {
  protocols  = ["https"]
  paths      = ["/api/search-results"]
  service_id = "${kong_service.search_api.id}"
}

Proxy for local development

create-react-app itself provides a built-in proxy for development. This may be configured to match the behavior of proxy for deployment.

For example add "proxy" to package.json:

{
  "proxy": {
    "/api/search-results": {
      "target": "http://localhost:8000",
      "pathRewrite": {
        "^/api/search-results": "/results"
      }
    }
  }
}

Replace http://localhost:8000 with the URL to your local or remote backend service.

Environment variables

REACT_APP_* environment variables are fully supported with this buildpack.

🚫🀐 Not for secrets. These values may be accessed by anyone who can see the React app.

heroku config:set REACT_APP_HELLO='I love sushi!'

Set vars for local dev

Requires at least create-react-app 0.7. Earlier versions only support Compile-time.

Create a .env file that sets a variable per line:

REACT_APP_API_URL=http://api.example.com
REACT_APP_CLIENT_ID=XyzxYzxyZ

Compile-time vs Runtime

Two versions of variables are supported. In addition to compile-time variables applied during build the app supports variables set at runtime, applied as each web dyno starts-up.

Requirement Compile-time Runtime
never changes for a build βœ“
support for continuous delivery βœ“
updates immediately when setting new config vars βœ“
different values for staging & production (in a pipeline) βœ“
ex: REACT_APP_BUILD_VERSION (static fact about the bundle) βœ“
ex: REACT_APP_DEBUG_ASSERTIONS (prune code from bundle) βœ“
ex: REACT_APP_API_URL (transient, external reference) βœ“
ex: REACT_APP_FILEPICKER_API_KEY (Add-on config vars) βœ“

Compile-time configuration

Supports REACT_APP_, NODE_, NPM_, & HEROKU_ prefixed variables.

Use Node's process.env object.

import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <code>Runtime env var example: { process.env.REACT_APP_HELLO }</code>
    );
  }
}

♻️ The app must be re-deployed for compiled changes to take effect, because during the build, these references will be replaced with their quoted string value.

heroku config:set REACT_APP_HELLO='I love sushi!'

git commit --allow-empty -m "Set REACT_APP_HELLO config var"
git push heroku master

Only REACT_APP_ vars are replaced in create-react-app's build. To make any other variables visible to React, they must be prefixed for the build command in package.json, like this:

REACT_APP_HEROKU_SLUG_COMMIT=$HEROKU_SLUG_COMMIT react-scripts build

Runtime configuration

Supports only REACT_APP_ prefixed variables.

🚫🀐 Not for secrets. These values may be accessed by anyone who can see the React app.

Install the runtime env npm package:

npm install @mars/heroku-js-runtime-env --save

Then, require/import it to use the vars within components:

import React, { Component } from 'react';
import runtimeEnv from '@mars/heroku-js-runtime-env';

class App extends Component {
  render() {
    // Load the env object.
    const env = runtimeEnv();

    // …then use values just like `process.env`
    return (
      <code>Runtime env var example: { env.REACT_APP_HELLO }</code>
    );
  }
}

⚠️ Avoid setting backslash escape sequences, such as \n, into Runtime config vars. Use literal UTF-8 values only; they will be automatically escaped.

Custom bundle location

If the javascript bundle location is customized, such as with an ejected created-react-app project, then the runtime may not be able to locate the bundle to inject runtime variables.

To solve this so the runtime can locate the bundle, set the custom bundle path:

heroku config:set JS_RUNTIME_TARGET_BUNDLE=/app/my/custom/path/js/*.js

✳️ Note this path is a * glob, selecting multiple files, because as of create-react-app version 2 the bundle is split.

To unset this config and use the default path for create-react-app's bundle, /app/build/static/js/*.js:

heroku config:unset JS_RUNTIME_TARGET_BUNDLE

Add-on config vars

🚫🀐 Be careful not to export secrets. These values may be accessed by anyone who can see the React app.

Use a custom .profile.d script to make variables set by other components available to the React app by prefixing them with REACT_APP_.

  1. create .profile.d/000-react-app-exports.sh

  2. make it executable chmod +x .profile.d/000-react-app-exports.sh

  3. add an export line for each variable:

    export REACT_APP_ADDON_CONFIG=${ADDON_CONFIG:-}
  4. set-up & use Runtime configuration to access the variables

For example, to use the API key for the Filestack JS image uploader:

export REACT_APP_FILEPICKER_API_KEY=${FILEPICKER_API_KEY:-}

npm Private Packages

Private modules are supported during build.

  1. Setup your app with a .npmrc file following npm's guide for CI/deployment.

  2. Set your secret in the NPM_TOKEN config var:

    heroku config:set NPM_TOKEN=xxxxx

Terraform

Terraform is included to support declarative configuration of routing behavior with the Kong provider.

Any *.tf files present in the root of the app (main.tf & routes.tf by default) will be forcefully applied during release phase. All changes, additions, & destructions are auto-accepted. If any error occurs, the release fails.

Running Terraform commands

Example running one-off Terraform commands:

heroku run terraform show

Some commands require the local Kong Admin API to be on-line:

heroku run "heroku-buildpack-kong-background-start && terraform plan"
heroku run "heroku-buildpack-kong-background-start && terraform refresh"

Additional Terraform providers

Terraform can of course be used to provision & configure other providers' resources too including Heroku itself, but the force-apply technique used during release means that accidental mistakes in config can be destructive to pre-existing resources.

To use Terraform collaboratively for higher-level configuration on Heroku, check out:

Kong Admin API

Admin console

Use kong CLI and access Kong's HTTP/REST Admin API in a one-off dyno:

✏️ Replace $APP_NAME with the Heroku app name.

heroku run bash --app $APP_NAME

Run Kong in the background of the one-off dyno:

~ $ bin/heroku-buildpack-kong-background-start

Use curl to issue Admin API commands:

~ $ curl http://localhost:8001
~ $ curl http://localhost:8001/status
~ $ curl http://localhost:8001/services
~ $ curl http://localhost:8001/routes

Execute CLI commands:

⚠️ Some commands require the config file and others the prefix.

✏️ The $KONG_CONF variable is already defined.

~ $ kong migrations list -c $KONG_CONF
~ $ kong health -p /app/kong-runtime

Expose the Admin API

Kong Admin API has no built-in authentication. Its exposure must be limited to a restricted, private network. For Kong on Heroku, the Admin API listens privately on localhost:8001.

To make Kong Admin API accessible from other locations, let's setup a secure loopback proxy with key authentication, HTTPS-enforcement, and request rate & size limiting.

First, set a strong, cryptographic Admin Key into the Heroku config var:

heroku config:set TF_VAR_kong_admin_key=<your unique key>

Then, define Admin API config in the routes.tf file using the Kong Terraform provider to create a service, route, & plugins:

variable "kong_admin_key" {
  type = "string"
}

resource "kong_service" "kong_admin_api" {
  name     = "kong-admin"
  protocol = "http"
  host     = "127.0.0.1"
  port     = 8001
}

resource "kong_route" "kong_admin_api" {
  protocols  = ["https"]
  paths      = ["/kong-admin"]
  service_id = "${kong_service.kong_admin_api.id}"
}

resource "kong_plugin" "kong_admin_api_request_size" {
  name       = "request-size-limiting"
  service_id = "${kong_service.kong_admin_api.id}"

  config = {
    allowed_payload_size = 8
  }
}

resource "kong_plugin" "kong_admin_api_rate" {
  name       = "rate-limiting"
  service_id = "${kong_service.kong_admin_api.id}"

  config = {
    minute = 5
  }
}

resource "kong_plugin" "kong_admin_api_key_auth" {
  name       = "key-auth"
  service_id = "${kong_service.kong_admin_api.id}"

  config = {
    hide_credentials = true
  }
}

resource "kong_plugin" "kong_admin_api_acl" {
  name       = "acl"
  service_id = "${kong_service.kong_admin_api.id}"

  config = {
    whitelist = "kong-admin"
  }
}

resource "kong_consumer" "kong_admin_api_consumer" {
  username  = "heroku-admin"
}

resource "kong_consumer_plugin_config" "kong_admin_api_consumer_config_acls" {
  consumer_id = "${kong_consumer.kong_admin_api_consumer.id}"
  plugin_name = "acls"

  config = {
    group = "kong-admin"
  }
}

resource "kong_consumer_plugin_config" "kong_admin_api_consumer_config_key_auth" {
  consumer_id = "${kong_consumer.kong_admin_api_consumer.id}"
  plugin_name = "key-auth"
  
  config = {
    key = "${var.kong_admin_key}"
  }
}

Commit & deploy these routes.tf changes to the app.

Now, access Kong's Admin API via the protected, public-facing proxy:

✏️ Replace variables such as $ADMIN_KEY & $APP_NAME with values for your unique deployment.

# Set the key in the request header:
curl -H "apikey: $ADMIN_KEY" https://$APP_NAME.herokuapp.com/api/kong-admin/status

Troubleshooting

  1. Confirm that your app is using this buildpack:

    heroku buildpacks

    If it's not using crak-buildpack, then set it:

    heroku buildpacks:set mars/crak

    …and deploy with the new buildpack:

    git commit --allow-empty -m 'Switch to crak-buildpack'
    git push heroku master

    If the error still occurs, then at least we know it's really using this buildpack! Proceed with troubleshooting.

  2. Check this README to see if it already mentions the issue.

  3. Search our issues to see if someone else has experienced the same problem.

  4. Search the internet for mentions of the error message and its subject module, e.g. ENOENT "node-sass"

  5. File a new issue. Please include:

    • build log output
    • link to GitHub repo with the source code (if private, grant read access to @mars)

Version compatibility

This buildpack will never intentionally cause previously deployed apps to become undeployable. Using master as directed in the main instructions will always deploy an app with the most recent version of this buildpack.

Releases are tagged, so you can lock an app to a specific version, if that kind of determinism pleases you:

heroku buildpacks:set https://github.com/mars/crak-buildpack.git#v6.0.0

✏️ Replace v6.0.0 with the desired release tag.

♻️ Then, commit & deploy to rebuild on the new buildpack version.

Architecture πŸ™

This buildpack combines several buildpacks, specified in .buildpacks, to support zero-configuration deployment on Heroku:

  1. heroku/nodejs buildpack
    • installs node, puts on the $PATH
    • version specified in package.json, engines.node
    • node_modules/ cached between deployments
    • NODE_ENV at buildtime:
      • defaults to NODE_ENV=development to install the build tooling of create-react-app's dev dependencies, like react-scripts
      • honors specific setting of NODE_ENV, like NODE_ENV=test for automated testing in bin/test
      • but forces NODE_ENV=production to be development to ensure dev dependencies are available for build
  2. mars/create-react-app-inner-buildpack
    • production build for create-react-app
      • executes the npm package's build script; create-react-app default is react-scripts build
      • exposes REACT_APP_, NODE_, NPM_, & HEROKU_ prefixed env vars to the build script
      • generates a production bundle regardless of NODE_ENV setting
    • sets default web server config unless static.json already exists
    • enables runtime environment variables
  3. mars/terraforming buildpack
  4. heroku-community/kong buildpack

πŸš€ The runtime web process is bin/heroku-buildpack-kong-web, which launches Kong's Nginx web server. Processes may be customized by committing a Procfile to the app.

A Heroku Postgres database is used by both Terraform and Kong to persist their configuration and state.

About

βš›οΈπŸ’ Heroku Buildpack for create-react-app: host React.js web apps with a Kong API gateway

Resources

License

Stars

Watchers

Forks

Packages

No packages published