Is it possible to set a base URL for NodeJS app?
Asked Answered
H

7

78

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)

Hachure answered 7/12, 2010 at 10:20 Comment(1)
Maybe you can update the answer, as it is outdated?Bemis
L
22

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

Legroom answered 7/12, 2010 at 11:12 Comment(3)
Update: Fixed the broken patching of simple strings.Legroom
Thanks for the comprehensive answer. The solution for patching express works. However, passing a parameter to staticProvider only sets the root directory where the static files are found, not the url to which they are served. I'm guessing a similar patch can be made that removes the /myapp from the url.Hachure
Overwriting a third party library is not wise since every time you update to a new version you also need to re-add your code to it.Highlands
E
155

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
        });
});
Euchology answered 13/5, 2011 at 15:40 Comment(3)
Note to people from Google: this is the correct answer even though it is not marked. It's usually best to avoid monkey-patching a well-maintained library if at all possible.Interlink
further note - project seems to be dead github.com/expressjs/express-namespace/issues/44 use the router in express 4 bulkan-evcimen.com/…Frowst
Does this process eval thur redirects? e.g. resp.redirect('/') but really you intend to go to /myappHydropathy
L
22

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

Legroom answered 7/12, 2010 at 11:12 Comment(3)
Update: Fixed the broken patching of simple strings.Legroom
Thanks for the comprehensive answer. The solution for patching express works. However, passing a parameter to staticProvider only sets the root directory where the static files are found, not the url to which they are served. I'm guessing a similar patch can be made that removes the /myapp from the url.Hachure
Overwriting a third party library is not wise since every time you update to a new version you also need to re-add your code to it.Highlands
J
13

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!

Jacobson answered 14/7, 2014 at 20:3 Comment(0)
P
8

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

Passer answered 18/9, 2012 at 21:32 Comment(1)
this must be the answer, instead of hacking the core.Faustofaustus
I
5

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

Insensibility answered 13/9, 2017 at 20:59 Comment(1)
elegant solution that can be used to modularize the different apps, still works in 2021, shame express is deadReface
A
3

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.

Aquileia answered 5/9, 2011 at 19:20 Comment(0)
P
1

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

Pumpkinseed answered 19/4, 2013 at 13:10 Comment(3)
It worth to note that this solution works smoothly with express-resouce. I have several express subapps that provides an API below the prefix /api using this solution and a regular website at /.Microsome
Just want to point out that: Error: 'app.router' is deprecated! Please see the 3.x to 4.x migration guide for details on how to update your app.Please
app.router is deprecatedReface

© 2022 - 2024 — McMap. All rights reserved.