Is it possible to set a base URL for NodeJS app? - node.js

I want to be able to host multiple NodeJS apps under the same domain, without using sub-domains (like google.com/reader instead of images.google.com). The problem is that I'm always typing the first part of the url e.g. "/reader" in Express/NodeJS.
How can I set up an Express app so that the base URL is something.com/myapp?
So instead of:
app.get("/myapp", function (req, res) {
// can be accessed from something.com/myapp
});
I can do:
// Some set-up
app.base = "/myapp"
app.get("/", function (req, res) {
// can still be accessed from something.com/myapp
});
I'd also like to configure Connect's staticProvider to behave the same way (right now it defaults to serving static files to something.com/js or something.com/css instead of something.com/myapp/js)

The express router can handle this since 4.0
http://expressjs.com/en/api.html#router
http://bulkan-evcimen.com/using_express_router_instead_of_express_namespace.html
var express = require('express');
var app = express();
var router = express.Router();
// simple logger for this router's requests
// all requests to this router will first hit this middleware
router.use(function(req, res, next) {
console.log('%s %s %s', req.method, req.url, req.path);
next();
});
// this will only be invoked if the path ends in /bar
router.use('/bar', function(req, res, next) {
// ... maybe some additional /bar logging ...
next();
});
// always invoked
router.use(function(req, res, next) {
res.send('Hello World');
});
app.use('/foo', router);
app.listen(3000);
Previous answer (before express 4.0) :
The express-namespace module (dead now) used to do the trick :
https://github.com/visionmedia/express-namespace
require('express-namespace');
app.namespace('/myapp', function() {
app.get('/', function (req, res) {
// can be accessed from something.com/myapp
});
});

At the moment this is not supported, and it's not easy to add it on your own.
The whole routing stuff is buried deep inside the server code, and as a bonus there's no exposure of the routes them selfs.
I dug through the source and also checked out the latest version of Express and the Connect middleware, but there's still no support for such functionality, you should open a issue either on Connect or Express itself.
Meanwhile...
Patch the thing yourself, here's a quick and easy way with only one line of code changed.
In ~/.local/lib/node/.npm/express/1.0.0/package/lib/express/servers.js, search for:
// Generate the route
this.routes[method](path, fn);
This should be around line 357, replace that with:
// Generate the route
this.routes[method](((self.settings.base || '') + path), fn);
Now just add the setting:
app.set('base', '/myapp');
This works fine with paths that are plain strings, for RegEx support you will have to hack around in the router middleware yourself, better file an issue in that case.
As far as the static provider goes, just add in /mypapp when setting it up.
Update
Made it work with RegExp too:
// replace
this.routes[method](baseRoute(self.settings.base || '', path), fn);
// helper
function baseRoute(base, path) {
if (path instanceof RegExp) {
var exp = RegExp(path).toString().slice(1, -1);
return new RegExp(exp[0] === '^' ? '^' + base + exp.substring(1) : base + exp);
} else {
return (base || '') + path;
}
}
I only tested this with a handful of expressions, so this isn't 100% tested but in theory it should work.
Update 2
Filed an issue with the patch:
https://github.com/visionmedia/express/issues/issue/478

Just to update the thread, now with Express.js v4 you can do it without using express-namespace:
var express = require('express'),
forumRouter = express.Router(),
threadRouter = express.Router(),
app = express();
forumRouter.get('/:id)', function(req, res){
res.send('GET forum ' + req.params.id);
});
forumRouter.get('/:id/edit', function(req, res){
res.send('GET forum ' + req.params.id + ' edit page');
});
forumRouter.delete('/:id', function(req, res){
res.send('DELETE forum ' + req.params.id);
});
app.use('/forum', forumRouter);
threadRouter.get('/:id/thread/:tid', function(req, res){
res.send('GET forum ' + req.params.id + ' thread ' + req.params.tid);
});
forumRouter.use('/', threadRouter);
app.listen(app.get("port") || 3000);
Cheers!

I was able to achieve this using a combination of express-namespace for the routes and a fix from the below google group discussion for the static assets. This snippet will treat a request to /foo/javascripts/jquery.js like a request to /javascripts/jquery.js:
app.use('/foo', express.static(__dirname + '/public'));
Source:
https://groups.google.com/forum/#!msg/express-js/xlP6_DX6he0/6OTY4hwfV-0J

I know this is a very old question but Express has changed a lot since most these answers were posted so I thought I'd share my approach.
You can, of course, use Routers with Express 4 to group together related functionality behind a particular path. This is well documented and has already been covered by other answers.
However, it is also possible to mount an entire application at a particular path. As an example, let's assume our application (the one we want to host at /myapp) looks like this, in a file called myapp.js:
var express = require('express'),
path = require('path'),
app = express();
app.use(express.static(path.join(__dirname, 'public')));
app.get('/hello', function(req, res) {
res.send('Hello');
});
// Lots of other stuff here
exports.app = app;
In our main js file we could then mount this whole application at the path /myapp:
var express = require('express'),
app = express(),
myApp = require('./myapp').app;
app.use('/myapp', myApp);
app.listen(3000);
Note that we've created two applications here, one mounted on the other. The main application could have further sub-apps mounted at different paths as required.
The code in myapp.js is completely independent of where it was mounted. It's similar to the structure used by the express-generator in that regard.
Some documentation about sub-apps can be found here:
https://expressjs.com/en/4x/api.html#app.mountpath
https://expressjs.com/en/4x/api.html#app.onmount

There are also reliability issues. If reliability is important, a common solution is to use a front-end reverse HTTP proxy such as nginx or HAProxy. They both use single-thread evented architecture and are thus very scalable.
Then you can have different node processes for different subsites, and if one site fails (uncaught exception, memory leak, programmer error, whatever) the rest of sub-sites continue to work.

I was looking for this feature but for API routes, not for static files. What I did was that when I initialized the router, I added the mount path. So my configuration looks like this
//Default configuration
app.configure(function(){
app.use(express.compress());
app.use(express.logger('dev'));
app.set('json spaces',0);
app.use(express.limit('2mb'));
app.use(express.bodyParser());
app.use('/api', app.router); // <---
app.use(function(err, req, res, callback){
res.json(err.code, {});
});
});
Notice the '/api' when calling the router

Related

How to serve static files in express app without having to prepend __dirname

Apologies for this question which may be seen as basic but I feel there must be an easy method for this for such a popular framework yet I am struggling to come across it.
I am wanting to serve page routes using a relative path.
i.e. sendFile("/pages/index.html")
but the only solution that works is using sendFile(__dirname + "/pages/index.html")
I have read posts which say to add app.use(express.static("public")) to serve static content but this only impacts get requests to the full URL from client not the sendFile() from server.
i.e. If client types http://...../pages/index.html it correctly returns but when they type http://...../ and I use sendFile("/pages/index.html") the route is incorrect.
Relevant chunks of code are below
const express = require('express');
const app = express();
app.use(express.urlencoded({extended: false}))
app.use(express.static("public"))
app.get("/", (req, res) => {
res.status(200);
res.sendFile("/pages/index.html")
});
Directory structure is public, public/css, public/js, public/pages.
Again all I'm trying to do is not have to write __dirname in every sendFile().
I feel like I'm missing something. Any suggestions are appreciated :) Thanks
As #Yasio linked https://stackoverflow.com/a/52031283/9488284 (Thank You).
The solution is to create a middleware function that prepends __dirname to every sendFile and then use that function instead of sendFile.
Like such
app.use((req, res, next) => {
res.show = (name) => {
res.sendFile(`/public/${name}`, {root: __dirname});
};
next();
});
You can then display pages (in my dir structure) using
app.get('/demo', (req, res) => {
res.show("pages/index.html");
});
i.e. when someone requests http://.../ you will return to them pages/index.html.
This essentially means instead of using sendFile() use show() to return files.

Calling Express by hand

In a node.js/express/socket.io application, how does one "call" express by hand to load/render the home page without saying app.use(blah). In other words, if I wanted to tell express to load index.html by hand instead of automatically.
var express = require('express'),
app = express(),
...
//app.use magically loads index.html when the browser hits 8080
app.use(express.static(path.join(__dirname, '../client/www'))); //index.html is in www
var port = process.env.PORT || 8080; //select your port or let it pull from your .env file
//===============PORT=================
http.listen(port, function () {
console.log('listening on: ' + port);
}
Where index.html is in www ? This doesn't work:
app.get('/', function(req, res){
res.sendfile('index.html', { root: __dirname + "/relative_path_of_file" } );
});
Nor this:
app.get('/', function(req, res){
res.render('/home/idf/Documents/js/react-trader/client/www/index.html', {user: req.user});
});
I was able to resolve the problem that is the cause of this question. I am using Passport to authenticate Express. I needed to protect the home page (index.html), so I added a route and ensured that the user had to be authenticated to view that page. So I said:
app.get('/index.html', ensureAuthenticated, function(req, res){
...
}
The problem is that if I do this, when the user authenticates, I couldn't figure out how to pass control to Express. I either could authenticate (and prevent accessing the home page) through Passport routing, or I could do Express. But I couldn't do both.
It turns out the answer is really simple (or at least in my very limited understanding at this point) I got to work by simply rewriting to
app.get('/index.html', ensureAuthenticated, function(req, res, next){
return next();
}
In essence, the way to break out of Passport routes and pass control to Express [or "call it by hand" - hence my question] is to return next();
This is not obvious at all, and it took quite a bit of experimentation to get it to work.

Capture all http requests with node/express

I am looking to capture all of the data from any request (images, fonts, css, js, etc) on my website so that I can capture the file details, specifically the file name and file size. I have found almost an identical question/solution:
Node.js : How to do something on all HTTP requests in Express?
But the solution appears to be deprecated with Express v4. Is there a simple solution to do this? As another approach I have tried the below solution with no luck:
var express = require("express");
var path = require("path");
var port = process.env.PORT || 3000;
var app = express();
var publicPath = path.resolve(__dirname, "public");
app.use(express.static(publicPath));
app.get("/", function(req, res){
// I want to listen to all requests coming from index.html
res.send("index.html");
});
app.all("*", function(){
// can't get requests
})
app.listen(port, function(){
console.log(`server listening on port ${port}`);
});
Also I am not looking to do this from Fiddler/Charles because I am looking to display this data on my site.
Express routes are predicated on order. Notice the answer that you linked in your question has the middleware defined, and used before all other routes.
Secondly you're trying to implement something that requires middleware, not a wildcard route. The pattern in link you provided in your question is not deprecated according to their docs.
app.use(function (req, res, next) {
// do something with the request
req.foo = 'testing'
next(); // MUST call this or the routes will not be hit
});
app.get('/', function(req, res){
if (req.foo === 'testing') {
console.log('works');
}
res.send("index.html");
});

How can I use middleware alongside express.static?

I have a nodejs application that serves a single page app via express.static. This all works fine, however when I try and create a simple piece of middleware:
app.use(function(req, res, next){
console.log('%s %s', req.method, req.url);
next();
});
app.use(express.static(path.join(__dirname, 'client')));
any attempt to load content from client fails with:
TypeError: Object function (req, res, next){
console.log('%s %s', req.method, req.url);
next();
} has no method 'concat'
If I use the middleware after the express.static call it works fine - but isn't called for static content. I need to setup the middleware so that any flash messages (from connect flash) can be sent as cookies to the static content.
Does anyone know how I can use middleware for all content, including static content? Eventually I'll be serving two folders, one public and one private (authenticated via passport).
I've put together a minimal implementation of your question and it works for me:
var express = require('express')
var path = require('path')
var app = express()
app.use(function(req, res, next) {
console.log('Middleware says %s %s', req.method, req.url);
next();
})
app.use(express.static(path.join(__dirname, 'client')))
app.listen(8080, function() {
console.log('server is ready')
})
I then started the server
$ node so.js
server is ready
and loaded http://localhost:8080/foo.txt in my browser
Middleware says GET /foo.txt
I'm using Express 3.6.0 - if you're using an older version of Express then you may well have stumbled across a bug that's since been fixed, similar to this one. If updating doesn't solve your problem then I would recommend updating your question to contain more code, perhaps a runnable, yet minimal example of the issue. Hope this helps!

Serve Static Files on a Dynamic Route using Express

I want to serve static files as is commonly done with express.static(static_path) but on a dynamic
route as is commonly done with
app.get('/my/dynamic/:route', function(req, res){
// serve stuff here
});
A solution is hinted at in this comment by one of the developers but it isn't immediately clear to me what he means.
Okay. I found an example in the source code for Express' response object. This is a slightly modified version of that example.
app.get('/user/:uid/files/*', function(req, res){
var uid = req.params.uid,
path = req.params[0] ? req.params[0] : 'index.html';
res.sendFile(path, {root: './public'});
});
It uses the res.sendFile method.
NOTE: security changes to sendFile require the use of the root option.
I use below code to serve the same static files requested by different urls:
server.use(express.static(__dirname + '/client/www'));
server.use('/en', express.static(__dirname + '/client/www'));
server.use('/zh', express.static(__dirname + '/client/www'));
Although this is not your case, it may help others who got here.
You can use res.sendfile or you could still utilize express.static:
const path = require('path');
const express = require('express');
const app = express();
// Dynamic path, but only match asset at specific segment.
app.use('/website/:foo/:bar/:asset', (req, res, next) => {
req.url = req.params.asset; // <-- programmatically update url yourself
express.static(__dirname + '/static')(req, res, next);
});
// Or just the asset.
app.use('/website/*', (req, res, next) => {
req.url = path.basename(req.originalUrl);
express.static(__dirname + '/static')(req, res, next);
});
This should work:
app.use('/my/dynamic/:route', express.static('/static'));
app.get('/my/dynamic/:route', function(req, res){
// serve stuff here
});
Documentation states that dynamic routes with app.use() works.
See https://expressjs.com/en/guide/routing.html

Resources