Serving multiple react apps with client-side routing in Express - node.js

I have different software products for one single service, which needs to be deployed to a single server. The clients are built with react, with a build setup by create-react-app, while the server runs Node.js and Express.
When I serve a single application from the server it is done the following way:
// App.js
// ...
// Entry point for data routes (API)
app.use('/data', indexRoute);
if(process.env.NODE_ENV !== 'development') {
app.use(express.static(path.join(__dirname, 'build-client')));
app.get('/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
}
I want to be able to serve multiple apps from the server. How should I do that?
What I tried is to wire in different static paths for the assets and separate the clients with different names, although it did not work. Like this:
// App.js
// ...
// Entry point for data routes (API)
app.use('/data', indexRoute);
if(process.env.NODE_ENV !== 'development') {
app.use(express.static(path.join(__dirname, 'build-client')));
app.use(express.static(path.join(__dirname, 'build-admin')));
app.get('/client/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
app.get('/admin/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
}
I have also tried to do it this way, but Express throw Error: No default engine was specified and no extension was provided:
if(process.env.NODE_ENV !== 'development') {
// Admin paths
app.use('/admin', express.static(path.join(__dirname, 'build-admin')));
app.get('/admin/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-admin' , 'index.html'));
});
// Site paths
app.use('/', express.static(path.join(__dirname, 'build-client')));
app.get('/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
}
How could I accomplish this or something similar?

After some tinkering I was able to achieve this without using virtual hosts. I used the first idea you gave in the question, except I left the main app at the root (i.e. /).
// when going to `/app2`, serve the files at app2/build/* as static files
app.use('/app2', express.static(path.join(__dirname, 'app2/build')))
// when going to `/`, serve the files at mainApp/build/* as static files
app.use(express.static(path.join(__dirname, 'mainApp/build')))
// These are necessary for routing within react
app.get('app2/*', (req, res) => {
res.sendFile(path.join(__dirname + '/app2/build/index.html'))
})
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/mainApp/build/index.html'));
});
After this, I went into mainApp/package.json and added
"proxy": "http://localhost:4141"
:4141 is the port that the express server is running on. This line will make calls to fetch('/some/route') go back to the server instead of into your react app itself.
Finally, we go to app2/package.json and add
"proxy": "http://localhost:4141/app2",
"homepage": "/app2"
I believe that the key here is the "homepage" key. The way I understand it, when react starts it searches for some static files at its homepage, and without the "homepage" piece I was only able to get either a blank white screen or the mainApp.
I hope this helps someone out there!
EDIT
I have since changed from serving my create-react-apps through my express server to serving them through netlify. Now I don't need to worry about this express setup, or the homepage key in package.json. The express server lives by itself, and the react apps can still both use the same api, and deployment is much easier. Setup with netlify is trivial.

After struggling for a while with this problem I've found a possible solution without compromising the original setup.
We used Express vhost package to setup handling of requests through virtual domains.
When you create your app instance, you should initialize as many apps with express as you want to expose separately (in our case its three separate apps plus the original app instance)
// Create an express instance
const app = express();
const appAdmin = express();
const appClient = express();
const appVendor = express();
After that you need to install vhost and import it. Then with specifying the static folder for each app you can handle serving the static files separately, while the remaining part deals with handling the request for the given subdomains respectively.
appAdmin.use(express.static(path.join(__dirname, 'build-admin')));
appClient.use(express.static(path.join(__dirname, 'build-client')));
appVendor.use(express.static(path.join(__dirname, 'build-vendor')));
appAdmin.use((req, res, next) => {
return res.sendFile(path.resolve( __dirname, 'build-admin' , 'index.html'));
});
appClient.use((req, res, next) => {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
appVendor.use((req, res, next) => {
return res.sendFile(path.resolve( __dirname, 'build-vendor' , 'index.html'));
});
app.use(vhost('domain.com', appClient));
app.use(vhost('www.domain.com', appClient));
app.use(vhost('a.domain.com', appAdmin));
app.use(vhost('b.domain.com', appVendor));
Don't forget to add the desired subdomains in your domain's DNS registry. Example:
...records
CNAME vendor #
CNAME admin #

Related

Cannot Get/ when express app is deployed on heroku

my dir structure
/src
--/public
--/server.ts
--package.json
--package-lock.json
above is my director structure
app.use(express.static(__dirname + "/public/"));
// app.use(express.static("/public/"));
const path = require("path");
app.get("/", (req, res, next) => {
// res.sendFile(path.join(__dirname, + "public", 'index.html'));
res.sendFile(__dirname , "index.html");
//res.send('Testing one two');
});
const port = process.env.PORT || '5005';
app.listen(port, () => console.log("Server running on port 5005"));
when I run the above code, it works well on my local machine but won't work when it is deployed to Heroku,
I tried just passing a string like this and it worked, but when I want to render a static file like the HTML file it wont work on heroku, any help? i think the problem is my directory structure
app.get("/", (req, res, next) => {
res.send('Testing one two');
});
If I recall correctly, express.static middleware is separate from res.sendFile. In other words, even if you set express.static to public, it will not do anything to res.sendFile, as it takes the first parameter as a path.
In my humble opinion, it would be better if you were to use an absolute path, like the following snippet below.
const path = require('path');
/** Code here... **/
app.get("/", (req, res, next) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
Explanations:
path.join is an utility to join path segments into one path. It is cross-platform compatible.
__dirname will get the current directory that the script is running from.
Further reading: Express methods.
You just need to give the address of the index.html file in the path for the code mentioned below and paste this code at the end of the inside of the express file and everything will work perfectly fine and you are good to go.
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, './public/index.html'));
});

NodeJS, Express. Cannot upload static content

I've tried to write node server which would run React app created by create-react-app. Actually, something strange happens and I don't have any clue what I'm doing wrong (run app as node server/index.js):
export default (app, dirname) => {
app.use(favicon(path.join(dirname, '..','build', 'favicon.ico')));
app.use(express.static(path.join(dirname, '..','build')));
// initialize routers
bootRotes(app);
if (process.env.NODE_ENV === AVAILABLE_ENVIROMENTS.DEVELOPMENT) {
expressBootDev(app, dirname);
} else {
app.get('/*', (req, res) => {
res.sendFile(path.join(dirname, '..', 'build', 'index.html'));
});
}
}
build folder contains build react app which created the following command npm run build
Strange things are happening when after uploading index page it tries to upload static content. For example http://localhost:5000/static/js/2.30e86b6e.chunk.js. Browser just adds / after each static content url and it turns to http://localhost:5000/static/js/2.30e86b6e.chunk.js/ and of course this url doesn't match to express.static middleware.
Moreover, I've checked via Postman, that url GET http://localhost:5000/static/js/2.30e86b6e.chunk.js withot / at the end provides content which is expected.
I work with PRODUCTION env, it means that expressBootDev doesn't have any impacts.
Has anybody has the same issue? I've spent whole day and don't know hopw to fix it.
When I'm creating a simple code in a root app folder with almost the same logic and run as node server.js and it works as expected:
//server.js
const express = require('express');
const favicon = require('express-favicon');
const path = require('path');
const port = process.env.PORT || 8080;
const app = express();
app.use(favicon(__dirname + '/build/favicon.ico'));
app.use(express.static(__dirname));
app.use(express.static(path.join(__dirname, 'build')));
app.get('/ping', function (req, res) {
return res.send('pong');
});
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(port);
And I don't see any principal difference
var fs = require('fs');
var express = require('express');
var router = express.Router();
// GET: Sent some basic info for usage
router.get('/', (req, res, next) => {
var fname = __dirname + '/../public/index.html';
var val = fs.readFile( fname, 'utf8', ( err, data) => {
//send can only be called once, write can be called many times,
// in short res.send(msg) == res.write(msg);res.end();
res.writeHeader(200, {"Content-Type": "text/html"});
res.write(data);
res.end();
});
});
module.exports = router;
Here is the example how you can do a static file serving with node.
https://github.com/msatyan/ApiServe1/blob/master/routes/index.js
The full project is
https://github.com/msatyan/ApiServe1
FYI: Node.js with HTTP1 is not an efficient for static file serving by design, I believe HTTP2 support in node has addressed this problem. The reason for inefficiency with HTTP1 is that it has to take the file content read at native layer to JavaScript layer and then send it through HTTP server.

Prevent Angular 6 router from overriding routes defined in Express Server

How do I prevent Angular routing from interferring with routes from an Express Node Server?
I'm setting up some routes ( to node-RED middleware) in my Express server:
server.js
// Serve the editor UI from /red
app.use(settings.httpAdminRoot,RED.httpAdmin);
// Serve the http nodes UI from /api
app.use(settings.httpNodeRoot,RED.httpNode);
// Angular DIST output folder
app.use(express.static(path.join(__dirname, 'dist')));
// Send all other requests to the Angular app
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.js'));
});
but in my router module they get always overridden ( with our without the default path redirection)
routing.module.ts
const appRoutes: Routes = [
{
path: "example-page",
loadChildren: "../modules/example/example.module#ExampleModule?sync=true"
},
// Last routing path specifies default path for application entry
/* {
path: "**",
redirectTo: "/example-page",
pathMatch: "full"
},*/
];
I'm only providing this little code because I want to ask in general how do I prevent Angular from interferring with routes defined in an Express server and what is the best practice for routing in an Express/ Node.js + Angular + AspNetCore + Webpack app.
If you're using Angular, then let Angular handle all pages. This code takes care of that and hence angular is handling all routes.
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.js'));
});
If you want express to handle some routes instead of angular, handle that route before handling angular route as follows:
app.get('/some-non-angular-path', (req, res) => {
//code to handle this path. once it is handled here, angular won't be involved
//non-angular-paths should come first in the order
});
app.get((req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.js'));
});

React App giving 404 on main js and css

I built a react app using "react-scripts". The application runs perfectly on my local development server but when I deploy to my actual server the applications seems to not find the main JS and CSS files being compiled. I get 404 on both.
Following is the information that might help.
The files on the server are located at
ads/build/static/js and ads/build/static/css || respectively
The 404s I am getting are on the following files:
https://www.example.com/ads/build/static/css/main.41938fe2.css
https://www.example.com/ads/build/static/js/main.74995495.js
Here is how my server is configured:
const express = require('express');
const path = require('path');
const app = express();
const favicon = require('serve-favicon');
//favicon
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.get('/ads', function (req, res) {
app.use(express.static(path.join(__dirname, 'build/static')));
console.log(path.join(__dirname, 'build'));
res.sendFile(path.join(__dirname, '/build/index.html'));
});
app.listen(9000);
In my package.json I have also included the homepage parameter as:
"homepage" : "https://www.example.com/ads
UPDATE
When I run the app on the server itself with the following path:
dedicated-server-host:9000/static/js/main.74995495.js that renders the JS file correctly
Is there some configuration that I am missing, the routing doesn't seem to be working. Please advise.
Use some indentation so you will see error like this:
app.get('/ads', function (req, res) {
app.use(express.static(path.join(__dirname, 'build/static')));
console.log(path.join(__dirname, 'build'));
res.sendFile(path.join(__dirname, '/build/index.html'));
});
You are setting the static route inside of the /ads handler, will add a new express.static route handler on every GET /ads request.
This will set the static route on server startup:
app.use(express.static(path.join(__dirname, 'build/static')));
app.get('/ads', function (req, res) {
console.log(path.join(__dirname, 'build'));
res.sendFile(path.join(__dirname, '/build/index.html'));
});
or:
app.get('/ads', function (req, res) {
console.log(path.join(__dirname, 'build'));
res.sendFile(path.join(__dirname, '/build/index.html'));
});
app.use(express.static(path.join(__dirname, 'build/static')));
But make sure that you get the path right - for example you may need:
app.use('/ads/build/static', express.static(path.join(__dirname, 'build/static')));
if you want the URL in your question to work.
To make it much simpler, you could use just this single handler to make express.static handle both the css/js and index.html at the same time:
app.use('/ads', express.static(path.join(__dirname, 'build')));
and change your index.html to use:
https://www.example.com/ads/static/css/main.41938fe2.css
https://www.example.com/ads/static/js/main.74995495.js
instead of:
https://www.example.com/ads/build/static/css/main.41938fe2.css
https://www.example.com/ads/build/static/js/main.74995495.js
Sometimes getting your paths structure right in the first place can make your route handlers much easier.

Intercept request for a static file in express.js

I have a node server, that serves static files in a PUBLIC folder like this:
var app = express();
app.listen(port);
app.use(compression());
app.use(express.static(__dirname + '/PUBLIC'));
There is a json file, let's say important.json that is located in /PUBLIC folder. This is being served as a static file
Now, I want to intercept request for this /PUBLIC/important.json, so that I can programatically return a random json structure instead.
None of the followings works:
app.get('/PUBLIC/important.json', function(req, res) {
console.log("caught1!")
});
app.get(__dirname + '/PUBLIC/important.json', function(req, res) {
console.log("caught2!")
});
app.get('important.json', function(req, res) {
console.log("caught3!")
});
How can I intercept request for that partically static file?
As the express.static middleware does not call the next middleware using next(), the definition order is important. You have to define your own middleware before using express.static.
app.get('/PUBLIC/important.json', (req, res, next) => {
console.log('caught');
next();
});
app.use(express.static(__dirname + '/PUBLIC'));
Could you tell us a bit more about your stack ?
Are you using nginx / apache to proxy_pass the traffic to your nodejs server ?
Are you just running your app with "node app.js"
Let's try to add this simple route in your application :
app.get('/', function (req, res) {
res.send('Hello World!');
});
And try to access it by removing URI parameters ? Does the "Hello world" show up ?
I just want to be sure the traffic is actually treated by your node app.
Your route definition is supposed to work for your actual request.

Resources