Using ngRoute with Express backend - angularjs

I have read several other answers on this topic, but I do not fully understand how this works. I have an angular front-end. I am trying to use $routeProvider to load partials into my page for a single page application. I am getting 404 for the $routeProvider requests and I think there must be a way to set this up without declaring a route for every partial on the server. There will be many more partials soon even though I only have one declared in my config right now. My routeProvider code looks like this:
myApp.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/register', {
templateUrl: 'partials/register',
controller: 'registerCtrl'
})
}])
but going to localhost:3000/register gives a 404 error. I looked at this response on SO as well as some others, but I have not been able to put all the pieces together.
Express 4, NodeJS, AngularJS routing
How is this solution from the above post
app.use('/*', function(req, res){
res.sendfile(__dirname + '/public/index.html');
});
able to find the partials and return them to the front end (btw this snippet did not work for me).
My app isn't much different than creating a new express app with express generator. I have these require and use statements related to routing:
var routes = require('./routes/index');
var users = require('./routes/users');
app.use('/', routes);
app.use('/users', users);
and this route to serve my home page in routes/index:
router.get('/', function(req, res, next) {
res.render('home');
});

When you use front-end routing you don't have to declare a route for every partial on the server. The simplest form of the solution is to let the user hit any route with a wildcard match on the back, and no matter what route they're viewing you just serve up the same front end application template. So in fact you for the most part do away with backend routing altogether. Once your JS kicks in and reads the current route out of the navbar, it will resolve the appropriate view.
Check out one of the tutorials for how to configure your express backend to handle this:
http://fdietz.github.io/recipes-with-angular-js/backend-integration-with-node-express/implementing-client-side-routing.html
There's some nuance and previously there was controversy around this structure because of things like SEO, but a lot of that has been cleared away by Google now being able to parse JS and resolve the view in SPAs (http://googlewebmastercentral.blogspot.com/2014/05/understanding-web-pages-better.html)
Of course, you will actually have some routes on the backend in order to accept api requests, but ideally that's it unless you choose to get fancy and go isomorphic, prerendering the js on the backend and serving a completed view (though that's better suited in React for now, at least until Angular 2.0)

Related

AngularJS and NodeJS http-server: rewrite URL

I'm using NodeJS http-server to code an AngularJS App.
I'm having a problem. When I try to access directly the url in the browser the Angular does not intercept the URL to show me the content.
If I type the URL manually like: http://127.0.0.1:8080/#!/clients it works, but not when I type directly: http://127.0.0.1:8080/clients
I want the http://127.0.0.1:8080/#! as default in the http-server.
I'm using in AngularJS the html5 mode and the hash prefix:
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
Is there a way to rewrite the url from http-server with the default /#!/ before the address?
Note: Below is an example of a more complex Express.js URL rewriting situation, where you may not wish to "catch all" routes, but instead discern between routes for views and routes for the server-side api. All solutions I could find only showed a generic catch-all approach, which did not fit practical application for an app whom requires access to server-side routes. If you do simply want the "catch all", see the other yellow note at the bottom of this answer for links on how to set that up.
If you turn off html5 mode, by default, you should be in hashbang (#!) mode...Turning on html5Mode allows you to remove the hashbang (#!), if you wish.
Here's more about the different modes:
https://docs.angularjs.org/guide/$location
HTML5 mode being enabled, gives you normal looking, non-hashbang URLs. Normally, when HTML5 mode is enabled, you'll hit the issue you described where pages within your app will load OK internally, but if you try and enter the URL directly into your browser (or access it via a bookmark), you'll get a Cannot GET error.
First, be sure you've set a <base> tag in the <head> your primary index file, like this:
<head>
<!-- all your script tags, etc etc -->
<base href="/">
<!-- rest of your front-end dependencies etc -->
</head>
That way Angular will know which is your primary index to load partials within.
Secondly, I will try and tell you how I approached re-writing my URLs in Express to solve the issue you have described. My answer may be somewhat incomplete, as I am still learning, and in truth, I do not fully understand why, once HTML5 mode is enabled in Angular, that the routing does not work properly. There also may be better ways to approach the problem as opposed to how I solved mine.
It seemed that once I switched to HTML5 mode, Angular intercepted my routes and was causing an issue when I was trying to use the $http service to request server-side api routes.
It seemed like turning on HTML5 mode basically took over all of my routing, and I had to find a way to tell Express to either pass a route to Angular or to continue the route (away from angular) using next(). This was my best assessment.
What I Did:
Enabled HTML5 mode [as you have done in your example], and set a
<base> in my index file (as noted above).
Rewrote my routing in ExpressJS using the native express.Router():
See: https://expressjs.com/en/guide/routing.html
At the very bottom of that page are instructions for express.Router()
I'll show you how I did it below
My Approach/Pseudo-code:
I setup a method in my Router so that if the incoming request contained /api/ (checked via regex), I would invoke ExpressJS's next() method and continue along in the route, which would hit the server controller. Otherwise, if the URL did not contain /api/, I the appropriate view page was delivered, in which Angular took over.
How I setup my express.Router():
I created a middleware folder in my app and created a file called api-check.js.
// Setup any Dependencies:
var express = require('express'),
router = express.Router(), // very important!
path = require('path');
// Setup Router Middleware:
/*
Notes: The `apiChecker` function below will run any time a request is made.
The API Checker will do a regex comparison to see if the request URL contained
the `/api/` pattern, to which instead of serving the HTML file for HTML5 mode,
it will instead `next()` along so the API route can reach the appropriate
server-side controller.
*/
router.use(function apiChecker (req, res, next) {
console.log('/// ROUTER RUNNING ///'); // will print on every route
console.log('URL:', req.originalUrl); // will show requested URL
var regex = /(\/api\/)/g; // pattern which checks for `/api/` in the URL
if (regex.test(req.originalUrl)) { // if the URL contains the pattern, then `next()`
console.log('THIS IS AN API REQUEST'); // api detected
next();
} else { // if the URL does not contain `/api`:
res.sendFile(path.join(__dirname, './../../client/index.html')); // delivers index.html which angular-route will then load appropriate partial
}
})
module.exports = router; // exports router (which now has my apiChecked method attached to it)
How I added my express.Router() to my Express App:
Depending upon how your code is modularized, etc, just go ahead and in the right place require your module (you will have to adjust the direct path depending upon your project), and then app.use() the module to intercept all of your routes and direct them to your Router:
// Get Router:
var apiCheck = require('./../middleware/api-check');
// Use Router to intercept all routes:
app.use('/*', apiCheck);
Now, any route (thus the /*) will go through the express.Router() apiChecker() function, and be assessed. If the requesting URL contains /api, then next() will be invoked and the server-side controller will be reached. Otherwise, if the /api slug is not detected in the URL, then the primary index.html base file will be sent, so that Angular can deliver the appropriate view via $routeProvider.
Note: If you don't need to discern between incoming routes, and just want to "catch all" incoming routes and hand back your <base> index file, you can do as outlined in another stackoverflow answer here. That answer uses app.get() to catch all GET requests to hand back your index. If you also need to catch POST requests or others, you may want to instead use app.all(), in place of app.get() in the aforementioned example. This will catch all routes, whether GET, POST, etc. Read more in the Express documentation here.
This was my personal solution, and there may be better, but this solved my problem! Would be interested to know what others recommend! Of course the downside to this, is that I have to build all of my internal api routes to include /api/ in them, however that seems to be OK in design overall, and maybe even useful in keeping me from confusing my routes from front-side views.
Hope this at least helps somewhat, let me know if you need any clarifications :)

Angular URL removing # using express to route request

I tried to remove '#' from angular-urls, I have done the tweaks in my front-end like
$locationProvider.html5Mode(true);
<base href="/*"> in my view file.
I am using express in by backend. THough Its works well but when I refresh page it gives me error url not found.
My default url is like from express to render view is like,
//server.js
app.use(express.static('app'));
var routes = require('./routes/index');
app.use('/', routes);
/routes/index
router.all('/', function(req, res) {
res.render('home');
});
It works in case if I click http://localhost:1337/ first. It moves nicely to this http://localhost:1337/first-param but when I click http://localhost:1337/first-param directly without going to base url it gives me not found error.
Please don't give me hint for .htaccess. I don't want to use .htaccess.
Well.. you are only handling '/' route in your app. So every time you ask the server for another route say '/something', the server does not know what to do.
Try This (Use router.get() instead of router.all() to only handle get requests):
router.get('/*', function(req, res) {
res.render('home');
});
This should handle all the routes.
UPDATE
Of course you might want to have other routes handled by your express app, say for your API.
To do that, I suggest you create your api in the format '/api/foo/{bar}'
And you set up your router in the following way :
router.route(/^((?!\/(api)).)*$/).get(function(req, res){res.render('home')});
The regular expression holds true for everything that does not start with /api and returns the home page.
Remember, add this configuration BELOW all the other routes to maintain the heirarchy.
To summarize : The last snippet of code handles all the routes except the ones that start with /api and you design your API to always start with /api . Use this method and it should work just fine.
Use superstatic to serve your application.
Then use the options in my answer here: Combining AngularJS, HTML5 locations and superstatic

Angular routing authorization security

I am new to Angular and one of the things that I am trying to wrap my head around is the route authorization. I am coming from the .NET/IIS world where route authorization is as simple as decorating your API or MVC controller with the [Authorize] attribute.
I have read several posts and documents on how Angular handles routing. My concern is that the authorization is happening on the client. What prevents the user from firing up the dev tools, breakpoint the script execution, and changing the variables in the authorization service that control whether or not the user is authorized to access this route?
As I mentioned, I am new to Angular, so maybe I have misunderstood how the routing works. If this is the case, please correct me.
So, my question is: How can one achieve the same level of security with Angular routing as you would if using server-side routing authorization?
Thank you.
It is evident that pure client side solution cannot exist. Thus, only Angular routing cannot be used in cases when a certain route have to be securely restricted. I am thinking that routing has to be handled on both ends.
I am using Node so here is what I did:
//first evaluate the restricted route
app.get('/admin/*', function(req, res) {
//authorization
var authenticated = call_to_auth_service();
if (!authenticated) {
res.status(403);
res.end();
}
else {
//just remove the front /
var url = req.url.replace('/admin/', 'admin/');
res.render(url);
}
});
//open access - everything else goes back to the index page and there the angular routing takes over
app.get('*', function(req, res){
res.render('index');
});
This works, but I am not sure if this is the best approach. What do you think? Is this the proper way of handling the routing?
Thank you.

Using both Angular routeProvider and Express routes

I'm trying to make angular's routeProvider to manage all the routes of my app. I define my routes accordingly in the app.js file of angular (client side):
(function(){
var app = angular.module("Crowdsourcing", ["ngRoute"]);
app.config(function($routeProvider){
$routeProvider
.when("/single/:street/:buildingNumber/:apartmentNumber", {
templateUrl: "/views/single.html'",
controller: "ListingCtrl"
})
.otherwise({redirectTo:"/"});
});
}());
However, when writing a URL (e.g. /single) on the browser that request is tried to be handled by my backend express/node api. Thus, it is not found because the backend routes are for example /api/foo. How can I make sure Angular's routeProvider always manages my requests?
And more important what is the basic flow of the application? When Client writing a URL how does the application knows whether the url is handled by express js or by routeProvider ?
Generally you distinguish URLs which are handled by the angular app from the express URLs by using a # sign between hostname and page URL.
For example:
If you want to request an URL from the backend you would call:
http://host.com/api/foo/
Instead if you want to request an URL from the angular app you would call: http://host.com/#/single
So if you want to call the angular app URL you have add the # sign.
This is a very important fact you need to keep in mind when working with single page applications.
Update:
Have a look at this seed project: https://github.com/btford/angular-express-seed
It uses the a similar technology stack as your project. This should make it more clear for you.
I have faced the same issue few days ago. The problem is, even when you make a server call for data it redirects it to client side routing and searches in $routeProvider.
You need to make sure you add something like below code in your server.js.
It means when ever there is a call starting with /api then it will redirect it to the routes in server side instead of client side routing.
var api = express.Router();
require('./app/routes')(api);
app.use('/api', api);
In your factory when we make a call as below this will go to server side routes.js rather than client side.
$resource('/api/adduser')

Angular And Node routing

I am using Node for back end Angular for front end but what will happen if will declare same route for front end and backend. I haven't tried it yet.
E.g: If I am building a TODO app and if I have /todos back end service and I am rendering todos view with same route using angular.
AngularJS is processing the route after the # by default. So nothing will happen if you don't change that.
Otherwise, the backend route will be called.
Angular is client side browser framework. Your app defaults to slash(/) which points to your index.html. Your routes prefixed with hash(#) which prevents browser to make requests to server.
Angular defaults to client side & use its own routing mechanism. Express provides server side RESTful routes which behaves like an REST Api for your angular app.
In case if you want to use HTML5 Pushstate API(removes hash(/)) from default angular routing mechanism, the only thing which separates angular routes and express/server routes, You just need to structure your app like below.
express()
.use('/api', backend) // backend is express app
.use('/', www) // www is public/static files
.all('/*', function (req, res, next) {
"use strict";
// Just send the index.html for other files to support HTML5Mode
res.sendfile('./app/index.html', {root: __dirname});
})
.listen(process.env.PORT || 8888, function () {
debug('Express dev server listening on port ');
});
Your express/server routes lies after /api part, and other routes(obviously your angular routes) will return index.html(html snapshot).
Above mechanism is mostly preferable for MEAN web apps.
Angular provides its own routes after # like myhost.io/angular/#/someroute while node provides ordinar routes like myhost.io/some/other/route witch makes almost impossible to fail!
NOTE:
But you must be careful with setting routes, because if you have static files (like angular client), your routes with same path won't work using express or connect.

Resources