I'm creating an application using Expressjs running on node under IISNode (i.e on windows).
I start by setting things up very much like all the expressjs examples I've seen:
backend.configure(function() {
backend.register('html', {
compile: function(str, options) {
return function(locals) {
return str;
};
}
});
backend.set('views', __dirname + '/views');
backend.set('view engine', 'html');
backend.set('view options', {
layout: false
});
backend.use(express.bodyParser());
backend.use(backend.router);
backend.use(express.static(__dirname + '/public'));
});
Lets say my site is running at localhost://mysite. I'm having to create all my route handlers as follows.
backend.get('/mysite/index', function(req, res, next) {
return res.render('index');
});
i.e. I'm having to prefix them all with "mysite". Non of the examples I've seen require this. Is this something to do with IISNode or something else I haven't configured?
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
Out of the box, this is indeed how you have to do it. You could look into express-resource, enabling resourceful routing - but that comes with it's own caveats, at least when it comes to route-specific middleware.
If it's just one path, I think you can handle that with app.set("basepath", "mysite").
To avoid having to modify your express app when deploying in IIS using iisnode, you need to deploy to the root of the IIS WebSite rather than a virtual directory under a site.
Related
I have a mean-stack application. By going to https://localhost:3000/#/home, it reads views/index.ejs. Here is the setting in app.js:
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.static(path.join(__dirname, 'public')));
app.all('/*', function(req, res, next) {
res.sendFile('index.ejs', { root: __dirname });
});
Actually, I don't use the feature of ejs in index.ejs. So now I want to use just a index.html rather than index.ejs.
I put the content of index.ejs in public/htmls/index.html and views/index.html. And here is the current setting in app.js:
var app = express();
// app.set('views', path.join(__dirname, 'views'));
// app.set('view engine', 'ejs');
app.use(express.static(path.join(__dirname, 'public')));
app.all('/*', function(req, res, next) {
res.sendFile('index.html', { root: __dirname });
// res.sendFile('index.html'); // does not work either
});
However, running https://localhost:3000/#/home returns
Error: No default engine was specified and no extension was provided.
Does anyone know how to fix it?
Edit 1: by following the answer of user818510, I tried res.sendFile('index.html', { root: path.join(__dirname, 'views') }); in app.js, it still can NOT find index.html.
Whereas, in routes/index.js, the following can find index.html, but it gives a warning express deprecated res.sendfile: Use res.sendFile instead routes/index.js:460:9.
var express = require('express');
var router = express.Router();
var path = require('path');
... ...
router.get('*', function(req, res) {
res.sendfile('./views/index.html'); // works, but a deprecation warning
// res.sendFile('index.html', { root: path.join(__dirname, 'views') }); does not work
});
It is really confusing...
If it's a single page mean application, then you only need to start express with static and put index.html in static/ dir :
Project layout
static/
index.html
server.js
server.js
'use strict';
var express = require('express');
var app = express();
app.use(express.static('public'));
var server = app.listen(8888, function () {
console.log("Server started. Listening on port %s", server.address().port);
});
Now you can call http://localhost:8888/#home
It looks like a problem with the path. Your index.html is located at public/htmls/index.html and views/index.html. Your root option in res.sendFile should be __dirname+/public/htmls/ or __dirname+/views
In your code, you are using the path:
res.sendFile('index.html', { root: __dirname });
Your app.js would be in the project root where you have public directory alongside at the same level. Based on your rootoption in res.sendFile, you would have to place index.html at the same level as your app.js.
You should change the root path in res.sendFile. Use:
res.sendFile('index.html', { root: path.join(__dirname, 'public', 'htmls') });
OR
res.sendFile('index.html', { root: path.join(__dirname, 'views') });
The above root is based on the path that you've mentioned in your question.
Here's the link to the docs:
http://expressjs.com/en/api.html#res.sendFile
Your no default engine error is probably because you have commented the line where you set the view engine to ejs but still have existing ejs views. Uncommenting that line with the root path change should solve your issue.
Do not serve static content from an application server.
Use a web server for that, and in production, a content delivery network like Akamai.
A content delivery network will charge you per bandwidth (e.g: 10 cents per Terabyte). Serving the equivalent of 10 cents in Akamai can cost you thousands of dollars using cloud instances.
In addition to that, your servers will have unnecessary load.
If you absolutely have to serve static content from your application servers, then put a reverse proxy cache like nginx, varnish or squid in front of your server. But that will still be very cost inefficient. This is documented in the express website.
This is common practice in every Internet company.
My project directory is called travel which has required dependencies node_modules and a subdir called views. Views subdirectory has layouts subdirectory. In the ~/travel/views/layouts there is .handlebar templates for the views. But when I go to the browser and type in localhost:3000/about or just localhost:3000 regardless of that I get 404 - not found I tried switching the res.render("about"); to res.render("/views/layouts/about"); but that does not work neither. It has something to do with the paths but I'm not sure what.
var express=require("express");
var app=express();
// set up handlebars view engine
var handlebars=require("express3-handlebars")
.create({defaultLayout:"main"});
app.engine("handlebars",handlebars.engine);
app.set("view engine","handlebars");
app.set("port",process.env.PORT || 3000);
app.get("/",function(req,res){
res.render("home");
});
app.get("/about",function(req,res){
res.render("about");
});
// custom 404 page (middleware)
app.use(function(req,res,next){
res.status(404);
res.render("404");
});
// custom 500 page (middleware)
app.use(function(err,req,res,next){
console.error(err.stack);
res.status(500);
res.render("500");
});
app.listen(app.get("port"),function(){
console.log("Express started on http://localhost:"+app.get("port")+";press ctrl+c to terminate,");
});
Assuming you have a subdirectory called views within the same directory as your Express app script, you need to set the Express views app setting like:
app.set('views', __dirname + '/views');
By default, the path is relative to the current working directory.
Additionally, you may wish to update your express3-handlebars module to be express-handlebars instead, as the latter is the newer of the two and is recommended by the current maintainer.
Try this by overriding the default layout
app.get('/', function (req, res, next) {
res.render('home', {layout: false}); });
Essentially when I use a catch-all route and use res.redirect('/') regardless of the url I enter it will always render the index/home page (ie Angular does not seem to 'see' the full url) however if I place res.render('index') in the catch-all route everything works fine. I don't want repeat code and redirecting to '/' should work, I have probably made a stupid mistake somewhere here and any help would be much appreciated!
Angular routing:
app.config(function ($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/',
{
templateUrl: 'partials/home.jade'
})
.when('/about',
{
templateUrl: 'partials/about.jade'
})
.otherwise( {redirectTo: '/'});
});
This will correctly render the about page when entering site-address/about:
app.get('/', function (req, res) {
res.render('index');
});
app.get('/partials/:name', function (req, res) {
res.render('partials/' + req.params.name);
});
app.get('*', function (req, res) {
res.render('index');
});
This will always just show the index page:
app.get('/', function (req, res) {
res.render('index');
});
app.get('/partials/:name', function (req, res) {
res.render('partials/' + req.params.name);
});
app.get('*', function (req, res) {
res.redirect('/');
});
Configuration if it helps:
// Configuration
app.configure(function () {
app.set('port', process.env.PORT || 1337);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(path.join(__dirname, 'public')));
app.use(app.router);
});
This is by design.
When you use res.redirect('/'), Node/Express is sending an HTTP redirect, which will change the URL in the browser, thus when your index template is rendered, and when the angular code is run, the URL is /, regardless of what the user entered (the whole point of the redirect).
When you omit the redirect and just send the template as a response, NodeJs responds with an HTTP 200 (success) along with HTML content. Since the URL didn't change, when your application runs, the angular routing properly routes.
EDIT TO ADD: Address Comment
Rather than have two routes render the same template, I would get rid of the / route all together, and just have the catch-all render the index template, which will then turn control over to the Angular router.
Otherwise, I would consider splitting your routes conceptually: All your application routes are specifically sent to angular router, and you render static routes via nodejs, and use your catch all to render a more appropriate page for a missing or unknown resource (more helpful for your users).
You can use regex-like languages to specify a single handler:
app.get('/()|(about)|(contact)/',function(req,res) {/* handle */});
For folder structure:
root
-web.js
-dist/index.html
-dist/rest of the page
Just paste the following snippet to your web.js
nodeApp.use('/', express.static(__dirname + '/dist'));
nodeApp.get('/[^\.]+$', function(req, res){
res.set('Content-Type', 'text/html')
.sendfile(__dirname + '/dist/index.html');
});
I have a problem where when I use the catch-all routing, all of my static assets (stylesheets, javascript files etc) don't load:
app.use(app.router);
app.use(express.static(path.join(__dirname, frontend.app)));
// when this is removed, I can load static assets just fine
app.get('*', function(req, res){
res.render('main');
});
When the app.get('*', ...) part is removed, I can load the static assets just fine (i.e. I can type in 'examplejavascript.js' and see the javascript file. When it is there however, express catches the assets.
$locationProvider.html5Mode(true);
are you sure your requests are hitting the nodejs server. using this means, angularjs will try to search for urlmapping inside browser.
try by commenting
$locationProvider.html5Mode(true);
I've tried to detect IE by using expressjs, the code surely redirect me to the error page.
But the problem is that it won't render css files and get image files in my folder.
I think the problem is that expressjs redirect to error_page before the static path is set.
So I put app.all('*', ieRedirecter);, after
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(__dirname + '/uploads'));
The problem is still occur... could someone help me out, thanks!
My settings
function ieRedirecter(req, res, next) {
if(req.headers['user-agent'].indexOf("MSIE") >= 0) {
var myNav = req.headers['user-agent'];
var IEbrowser = parseInt(myNav.split('MSIE')[1])
if(IEbrowser < 9) {
res.render('error/browser_error', {title : 'not supporting your browser'})
} else {
next();
}
} else {
next();
}
};
app.configure(function(){
app.set('port', process.env.PORT || 3000);
...
...
...
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(__dirname + '/uploads'));
app.all('*', ieRedirecter);
})
This order doesn't work:
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(__dirname + '/uploads'));
app.all('*', ieRedirecter);
app.router is the middleware that handles all your routing requests, including app.all(), even though you declare that route after your other middleware (fun fact: at any moment you declare a route – using app.{get,post,all,...} – the app.router middleware is inserted into the middleware chain automatically).
If possible, the easiest solution would be to move the static and IE middleware to before your routes (but I realize that might cause problems with your application setup):
// static middleware first...
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(__dirname + '/uploads'))
// ...followed by the IE check
app.use(ieRedirecter);
// ...followed by your routes
app.get('/', ...);
...
Another solution would be filter out requests for static resources in your ieRedirecter middleware by looking at req.path, but that assumes that you can easily identify those requests (like if they start with /css or /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