Express.js and intercepting .js requests and serving .gz instead - express

So, I am using webpack to compress/gzip my js files. Then I want to use my Express server to serve those up when a .js request comes in. I am having a devil of a time getting this to work in production. I am able to in dev. I feel it has to do with how I am setting my static files. Assitance?
app.use(express.static(path.join(__dirname, 'build')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
// THIS is not working
app.get("*.js", function (req, res, next) {
req.url = req.url + '.gz';
res.set('Content-Encoding', 'gzip');
next();
});
I tried positioning above, middle etc.. of the above code. Not working. I know my webpack is building it as I see the output in the build folder. I just can't seem to get my express server to serve up the .gz version.

My guess is that in production you are running version 2.x and in dev you are using 3.x.
As per http://51elliot.blogspot.com/2012/08/serve-gzipped-files-with-expressjs.html you can see that...
For 2.x
// basic URL rewrite to serve gzipped versions of *.min.js files
app.get('*', function (req, res, next) {
req.url = req.url + '.gz';
res.header('Content-Encoding', 'gzip');
next();
});
and for 3.x:
app.get('*', function (req, res, next) {
req.url = req.url + '.gz';
res.set('Content-Encoding', 'gzip');
next();
});
NOTE: I have not tried this, just a hunch.

Related

Serve Entire React CRA from AWS S3 bucket using Express

I am basically trying to use express as a sort of reverse proxy. My end goal is to serve up different react CRA bundles to different users. Right now though I am just working on a Proof of Concept to see if it is even possible to do this.
TLDR Goal:
use express to point to a specific CRA bundle stored in a s3 bucket and serve it
This is the code I am using in express:
app.get('/*', function (req, res) {
const bucketParams = {
Bucket: 'some-dumb-bucket',
Key: 'cra/index.html'
};
s3.getObject(bucketParams)
.on('httpHeaders', function (statusCode, headers) {
res.set('Content-Length', headers['content-length']);
res.set('Content-Type', headers['content-type']);
res.set('Last-Modified', headers['last-modified']);
this.response.httpResponse.createUnbufferedStream()
.pipe(res);
})
.send();
})
The problem I am encountering is that all of my content is coming back with wrong headers. When I go into s3 and view the metadata it has the right headers so why is it fetching all the headers as "text/html"?
I figured out what I was doing wrong! It was looping through and grabbing the same index.html headers. Fix:
app.get('/*', function (req, res) {
const bucketParams = {
Bucket: 'some-dumb-bucket',
Key: 'auth/index.html'
};
if (req.url && req.url !== '/') {
bucketParams.Key = `auth${req.url}`;
} else
bucketParams.Key = `auth/index.html`;
// send the assets over from s3
s3.getObject(bucketParams)
.on('httpHeaders', function (statusCode, headers) {
res.set('Content-Length', headers['content-length']);
res.set('Content-Type', headers['content-type']);
res.set('Last-Modified', headers['last-modified']);
res.set('ETag', headers['etag']);
this.response.httpResponse.createUnbufferedStream()
.pipe(res);
})
.send();
});
Code could be a tiny bit cleaner but PoC working.

Multiple Errors serving HTML pages with Express

I'm trying to route pages using Express, but I can't seem to route from my main page. I'm using HTML as the view engine and I feel like everything is set up properly, but I'm seeing the error:
"TypeError: path must be absolute or specify root to res.sendFile"
Here is my file structure:
root
-public
--images
--index.html
--main.css
--sponsors.html
--team.html
app.js
I've tried multiple file routes, res.render versus res.sendFile.
var express = require("express")
var app = express()
const port = 3000
var http = require("http")
const path = require('path')
app.set("view engine", "html")
app.listen(port, () => console.log(`Example app listening on port
${port}!`))
app.use(express.static(__dirname + '/public'));
app.get('/sponsors', function(req, res) {
res.sendFile('sponsors')
})
app.get('/', function(req, res) {
res.sendFile('index')
})
app.get('/team', function(req, res) {
res.sendFile('team')
})
To render from a view engine, use res.render(), not res.sendFile().
And, your view engine will need to be able to find a file with that name and an appropriate file extension in the path for the view engine.
If you want to use res.sendFile() without the view engine, then you must specify an actual filename or include the root option that tells it where to look. You must also use the actual file extension on the filename.
Remove the following route method.
// remove the following code
app.get('/', function(req, res) {
res.sendFile('index')
});
Update the default path to the static method you had already written.
app.use('/', express.static(__dirname + '/public'));

CRA embedded in express app breaks express routing

I have created an CRA app and have a couple express routes loading the CRA build files, for example:
app.get('/files', async (req, res, next) => {
...
try {
res.format({
html: function() {
const fileLoc = './public/react_ui/build/index.html';
const stream = fs.createReadStream(path.resolve(fileLoc));
stream.pipe(res);
}
});
} catch (e) {
next(e);
res.redirect(SEE_OTHER.http_status, '/login');
}
});
Prior to added the CRA, the express app exposed the /public folder like this:
// access to express App code files
app.use(express.static(__dirname + '/public'));
Now that I have the CRA app embedded, I wanted to expose the build files like this, otherwise the index.html file created by building the CRA does not know where the /static/js/* are:
// access to React App build files
app.use(express.static(__dirname + '/public/react_ui/build'));
However, it breaks the express routing. For instance, when I logout of the app, it is supposed to send me to the endpoint / and this checks if I am logged in or not, if not, then it is supposed to send me to the login page like this:
app.get('/', function(req, res) {
...
isLoggedIn(req, function(status) {
switch (status.status) {
case 200:
res.redirect(303, '/loader');
break;
default:
res.redirect(303, '/login');
}
});
});
However, this is what is breaking. If I remove the command to expose the /build folder above, then the routing works again and I am sent to the login page, but accessing the CRA pages breaks, because the build files are NOT FOUND.
// access to React App build files - if removed, routing works again
app.use(express.static(__dirname + '/public/react_ui/build'));
Does anyone have any suggestions as to why this is happening? I don't know if this is a react app issue, an express issue, or something else. Any insights would be helpful.
You have conflicting routes.
app.js
app.use('/', express.static(__dirname + 'path/to/static/build'));
// Dont use '/' as it used for static route.
app.use('/auth', (req, res) => {
...
isLoggedIn(req, function(status) {
switch (status.status) {
case 200:
res.redirect(303, '/loader');
break;
default:
res.redirect(303, '/login');
}
});
})
Note you can use whatever route for static build. I have given general convention.

Express: serve static files on subroute

I'm trying to get routing work using Express and create-react-app.
My goal is to address the user to the homepage of the application when the URL is / and to the login page when the URL matches /login.
In my server.js I have two routes defined:
var mainRoutes = require("./routes/mainRoutes");
var apiRoutes = require("./routes/apiRoutes");
[...]
app.use("/", mainRoutes);
app.use("/api", apiRoutes);
While apiRoutes contains all the api routing definitions, mainRoutes is responsible for the main navigation (at least this was the idea):
var express = require("express");
var path = require("path");
let router = express.Router();
router.route("/").get((req, res, next) => {
res.sendFile("index.html", { root: "./client/build/" });
});
router.route("/login").get((req, res, next) => {
res.send("This is the login page");
});
module.exports = router;
Somewhere I read about serving the static asset generated by the building process of create-react-app so I added:
// Priority serve any static files.
app.use(express.static(path.join(__dirname, "client/build")));
// All remaining requests return the React app, so it can handle routing.
app.get("*", function(req, res) {
res.sendFile(path.join(__dirname + "/client/build/index.html"));
});
Adding these lines, I successfully see my index.html but I can't visit both /login and /apisubroutes since it redirect me on the main page (index.html) each time.
It's like I need to serve the static files on my subroute mainRoutes but I don't have an idea on how to do that.
How can I make this work?
app.get('*') would match every single route that you have.
You should do something like this:
var mainRoutes = require("./routes/mainRoutes");
var apiRoutes = require("./routes/apiRoutes");
[...]
app.use(express.static(path.join(__dirname, "client/build")));
app.use("/", mainRoutes);
app.use("/api", apiRoutes);
// If the app reaches this point, it means that
// the path did not match any of the ones above
app.use(function(req, res, next){
// redirect the user to where we serve the index.html
res.redirect('/');
});
create-react-app I believe handles routing different, you cannot hook up the browser's route to the route you want to serve because you're running a single page application", unless you do universal routing with server and the js bundle

Serve gzip html page in node

I use webpack-compression-plugin ta compress all my static files and hml files beforehand to gzip and brotli format. If browser supports it I use brotli, if not gzip and last option is original file. So I would have something like this for example after bundling.
bundle.js
bundle.js.gz
bundle.js.br
On server I use express-static-gzip to serve static files and everything is working fine. All my client static assets are compressd and served like that.
import expressStaticGzip from 'express-static-gzip'
const app: Express = new Express()
process.env.PWD = process.cwd()
app.set('view engine', 'ejs')
app.set('views', path.join(process.env.PWD + '/src/server/views'))
app.use(expressStaticGzip(path.join(process.env.PWD + '/src/dist'), {indexFromEmptyFile: false, enableBrotli: true, maxAge: '1y'}))
app.use((req, res, next) => {
res.set('Cache-Control', 'no-cache')
return next()
})
/* Use server side rendering for first load */
app.use(appRenderer)
// Routes
app.get('*', (req, res) => {
res.render('index')
})
app.listen(PORT, () => {
console.log(`
Express server is up on port ${PORT}
Production environment
`)
})
The problem I have is with my html file, root. Although I also have gzip and br version of it, it is not served like that. I make it by bundling server side code. Express compression module doesn't work and I also want static compression. I am not using nginx.
With the help of this plugin and as was suggested here I got it working
My code:
Ensure that you've pre-gzipped .js and .css files
const checkForHTML = req => {
const url = req.url.split('.');
const extension = url[url.length -1];
if (['/'].indexOf(extension) > -1) {
return true; //compress only .html files sent from server
}
return false;
};
var compress = require('compression');
app.use(compress({filter: checkForHTML}));
const encodeResToGzip = contentType => (req, res, next) => {
req.url = req.url + '.gz';
res.set('Content-Encoding', 'gzip');
res.set('Content-Type', contentType);
next();
};
app.get("*.js", encodeResToGzip('text/javascript'));
app.get("*.css", encodeResToGzip('text/css'));
I wanted compression to happen only for .html because I'm using .ejs template, so need to compress .html on runtime. Compressing static files(js/css) using express compression isn't good idea because it will do it on every request and those are static files.
Or else, cache your results as suggested here
Other solution using nginx, as you posted in your comments also seems nice.