Wednesday, December 27, 2017

Deploy app to Heroku with separate frontend/backend servers

Leave a Comment

I'm currently trying to deploy an app to a single Heroku dyno which does not use create-react-app but has an express server for webpack and a rails API backend.

I'm having issues getting express to proxy my requests to the API (but works fine locally), here is the error from Heroku logs:

2017-12-03T16:00:18.436271+00:00 app[web.1]: Error: connect ECONNREFUSED 127.0.0.1:3005 2017-12-03T16:00:18.436308+00:00 app[web.1]:     at Object.exports._errnoException (util.js:1018:11) 2017-12-03T16:00:18.436309+00:00 app[web.1]:     at exports._exceptionWithHostPort (util.js:1041:20) 2017-12-03T16:00:18.436311+00:00 app[web.1]:     at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1090:14) 

I made sure to use buildpacks for both node and rails as described here.

Here is the relevant code:

Procfile

web: npm run start:prod api: bundle exec rails server --port=3005 --environment=production -b 127.0.0.1 

package.json

{   "name": "foo",   "version": "1.0.0",   "main": "index.js",   "license": "MIT",   "proxy": "http://127.0.0.1:3005/",   "engines": {     "node": "6.10.2",     "yarn": "0.24.5",     "npm": "5.5.1"   },   "scripts": {     "start": "NODE_ENV=development node server",     "start:prod": "yarn run build && NODE_ENV=production node server",     "build": "NODE_ENV=production webpack -p --config ./webpack.prod.js --progress --colors --display-error-details"   }, 

server.js

const express = require('express'); const proxy = require('express-http-proxy');  const app = express();  const port = process.env.PORT || 3000; const path = require('path') const webpack = require('webpack')  const proxyHost = '127.0.0.1'; const proxyPort = '3005';  app.use('/api', proxy(`${proxyHost}:${proxyPort}`));  const isProd = process.env.NODE_ENV === 'production'  let config  if (isProd) {   config = require('./webpack.prod.js') } else {   config = require('./webpack.dev.js') }  const publicPath = config.output.publicPath || '/'; const outputPath = config.output.path || path.resolve(process.cwd(), 'dist');  if (!isProd) {   console.log('Development env detected: Initializing hot reloading')   const webpackDevMiddleware = require('webpack-dev-middleware')   const webpackHotMiddleware = require('webpack-hot-middleware')   const compiler = webpack(config)    app.use(webpackHotMiddleware(compiler, {     log: console.log,     path: '/__webpack_hmr'   }))    app.use(webpackDevMiddleware(compiler, {     entry: config.entry,     publicPath: config.output.publicPath,     stats: {       colors: true     }   }))    app.use('*', function (req, res, next) {     const filename = path.join(compiler.outputPath, 'index.html')     compiler.outputFileSystem.readFile(filename, (err, result) => {       if (err) {         return next(err)       }       res.set('content-type', 'text/html')       res.send(result)       res.end()     })   })  } else {   app.use(publicPath, express.static(outputPath));   app.get('*', (req, res) => res.sendFile(path.resolve(outputPath, 'index.html'))); }  app.listen(port, (err) => {   if (err) {     console.log(err.message)   } else {     console.log(`Server Started at port ${port}`);   } }); 

Any help would be greatly appreciated!

EDIT FOR SOLUTION

So I was able to get to a solution based on the accepted answer below, but thought I'd update the post to give the specifics.

As pointed out in the answer, it seems Heroku uses a different dyno per process in your Procfile, which was why the frontend/backend servers were originally unable to communicate with each other.

To circumvent this, I simply created a dummy procfile which used foreman to initialize the real procfile:

Procfile (dummy used by Heroku)

web: foreman start -f StartProcfile 

StartProcfile (the actual processes)

web: npm run start:prod api: bundle exec rails server --port=3005 --environment=production 

It should be noted that in my case I ran into additional issues with exceeding the dyno memory cap AND taking longer than 60s to bind to the assigned heroku port. Turns out it was because I was including the build step as part of the web process, when I should have been using the postinstall hook in package.json.

package.json

  "scripts": {     "start": "NODE_ENV=development node server",     "start:prod": "NODE_ENV=production node server --optimize_for_size --max_old_space_size=460 --gc_interval=100",     "build": "NODE_ENV=production webpack -p --config ./webpack.prod.js --progress --colors --display-error-details",     "postinstall": "npm run build"   }, 

1 Answers

Answers 1

Each line in the Procfile ends up running in a separate dyno. If you need both processes to run on the same dyno (in this case web) then you need to either redefine web to call something like a shell script that launches the node server and then launches the rails server, or have the node boot process launch the rails server.

Full disclosure, I have never actually done what you are attempting. Binding the rails API server to port 3005 may not work even after making the changes outlined above. But, for sure, 127.0.0.1 in a web process will never be 127.0.0.1 in an api process since they will always run on different dynos. Hopefully that info helps.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment