node.jsiiscreate-react-appiis-8iisnode

How to serve Node Express with React frontend from IIS using IISNode


I am in the final stages of my project. I have an operating website running from NodeJS Express which will serve the built create-react-app frontend. Now I need to configure it to run from IIS using IISNode. I have followed a number of directions but without success. The reason for IIS is so that the site will be served without the user being logged in and available so long as the server (Windows server 2012 R2, IIS 8) is running.

I have read of numerous "hacks" which people used to get their own site running.

The node is listening on process.env.PORT || 8000. When I am running directly from node (npm run start) there is no difficulty, but when attempting to do the same from IIS it gives me errors finding the backend URLs. When attempting to run React as a separate site I get a 404 error searching for the initial API, while when running it with the frontend hosted in the "public" folder I am getting 403.14: Cannot list directory contents.

I currently don't even know which code to display in order to solve this issue, but I suspect the web.config might be part of it. I am including that along with my bin/www file from node.

//   /bin/www

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('node-backend:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '8000'); // 
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
  console.log('Listening on ' + bind);
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>

        <handlers>
            <add name="iisnode" path="app.js" verb="*" modules="iisnode" />
        </handlers>\
<!-- Removed in an effort to get a valid URL response -->
        <!-- <rewrite>
            <rules>
                <rule name="api">
                    <match url="/*" />
                    <action type="Rewrite" url="/*:8000" />
                </rule>
            </rules>
        </rewrite> -->

        <security>
            <requestFiltering>
                <hiddenSegments>
                    <add segment="node_modules" />
                </hiddenSegments>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>

Please let me know what other, if any, data is needed to solve this problem.

*** EDIT *** Some progress made by altering web.config enabling the section and pointing all requests to /app.js. Here is the error message I am now receiving:

iisnode encountered an error when processing the request.

HRESULT: 0x2
HTTP status: 500
HTTP subStatus: 1002
HTTP reason: Internal Server Error
You are receiving this HTTP 200 response because system.webServer/iisnode/@devErrorsEnabled configuration setting is 'true'.

In addition to the log of stdout and stderr of the node.exe process, consider using debugging and ETW traces to further diagnose the problem.

The node.exe process has not written any information to stderr or iisnode was unable to capture this information. Frequent reason is that the iisnode module is unable to create a log file to capture stdout and stderr output from node.exe. Please check that the identity of the IIS application pool running the node.js application has read and write access permissions to the directory on the server where the node.js application is located. Alternatively you can disable logging by setting system.webServer/iisnode/@loggingEnabled element of web.config to 'false'.

The 2 links it refers to were both written over 10 years ago and gave no helpful information that I could discern.

I have also tried wrapping my app.js code in a try/catch block and using fs to write the error log to a text file, with no success. I believe that it is not even accessing the app.js before throwing the error. All paths have been given a blanket "full control" to "EVERYONE" just to completely eliminate permissions as an issue (for now).

Updated web.config:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>

        <handlers accessPolicy="Execute">
            <add name="iisnode" path="app.js" verb="*" modules="iisnode" resourceType="Either" requireAccess="Execute" />
        </handlers>
        <rewrite>
            <rules>
                <rule name="api">
                    <match url="/*" />
                    <action type="Rewrite" url="/app.js/*" />
                </rule>
            </rules>
        </rewrite>

        <security>
            <requestFiltering>
                <hiddenSegments>
                    <add segment="node_modules" />
                </hiddenSegments>
            </requestFiltering>
        </security>
        <tracing>
            <traceFailedRequests>
                <add path="*">
                    <traceAreas>
                        <add provider="ISAPI Extension" verbosity="Verbose" />
                        <add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,FastCGI,WebSocket,ANCM,Rewrite,RequestRouting,iisnode" verbosity="Verbose" />
                    </traceAreas>
                    <failureDefinitions statusCodes="403.14" verbosity="Warning" />
                </add>
            </traceFailedRequests>
        </tracing>
        <iisnode watchedFiles="*.js" loggingEnabled="true" logDirectory="iisnode" devErrorsEnabled="true" />
    </system.webServer>
</configuration>

Solution

  • I have dramatically modified my approach and am now running the application as 2 sites on IIS. The backend is running on port 90 and the front end (REACT) is running on port 80 with the requests to port 90 hard coded in the code.

    Progressing now to determine why cors() isn't functioning correctly.