Serving static files from an express/node.js application - express

Hi I am a newbie and started to learn about node recently. I took an Heroku tutorial on websockets (https://devcenter.heroku.com/articles/node-websockets) and adapted it for a specific project I was working on. In the example code there was a single index.html file with some embedded javascript. I moved this script out to a separate file and referenced it in the HTML. Everything worked fine locally but doesn't work when i deploy to Heroko. I chatted with the very helpful team at Heroku who informed me that my server side code is serving up all files as HTML and I need to change the code. They gave me some pointers and I tried as many things as I could over several days but to no avail. In the end they recommended coming to this forum as a way to solve the problem as it is beyond their scope. The existing code that serves up the index.html file is as follows:
const express = require('express');
const SocketServer = require('ws').Server;
const path = require('path');
const PORT = process.env.PORT || 3000;
const INDEX = path.join(__dirname, 'index.html');
const server = express()
.use((req, res) => res.sendFile(INDEX) )
.listen(PORT, () => console.log(Listening on ${ PORT }));
At first i edited this to include the line:
app.use(express.static('public'))
but this didn't work. I then amended as follows and it still doesn't work:
const INDEX = path.join(__dirname, 'index.html');
const JS = path.join(__dirname, 'client.js');
const server = express()
.use((req, res) => {
res.sendFile(INDEX);
res.sendFile(JS);
I have looked at other tutorials that work when i run them in isolation but when I try to adapt my above code it simply doesn't work. I would really appreciate if someone out there could point me in the right direction.
BTW this is what Heroku told me:
"To explain a bit further this error Uncaught SyntaxError: Unexpected token < is because the URL for http://thawing-journey-33085.herokuapp.com/client.js isn't serving a javascript file but is instead trying to serve the HTML for the homepage. This suggests you have an issue with the routing in your application which you'll need to review. This is probably because your server.js file doesn't check for any particular URL before sending the index.html file."
Thanks

I serve my static files like this:
// define the folder that will be used for static assets
app.use(Express.static(path.join(__dirname, '../public')));
// handle every other route with index.html, which will contain
// a script tag to your application's JavaScript file(s).
app.get('*', function (request, response){
response.sendFile(path.resolve(__dirname, '../public', 'index.html'));
});
This way i set the static folder in the express.static middleware so i can serve the files. And then i redirect all url request to the index.html
To know more: express static

Related

Express is serving files without going through route handlers?

I have the following code:
const app = express();
app.use(express.static(path.resolve('../frontend/dist')));
const server = http.createServer(app);
app.get('/', (req, res) => {
console.log('received request');
res.sendFile(path.resolve('../frontend/dist/index.html'));
});
If I comment out the app.get handler, index.html is served at localhost:3000 anyway, apparently due to the second line. The get handler is not actually executing - received request is never printed in the console.
But without the second line, it can't serve static assets and JS & CSS fail to load. What am I doing wrong here? Why is the static asset config causing the route to be ignored?
express.static() is a generic route handler. It tries to match incoming requests to specific files at a target location in your file system. One of the special features it has is that if it gets a request for / it will look for index.html in the target directory.
You can see the feature described in the doc and, in fact, there's an option { index: false} that can turn the feature off if you don't want it to serve index.html automatically.

VueJS 3 project: "Cannot get" message with all paths but root

I have been trying for the past hour and a half to debug a project of mine.
The project works fine locally. Routes are systematically not working remotely except for one, the root of the project. I get a Cannot GET message in the browser elsewhere (all other paths).
From what I understand from my readings, this might have something to do with vue-router and might be caused by the server.js file I created in the root folder. The problem might have to do with the fact that my routes are dynamic.
const express = require('express');
const serveStatic = require("serve-static")
const path = require('path');
app = express();
app.use(serveStatic(path.join(__dirname, 'dist')));
const port = process.env.PORT || 3000;
app.listen(port);
This might also be caused by the fact I did not create the project with the history mode (although I'm pretty sure I did). I understand that I might have to go to my webpack config file to solve this, but I don't think I have one.
EDIT: By the way, my VueJS application is just a front-end to the PokéAPI back-end. I didn't build the back-end myself.
Simply creating the project with history mode is not enough. Your browser is trying sending text/html GET requests to the path you navigate to. But your app is a single page application which only has one index.html in /public, so you get 404 errors. Its in the vue-router docs:
To fix the issue, all you need to do is add a simple catch-all fallback route to your server. If the URL doesn't match any static assets, it should serve the same index.html page that your app lives in.
For Node.js+express setup, you can use connect-history-api-fallback package.
npm install --save connect-history-api-fallback
Then change your server.js file to this:
const express = require('express');
var history = require('connect-history-api-fallback');
const serveStatic = require("serve-static")
const path = require('path');
app = express();
app.use(history());
app.use(serveStatic(path.join(__dirname, 'dist')));
const port = process.env.PORT || 3000;
app.listen(port);
In case you don't want to use third-party package you might be able to simply do this:
const express = require('express');
const serveStatic = require("serve-static")
const path = require('path');
app = express();
app.use(serveStatic(path.join(__dirname, 'dist')));
app.get(/.*/, function (req, res) {
res.sendFile(__dirname + "/dist/index.html");
});
const port = process.env.PORT || 3000;
app.listen(port);
But I'd recommend using the package as it handles some edge-cases

Built Vue SPA not working when using "publicPath"

I set a public path in vue.config.js like
module.exports = {
publicPath: '/subpath'
};
it works fine when I start the devlopment server with npm run serve (vue-cli-service serve). The app becomes available at localhost:8080/subpath which is exactly what I want. It uses vue-router and at least in development it seems to work perfekt with the /subpath as publicPath.
Problem
I am using Express to serve the app files. Running npm run build the app will be built and stored to ./dist folder (default output folder).
// simplified server.js
app.use(express.static(path.join(__dirname, '/dist')));
app.get('/', (req, res, next) => {
res.sendfile('./dist/index.html');
});
After starting my express server and visiting my browser at localhost:<port>/subpath or localhost:<port> it can't find the necessary files.
// EXAMPLE FROM BROWSER CONSOLE
GET http://localhost:5050/subpath/js/app.6c6daa90.js net::ERR_ABORTED 404 (Not Found)
Which somehow looks obvious to me but I don't know how to set the express server correctly to respect the publicPath setting . Maybe there is a different approach ?
Attempt #1
I made a little change in my express server.js
// app.use(express.static(path.join(__dirname, '/dist')));
/* NEW */
app.use('/subpath', express.static(path.join(__dirname, '/dist')));
This way it should serve the static files from the /dist folder when requested with /subpath/app.js
Result
From Browser console when requesting localhost:<port>/subpath or localhost:<port>
Refused to load the font 'data:application/font-woff2;base64,<omitted>' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'font-src' was not explicitly set, so 'default-src' is used as a fallback.
Refused to load the image 'http://localhost:5050/favicon.ico' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback.
I played around with express.static and this combination seems to work for me
const path = require('path');
const express = require('express');
const history = require('connect-history-api-fallback');
const app = express();
app.use(history());
app.use(express.static(path.join(__dirname, '/dist')));
app.use('/subpath', express.static(path.join(__dirname, '/dist')));
const listener = app.listen(5050, () => {
console.log(`Open http://localhost:${port} in your browser`);
});
Sidenote : Adding connect-history-api-fallback was not required to solve the initial problem but is required for vue-router to work properly when in history mode.

express and static assets with external resources

I am using Express to serve static assets. Frontend is AngularJS 1.x and I have html5mode enabled. Trying to implement Recaptcha is where I noticed the following in Chrome dev tools:
Uncaught SyntaxError: Unexpected token <
api.js?onload=vcRecaptchaApiLoaded&render=explicit“:1
When I click on the function to initiate the Recaptcha process I receive:
Error: reCaptcha has not been loaded yet.
So far this makes sense to be bacause I noticed the string that the first error is reporting is part of the url path to load Recaptcha from Google.
When I click on the url (api.js?onload=vcRecaptchaApiLoaded&render=explicit“:1) in chrome tools it loads my index.html! Strange!
This has be believing it has something to do with my static asset serving. I have played around with my express server until the cows came home and cannot figure out how to remedy.
Live example:
http://ninjacape.herokuapp.com
Here is my code and thank you for taking a look!
index.html
<script src=“https://www.google.com/recaptcha/api.js?onload=vcRecaptchaApiLoaded&render=explicit“ async defer></script>
express.js
var express = require('express');
var compression = require('compression');
var app = module.exports.prod = exports.prod = express();
var devAPI = 'http://localhost:1337';
app.use(compression());
app.use(express.static('.tmp'));
app.get('/*', function(req, res) {
res.sendFile(__dirname + '/.tmp/index.html');
});
var proxy = require('express-http-proxy');
app.use('/api', proxy(devAPI));
var port = process.env.PORT || 8000;
app.listen(port);
Well... I wish I had a better answer however I am just happy I got it to work. Something in the way I am statically serving files is appending any url in index.html to http://localhost:8000. To work around this I took a look at the actual request coming into Express and found the url. Then added logic to redirect that request to the real url. See commented code below for more info:
// Any requests matching /*
app.get('/*', function(req, res, next) {
// Log the original url express is tying to go to
console.log(req.url);
// This is the url found from the step above (Where are the extra characters coming from?!)
var url ='/%E2%80%9Chttps://www.google.com/recaptcha/api.js?onload=vcRecaptchaApiLoaded&render=explicit%E2%80%9C'
// Self explanatory
if (req.url === url) {
// Respond by redirecting the request
res.redirect('https://www.google.com/recaptcha/api.js?onload=vcRecaptchaApiLoaded&render=explicit')
//End this block and continue
next();
} else {
// If it doesn't match the above url, proceed as normal
res.sendFile(__dirname + '/.tmp/index.html');
}
});

How to make an Express site without a template engine?

I do not want Jade or EJS on my site. How can I create an express site without it defaulting to the Jade templates? Thanks
If what you want is directly to serve static html files with the possibility to cache resources, while still being able to hit "/" and get index.html, then the answer is as easy as this:
var express = require('express');
var http = require('http');
var app = express();
app.use(express.static(__dirname + '/public'));
http.createServer(app).listen(3000);
Gotcha: Html files including index.html must be inside /public folder instead of /views
You could use commands below to install express-generator globally and then scaffold a project without a view engine
npm install -g express-generator
express newProject --no-view
You can comment out the lines
app.set 'views', __dirname + '/views'
app.set 'view engine', 'jade'
from the Express initialization code.
If you are serving only static content: https://github.com/visionmedia/express/blob/master/examples/static-files/index.js
Otherwise, use your database, your files, your user input, or whatever to concatenate a string that will make up the http response.
// Express 3.x
app.get('*', function(req,res){
fs.readFile('./foo.txt', 'utf8', function (err, data) {
if (err) throw err;
data += (req.query['something'] || "")
res.type('text/plain');
res.send(200, data);
});
});
With that said: I have grown to love Jade as I've been playing with it for the past few months. It has its idiosyncracies but it's orders of magnitude faster to write any complicated html.
With Express 4.0.0, the only thing you have to do is comment out 2 lines in app.js:
/* app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade'); */ //or whatever the templating engine is.
And then drop your static html into the /public directory. Example: /public/index.html
Use Restify
http://restify.com/
var restify = require('restify'),
fs = require('fs');
var server = restify.createServer({
certificate: fs.readFileSync('path/to/server/certificate'),
key: fs.readFileSync('path/to/server/key'),
name: 'MyApp',
});
server.listen(8080);
It borrows heavily from Express -Routing