Error while serving two node server based frontend via reverse proxy - node.js

I've a single domain and hence need to serve two different react applications running on two different node servers via nginx reverse proxy. Here is my nginx.conf file
server {
listen 80;
listen [::]:80;
client_max_body_size 128g; # allows larger files (like videos) to be uploaded.
location / {
proxy_pass http://192.168.0.105:8081;
}
location /admin {
proxy_pass http://192.168.0.105:8080;
}
}
What I am trying to achieve here is that if user lands on abc.com then he will be served react application from http://192.168.0.105:8081 while if user visits abc.com/admin he is served http://192.168.0.105:8080.
Both applications have different node servers serving react's bundle created by create-react-app. The node's server.js looks like this for both applications
const express = require("express");
const bodyParser = require("body-parser");
const path = require("path");
const cors = require("cors");
const app = express();
const qs = require("querystring");
const axios = require("axios");
const PORT = process.env.PORT || 8080;
const serverEnvironment = process.env.serverEnv || "dev";
app.use(cors());
app.use(express.static(path.join(__dirname, "build")));
app.use(bodyParser.json());
app.get("*", function(req, res) {
console.log("Requesting Admin UI");
res.sendFile(path.join(__dirname, "build", "index.html"));
});
app.listen(PORT, () => {
console.log(
`\nServer is being served in ${serverEnvironment} environment at port ${PORT}`
);
});
Everything works fine when I visit localhost:4000(4000 is port on which nginx is running) it serves me my expected portal. But when I visit localhost:4000/admin instead of serving admin application it ends up giving following error
Uncaught SyntaxError: Unexpected token '<' 2.0f3deea8.chunk.js:1
Uncaught SyntaxError: Unexpected token '<' main.a2e30dbb.chunk.js:1
When I checked network tab all the chunks of js and css are returning HTML instead of relevant js or css.
I also tried adding
location ~ .(static)/(js|css|media)/(.+)$ {
try_files $uri $uri/ /$1/$2/$3;
}
to nginx.conf but of no use. Am I missing something over here or is it not the correct way to reverse proxy 2 applications?

Are you seeing the content of index.html for your js/css in your network tab?
If yes, then it is usually caused by wrong base href in your index.html.
Try changing it to root.
<base href="/">
Other possible solution might be just adding this line to your nginx config.
include /etc/nginx/mime.types;

If you have base url /admin in node app too then proxy_pass should be to http://192.168.0.105:8080/admin; instead of http://192.168.0.105:8080;
Ideal scenario you can have your compressed react resource served from nginx at port 80 by copying it to html or any desired folder and just the different node apps proxied to different routes.

I would simply use a rewrite in your proxy location like:
server {
listen 80;
location / {
proxy_pass http://192.168.0.105:8081;
}
location /admin {
rewrite /admin/(.*) /$1 break;
proxy_redirect off;
proxy_pass http://192.168.0.105:8080;
}
}
With this configuration a user visiting /admin will be send to your admin ReactJS Application. The NGINX will rewrite the uri before submitting it to the upstream server.

Related

Express server and NGINX proxy only serving index.html, no other files

I have a very simple express app which serves everything in the build folder for my react app. Here's the entire thing:
const express = require("express");
require("dotenv").config();
const app = express();
const port = process.env.PORT || 5000;
app.use(express.static(process.env.PUBLIC_DIR));
app.use(express.json());
app.listen(port, () => {
console.log(`Server is running on port ${port}...`);
});
When running this on my local machine, it works fine. No issues.
On my EC2 instance, I'm using NGINX as a reverse proxy. Here's what the config in my default sites-available file looks like:
location / {
proxy_pass http://localhost:5000/;
}
location /upvotes {
proxy_pass http://localhost:5002/;
}
When you just go to the main site, another express app on 5000 serves a totally unrelated Gatsby project. That works fine, no issues.
When you go to /upvotes, this express app on 5002 does serve the index.html file perfectly fine, but it doesn't serve any of the accompanying .js and .css files that are also in that directory.
Does anyone know why this could be happening?
I eventually gave up and combined the two express apps into one and handled the /upvotes route using express. 🤷

Serving static React app from server, crashing on refresh

I have made a React app which is hosted on the same machine as my API server as guided by this tutorial.
My directories are set up in such way that
- root
- server.js (entry for the API server)
- app (for all back-end stuff)
- client (directory for React app)
- public (static files used in the front-end)
- src (for all front-end stuff)
- build (directory containing static build)
- index.html (entry for front-end)
- ... (other bundled stuff)
And I am serving a static copy of my React app in below method.
// server.js
const app = express();
if (process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'));
}
app.use('/api', routes); // for API routes
I am having a problem where, when I boot this up locally, (with NODE_ENV=production) everything smooth and page refreshes do not break the app.
However, after I have deployed this to Elastic Beanstalk, page refreshes break the app with this html error message being displayed in the browser.
Cannot GET /{whichever url path I had prior to refresh}
I could see in the logs that browser tried to send a request to the server with GET /{url_pathname}
I initially suspected something going funny with having both React router and Express router but then again, I am confused why this is not consistent with the case in the localhost.
If you are using NGINX, you need to update the configuration file located at
/etc/nginx/sites-available
location / {
try_files $uri $uri/ /index.html$is_args$args;
}
Or this if your app is not located in the default directory, for example "app-location" you would get this.
location /app-location {
root /var/www;
index index.html;
try_files $uri $uri/ /app-location/index.html;
}
And restart nginx
sudo service nginx restart
If you are running a react app that basically runs on a single index file, then your middleware router should have a catch-all route that always points to that index file somewhere.
app.get('/*',(req, res) => {
res.sendFile(path.join(__dirname + '/client/build/index.html'))
})
Please note that the current example is dependent on the path module...So:
import path from 'path'
// or
const path = require('path')

Express + Nginx. Can't serve static files

This is my project folder
/
public/
index.html
main.js
adaptor.js
main.css
node_modules/
socket.io/
index.js
and this is static file configuration in my index.js
app.use(express.static(path.join(__dirname, '/public')));
app.use(express.static(path.join(__dirname, '/node_modules')));
app.get('/', (req, res)=>{
res.sendFile(path.join(__dirname, 'public', '/index.html'));
})
and this is my index.html
<script src="/socket.io/socket.io.js" charset="utf-8"></script>
<script src="/adapter.js" charset="utf-8"></script>
<script src="/main.js" charset="utf-8"></script>
and this is my nginx configuration
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ /index.html =404;
proxy_pass http://localhost:8080;
}
But I am getting 404 on all the scripts. And an another strange thing is that mime-type on those files is set to text/HTML
What am I doing wrong here?
I have a project, with an identical project structure, and it has the same configuration, but it works for it, and it isn't working in this case.
You don't need to configure Nginx and Express to serve static files. Both are capable of doing the job independently, but it is up to you to choose.
For these examples I am assuming the same file structure provided in your question.
In both configurations, load files from / in HTML:
<script src="/main.js"></script> <!-- loads from myapp/public/main.js -->
Express as static file server, Nginx as reverse proxy
express app
const express = require('express');
const app = express();
app.use(express.static('public')); // notice the absence of `__dirname`, explained later on
// if the request URL doesn't match anything in the 'public' folder,
// it will start searching here next.
app.use(express.static('some_other_folder'));
// from my testing, express will automatically locate index.html if
// it is in a static folder. Declaring a route is not required.
/*
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
*/
app.listen(8080);
// GET / => index.html
// GET /main.js => main.js
Quick side note: the use of __dirname in express.static() is not required. Under the hood (actually, it's here on line 65) , Express uses the native Node.js path.resolve(). From the docs:
The path.resolve() method resolves a sequence of paths or path segments into an absolute path.
Using path.resolve(__dirname, 'public') actually returns the same as path.resolve('public'). I am thinking that your problem was really telling Nginx to serve static files AND proxy the same requests to Express. OK, on to the rest of my answer.
Nginx configuration
server {
listen 8081; # must be different port from Express
server_name example.com;
location / {
# hand ALL requests back to express
proxy_pass http://localhost:8080;
}
}
Nginx as static file server, Express as API server
Nginx configuration
server {
listen 8081;
server_name example.com;
location / {
root /path/to/website/public;
index index.html;
try_files $uri $uri/ #express; # instead of 404, proxy back to express using a named location block;
# source: https://stackoverflow.com/a/15467555/8436941
}
location #express {
proxy_pass http://localhost:8080;
}
}
Express app
const express = require('express');
const app = express();
// actually, Nginx has already taken care of static files. You can still define other routes for API functions for example.
app.get('/my/api', (req, res) => {/* query database, etc. */});
app.listen(8080);
Hope this helps!
Removing the try_files directive from the nginx configuration file solved the issue for me.
app.use(express.static(path.join(__dirname, '/public')));
app.use(express.static(path.join(__dirname, '/node_modules')));
I think those two lines are the reasons why it raises an error.
Can you delete the second one and give it a try? Maybe your express app sets the primary static serving folder "node_modules" and that is why the app cannot serve your main script files.
Also, I don't think it is good idea to serve node_modules directory as static folder. I think in your setup, "bower" seems more appropriate choice as a javascript package manager.
Thanks
Add a . before the / to denote a relative path:
<script src="./socket.io/socket.io.js" charset="utf-8"></script>
<script src="./adapter.js" charset="utf-8"></script>
<script src="./main.js" charset="utf-8"></script>
If you have this structure:
myApp
app.js
-public/
-js/
In app.js you should do like this:
self.app.use(express.static(path.join(__dirname, 'public')));
staticRoutes.forEach(route=> {
self.app.use(BASEPATH+route,express.static(path.join(__dirname, 'public')));
});
where staticRoutes is an array like
staticRoutes=[
'services/'
'about/'
];
and in the html (development)
<script type="text/javascript" src="js/util.js"></script>
About Us
while when in production is safer using the full path + protocol.

Hubot/Express: disable basic auth in a specific route

I am using Hubot and I've defined the environment variables EXPRESS_USER and EXPRESS_PASSWORD to enable basic authentication. Hubot uses express and basically it's
setupExpress: ->
user = process.env.EXPRESS_USER
pass = process.env.EXPRESS_PASSWORD
stat = process.env.EXPRESS_STATIC
express = require 'express'
app = express()
app.use (req, res, next) =>
res.setHeader "X-Powered-By", "hubot/#{#name}"
next()
app.use express.basicAuth user, pass if user and pass
app.use express.query()
app.use express.bodyParser()
app.use express.static stat if stat`
I want to expose an HTTP command in a script that doesn't need basic auth. However I'm not able to change change the code in Hubot where express it's being initialized
robot.router.get '/some-anonymous-path', (req, res) ->
console.log 'Should be here without need to authenticate
Does anyone know if it's possible to do it in expressjs.
Thanks in advance
Bruno
How about putting nginx in front of your Hubot? Then you can also add SSL, only allow access to specific paths, rewrite urls or even serve static content created by hubot scripts. A simple example nginx.conf block:
upstream hubot {
server localhost:8080;
}
server {
listen 80;
satisfy any;
allow 127.0.0.1;
deny all;
location ~ ^/(public|obscured) {
allow all;
proxy_pass http://hubot;
}
location ~ ^/(static) {
auth_basic "Restricted";
auth_basic_user_file htpasswd;
root /www;
}
location ~ {
auth_basic "Restricted";
auth_basic_user_file htpasswd;
proxy_pass http://hubot;
}
}
Then toss an htpasswd pair in /etc/nginx/htpasswd and in your hubot init script set BIND_ADDRESS=localhost (default bind is 0.0.0.0) and you'll be off to the races.

Nginx X-Accel-Redirect not working with node.js

I'm trying to set up authorized file access on nginx backed by node.js. For some reason all the examples don't work for me. I'm trying to server files from /data/private/files
My nginx configuration:
...
server {
listen 4000;
server_name localhost;
location / {
proxy_pass http://127.0.0.1:3000/;
}
location /files {
root /data/private;
internal;
}
My node server.js:
var http = require('http');
http.createServer(function (req, res) {
console.log(req.url);
res.end('works');
}).listen(3000);
When I request http://localhost:4000/xyz then the request is correctly being passed on to node. When I request http://localhost:4000/files/test.jpg I just get a 404 and nothing gets passed to node. What am I doing wrong? When I commend out internal then test.jpg gets served correctly by nginx directly, so I assume the paths are correct?
I'm pretty sure I had this working at some point before but on a different server somewhere maybe with a different node and nginx version. Tried it with nginx 1.6.0 and 1.2.6, node v0.10.21. I've also added all the proxy_set_header and proxy_pass options that you find in all the examples, nothing works. I'm running this in a Vagrant based Ubuntu VM right now, but doesn't work on Mac either.
I know that I have to set the header through res.setHeader("X-Accel-Redirect", req.url);, but that's not the issue here as I don't even get to that phase where I could set the required header in node.
You misunderstand how internal and X-Accel-Redirect work.
The main idea is that you go to some URL which is proxied to app. Then app decides whether you should get access to file or not. In former case it response with X-Accel-Redirect to protected url (one with internal).
So you should go to some other URL, e.g. http://localhost:4000/get/files/test.jpg and your application could look like this:
var http = require('http');
http.createServer(function (req, res) {
if (req.url.indexOf('/get/files/') == 0) {
if (userHasRightToAccess()) {
res.setHeader('X-Accel-Redirect', res.url.slice(4));
res.end('');
} else {
// return some error
}
} else {
console.log(req.url);
res.end('works');
}
}).listen(3000);

Resources