I have an app where I am trying to remove the hashbang ( ! and #) prefixes for my routes, but still have people be able to use bookmarked routes. For the most part I have been able to get it work with html5Mode set to true, but there are a few cases where it is not working. Here is how I have my server configured:
var router = require('./router')(app);
app.use(express.static(path.join(__dirname, '../client')));
app.get('*', function (req, res, next) {
res.sendFile('index.html', {root:'../client/app/'});
});
router in this case looks like this:
var express = require('express');
var router = express.Router();
var Products = require('../../database').Products;
router.get('/:flavor', function (req, res) {
var flavor = req.params.flavor;
Products.findOne({flavor:flavor}, function (err, product) {
if (err) {
throw err
}
res.json(product);
});
Getting the flavor routes, is one case where this setup does not work. If someone directly types into the browse, mysite.com/lemon they receive the JSON data back, only (no template or anything). Normally this is used by the angular app, (which would typically make the request to /lemon and implement it into the template). However, if I move the router below the app.get('*'), then any request made by Angular for the data is returned with the entire index.html page. How can I make it so that a direct request by the browser for a route that normally returns JSON sends the index file?
Related
I am trying to setup a multi language website with Express and NodeJs. My problem is I get redirected what it feels like 100 times and my browser is giving me a error that the webpage is not working because it redirected me too many times.
app.js
app.use('/', (req,res,next) => {
res.redirect('/en-US');
next();
});
app.use('/:lang', indexRouter);
app.use('/:lang/users', usersRouter);
index.js (indexRouter)
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index');
});
module.exports = router;
The problem is that this route handler:
app.use('/', (req,res,next) => {
res.redirect('/en-US');
next();
});
will get hit for not only /, but also /en-US. app.use() matches any route handler for which the path is equal to or a subset of the requested path. So, the browser requests "/", you redirect to "/en-US", which then redirects to "/en-US" and so on, an infinite loop.
I don't know the overall URL design of your site to know what the best overall solution is. You can prevent the infinite redirect loop by just changing app.use() to app.get():
app.get('/', (req,res,next) => {
res.redirect('/en-US');
});
But, that will make the redirect only work for GET requests which may or may not be OK. If you want all HTTP verbs to redirect, you could change to app.all():
app.all('/', (req,res,next) => {
res.redirect('/en-US');
});
The important thing to understand here is that app.get(), app.post(), app.all(), etc... all require an exact match for the URL path, whereas app.use() just requires a subset match. This is a little understood aspect of the Express design.
In addition, remove the call to next() after you do res.redirect(). At that point, you've sent the response, you don't want any other request handlers to see the request. You're done with routing.
under your app.js
Try using
app.use('/', router )
How about you try dealing with the '/' route through the app.js directly instead of index.js
I try to make a Single Page App on Express. The main problem is that I use Express route feature, that re-renders the view each time the URL changes and GET request gets to server. I have a rather usual code like:
// Express routes
var routes = {
index: require('./routes/home')
};
// use routes
app.use('/', routes.index);
//home.js
var express = require('express');
var router = express.Router();
router.get('/', function (req, res, next) {
res.render('home', {title: "ITEF"});
});
router.get('/about', function (req, res, next) {
res.render('home', {title: "ITEF"});
});
Is there any way to make router ignore URL changes and let the front-side app be as it is? The plan is to buld all UX logic according to URLs, but without countless re-rendering.
Found out that it's rather easy with pushState HTML5 feature:
window.history.pushState('object or string', 'Title', '/about');
Details described in a good article here.
I little bit late for answering, but I managed to do what you asked using a library called Finch.js.
So, when I want to call another URL in my Single Page Application, I use Finch.navigate('/profile'). The URL will change, and there will be no call to the node server.
Finch.route("/profile", function () {
main.viewModel(new app.model());
});
Finch.listen();
Finch.navigate('/profile');
You can learn more about the library here FINCH
I'm using a module called consign to include several different modules in a directory at once (instead of having a bunch of require statements). Within these modules, I've been setting the mount path for each endpoint at the top of the router file so as not to repeat it several times throughout the file. However, consign passes the same router to each of these (which should normally be fine) and the mount path is actually being overwritten via the use() method if the path is the same in any of the files. I'll try and show this the best way I can...
/routes/api.js
var express = require('express');
var router = express.Router();
var consign = require('consign');
// get all routes inside the api directory and attach them to the api router
// all of these routes should be behind authorization
consign({
cwd: 'routes'
})
.include('api')
.into(router);
module.exports = router;
/routes/api/player.js
module.exports = function (router) {
router.use('/player', router);
router.get('/getInfo', function (req, res, next) {
res.error = false;
res.data = {};
res.message = "Player getInfo API Call - IN DEVELOPMENT";
next();
});
};
/routes/api/profile.js
module.exports = function (router) {
router.use('/profile', router);
router.get('/getInfo', function (req, res, next) {
res.error = false;
res.data = {};
res.message = "Profile getInfo API Call - IN DEVELOPMENT";
next();
});
}
Consign is loading in the modules just fine, but the router.use() method seems to be overwriting the callbacks when the paths are the same (disregarding the base path that is). For instance, both "/player/getInfo" and "/profile/getInfo" work as a call, but are both responding with "/profile/getInfo" data.
BTW - in case you're wondering and in case it's pertinent, I have a small piece of middleware called "formatResponse" that will take the data and format all of the calls in the same way, which is why I have a "next()" instead responding from the function itself. The code for that is below as well.
/middleware/formateResponse.js
module.exports = function(req, res, next) {
res.json({
error: res.error,
data: res.data,
message: res.message
});
}
The way you're doing it right now, there's no scope. The fact that you mounted the router on '/profile' and then added a get statement to the '/getInfo' path doesn't really change the scope the way you think it does. They're both stored to match on '/getInfo', with the last one in winning (regardless of prefix. I bet navigating to http://your-server/getInfo will work too).
You either need to use a different router for each module (and then mount that one on the path root you want) or else be more explicit in the rest of the routes (e.g. call router.get('/profile/getInfo', ...).
I'm using Node and Anugular, and I have created a RESTful api from my application, and created an angular resource to use this. I'm confused as to how the Angular ui-router directive reconciles with the Node Routing system on the server.
At the moment I have set up my routes/states in ui-router like this:
$stateProvier
.state('admin', {
url:'/admin',
templateUrl:'views/admin.html',
controller: 'adminController'
});
And this loads into the ui-view on my homepage, when I navigate to this url from a link on the loaded page.
HOWEVER, when I manually type in localhost/admin I get the route from Node, rather than the state render through angular.
Now I'd like to Angular to handle all the navigation on my app, and my resource to get the information, even if the address is typed manually into the navigation bar.
I've created a route in Node is for index, which contains my index.html page from angular, which effectively contains the whole app angular code, including all the routing.
My question is, how can I get angular redirect if I manually type the url into the address bar, and still have the data from the $resource.
I'm directing my resource to '/admin' - could this be the problem?
Does this mean that I need to add the contents of /routes/appointments' into the base node file (server.js), and then remove the route? If so then how do i direct my resource to the correct REST api?
app structure
public
-angular app
-app.js //for angular
routes
index.js
appointments.js
models
views
- index.ejs
server.js //node server file
here is my code exerpts
server.js
//standard routing code
var routes = require('./routes/index');
var appointments = require('./routes/appointments');
var app = express();
//configuring Express
app.set('port', process.env.PORT || 3000);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.set('views', __dirname + '/views');
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use('/', routes);
app.use('/', appointments);
routes/index.js
var express = require('express');
var router = express.Router();
// ./routes/index.js
router.get('/', function(req, res) {
res.render('index', { title: 'Homepage' });
});
module.exports = router;
routes/appointments.js - this is the basis of my RESTFUL api
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Todo = require('../models/Appointments.js');
/* GET /todos listing. */
router.get('/admin', function(req, res, next) {
Todo.find(function (err, todos) {
if (err) return next(err);
res.json(todos);
});
});
module.exports = router;
One way to do this is via the Accept header. If the request only accepts JSON then let the request go through to your API. If the request accepts HTML then always serve up your index page. Then once the index page loads angular's router will take care of the rest.
// Angular config - default Accept header for all ajax requests
$httpProvider.defaults.headers.common = {
'Accept': 'application/json'
};
// Middleware in Node to "intercept" non JSON requests
// Place this after express.static middleware but before your route definitions.
app.use(function(req, res, next) {
// keep in mind this applies to all requests, so 404s will need to be handled by your angular app since any url will serve up the index page
if(req.header('Accept') !== 'application/json') {
console.log('serving the index page');
req.url = '/'; // force the url to serve the index route.
}
next();
});
One more thing to note about this approach is that obviously you won't be able to see/debug your JSON data by hitting the URL directly anymore. There are several useful tools like Advanced REST Client or POSTman which actually give you better control and more options for things like that. Just make sure you set the Accept header in one of those tools and you'll be able to see the JSON response.
The actual URL is localhost#!/admin, try that. Angular hides the hashbang #!
Angular's URL routing is an "illusion" in that way. It only works on the client-side and only when your Angular app is loaded, which is on the main / route.
A solution could be to conditionally redirect from localhost/admin to localhost#!/admin, i.e. redirecting to your Angular app and passing it the #!/admin path. The condition could be a check for whether or not JSON was requested.
router.get('/admin', function(req, res, next) {
if(req.header('Accept') !== 'application/json')
return res.redirect('/#!/admin');
Todo.find(function (err, todos) {
if (err) return next(err);
res.json(todos);
});
});
You'll also need to configure Angular such that when it requests '/admin' json data from the server, it should only accept json (by setting the request header), which is how the the server will distinguish it from the regular '/admin' request. For that, if you're using $http.get you would do $http.get('/admin', {'Accept':'application/json'})
I am using an express.js package called express-subdomain to facilitate requests to defined subdomains I set up.
As far as I understand, the subdomain constructor function expects an express router object which I pass to it from an exported router module.
What I have tried is as follows:
MAIN APP.JS SERVER FILE
var common = {
express: require('express'),
subdomain: require('express-subdomain')
};
common.app = common.express();
module.exports = common;
common.app.listen(3000, function () {
console.log(('app listening on http://localhost:3000'));
});
var router = require('./router/index');
// Error Handling
common.app.use(function(err, req, res, next) {
res.status(err.status || 500);
});
router/index
module.exports = function (){
var common = require('../app');
var router = common.express.Router();
common.app.get('/', function(req, res) {
res.send('Homepage');
});
common.app.use('/signup', require('./routes/signup'));
common.app.use(common.subdomain('login', require('./routes/login')));
}();
routes/login
var express = require('express');
var router = express.Router();
router.get('/', function (req, res) {
res.send('login working');
});
router.get('/info', function (req, res) {
});
module.exports = router;
I have tried to access the login subdomain at the following urls:
http://login.localhost
http://login.localhost:3000
http://login.localhost.com
http://login.localhost.com:3000
Any clarification or assistance appreciated.
author of express-subdomain here 👋
A couple of things:
Hosts must be setup correctly - I'd recommend something like so in your /etc/hosts file.
127.0.0.1 myapp.local
127.0.0.1 login.myapp.local
For more information on this see https://github.com/bmullan91/express-subdomain#developing-locally
Register the subdomain routes before any others, including the homepage route. The order is very important
The pattern you're using in /routes/index.js is not advised (requiring a self invoking function). Exporting the Router like you done in /routes/login.js is cleaner.
Finally, If you're still stuck take a look at the source for express subdomain and in particular its tests.
Happy coding.