How to setup Sails.js routes to support pushstate with a SPA on the frontend - api

How to setup SailsJS routes to support pushstate, but still be able to serve requests prefixed with /api along with serving static assets as well?
Using:
Backbone 1.x with pushState: true
Sails 0.10.x
Suggested solution from this thread is to use the /* wildcard and redirect all to the index view.
/path/to/app/config/routes.js
'/*': {
view: 'index'
}
The problem is that this will redirect everything to index. including static file assets, the express.static middleware doesn't seem to have an effect, this route is somehow taking precedence.
Also, this prefix below doesn't have any effect, since the route above takes precedence, however the prefix works if I just remove the wildcard i.e '/': { view: 'index' }
/path/to/app/config/blueprint.js
module.exports.blueprints = {
...
prefix: '/api',
...
}
Obviously, this does not seem as a core SailsJS issue, but rather my minimal knowledge the expressjs router, since that's what sailsjs is using.
Theoretically, I could explicitly just list out all the assets prefixed routes and have a controller to serve all, since they are well known and static i.e. '/js*': { controller: 'AssetsController', action: 'serve', and so on for '/styles*', '/images*', '/templates*', '/fonts*', but I'm really hesitant on doing so, I'm hoping for a better practice solution.
Also that won't solve this routes /api problem with the wildcard '/*'

If you're willing to use the development version of Sails from GitHub, you can use the skipRegex route option to let your wildcard route ignore API requests, and the skipAssets option to have it ignore asset URLs:
'/*' : {view: 'index', skipAssets: true, skipRegex: /^\/api\/.*$/}
Otherwise, you can create a controller to serve your view, and add the code to skip unintentionally-matched URLs in the action code:
// api/controllers/StaticController.js
module.exports = {
index: function(req, res, next) {
if (req.path.match(/\..*/g) || req.path.match(/^\/api\/.*$/)) {
return next();
}
return res.view('index');
}
}
Then in /config/routes.js:
'/*': 'StaticController.index'
......

Try updating your config/404.js file.
Remove the following:
res.status(result.status);
res.render(viewFilePath, function (err) {
// If the view doesn't exist, or an error occured, send json
if (err) { return res.json(result, result.status); }
// Otherwise, serve the `views/404.*` page
res.render(viewFilePath);
});
Add this: res.redirect("/");
Cheers

Related

How to redirect if subdirectory includes hash Nuxt?

At the request of the SEO specialist, I need to implement the following functionality.
I have to redirect if the link contains a capital letter.
For example https//domain.com/#Contacts ==> https//domain.com/#contacts.
In Nuxt I implemented with by creating function on server.js file which located on middleware folder.
But it doesn't work if path contains hash(#)
export default function (req, res, next) {
const url = req.url;
if (url !== url.toLowerCase()) {
res.writeHead(301, { Location: url.toLowerCase() });
res.end()
} else {
next();
}
}
I would be grateful if you answer or help
The part after the hash (fragment identifier) is never going to be sent to the server by your browser, so what you're trying to do is impossible using redirects.
You can access them on the client-side, but I don't think that it would do any good in terms of SEO.

Nuxt.js routing

I am migrating from ASP.NET MVC to Nuxt.js and I am trying to keep the same urls so I can keep the SEO ratings
My current URLs are
www.url.com/#computers/#laptops/#dell?page=1
All of the # are dynamic values so URL might also be
www.url.com/#phones/#smartphones/#apple?page=1
What is the easiest way to point all these URLs to one page ITEMS and to be able to get values for every # and also for QueryString values ?
Thanks in advance
You can accomplish this with serverMiddleware in nuxt
Make a file for your middleware:
middleware/server/seoMiddleware.js
Add that to your nuxt.config.js in the serverMiddleware block:
serverMiddleware: [
'~/middleware/server/seoMiddleware.js'
]
Create a file to handle a list of your 301's:
middleware/server/301.js
Add your path mapping using regex in your 301.js:
export default [
{ from: '/(#[\w\d]+)\/(#[\w\d]+)\/(#[\w\d]+)', to: 'items', capture: true }
]
Pop open your seoMiddleware.js
const redirects = require('./301.js').default
export default function (req, res, next) {
const redirect = redirects.find(redirect => req.url.test(redirect.from))
if (redirect) {
const to = redirect.capture ? applyCapturesToUrl(req.url, redirect) : redirect.to
res.writeHead(301, { Location: to })
res.end()
}
// failover
next()
}
const applyCapturesToUrl(url, redirect) {
const matches = url.match(redirect.from)
return `/${redirect.to}/` + matches.map(match => match.replace('#', '')).join('/')
}
We've used some simple regex here to create capture groups based on your URL structure and and serverMiddleware to intercept the request and return a 301 (permanent redirect) if we find a match. We failover with next() and allow the request through if there was no match found. We use a capture flag on the redirect object so we know when we're dealing with capture groups, without it we can just do simple pattern matching.

Do we need to use plugins for modularizing

Is it mandatory to use plugins for modularizing the code. Can we just create js files and put the handlers and routes in different js files and export them as needed to achieve modularization.
Plugins are just Hapi's way of modularizing application code.
For instance, in my application i wanted to perform https and www redirections on requests.
Initially the code looked like this -
server.ext({
type: 'onRequest',
method: function (request, reply) {
if (/^www\./.test(request.headers.host)) {
return reply()
.redirect('https' + '://' + request.headers.host.replace(/^www\./, '') + request.url.path)
.code(301);
} else {
reply.continue();
}
}
});
After that I created a plugin hapi-gate, thinking that other people like me would also have this need.
Now my code looks like this -
server.register({
register: require('hapi-gate'),
options: {https: true,
www: true} // will force https and www on all requests
})
You decide now which one looks cleaner and modularized..

Laravel routes.php include file using Session

Not sure if this is possible, but here it goes.
What I am looking to do is include my "admin" routes as a separate file, only if the user is an admin (therefore a non admin will get a 404 error
routes.php
if( Session::get('user')->is_admin )
require_once('routes-admin.php');
if( Auth::check() )
require_once('routes-user.php');
Route::get('/', function() {
return view('home');
});
routes-admin.php
Route::get('admin', function() {
return view('admin-dashboard');
});
routes-user.php
Route::get('user', function() {
return view('user-dashboard');
});
What I am trying to do is avoid having the test repeated with every single Route
so if my user segment has 10 pages I currently need 30 lines of code dedicated to Auth::check() (the if, else and redirect if not), where I can instead have a single check on routes.php and the user will get a 404 if they don't belong
Is there a way to perform this check outside of the Route?
Perhaps you want to read documentation first?
Route::group(['middleware' => 'auth'], function()
{
Route::get('/', function()
{
// Uses Auth Middleware
});
Route::get('user/profile', function()
{
// Uses Auth Middleware
});
});
Above code does exactly what you need, is "person logged in?" let him go to page "whatever".
You can create middlewares (check if user is admin or basic user) yourself and apply on groups.
Example middleware
class BeforeMiddleware implements Middleware
{
public function handle($request, Closure $next)
{
// Perform action
return $next($request);
}
}
Do not get me wrong, just your approach is really not Laravel like. Try to see some open source projects done in L5 or even in L4. Try to use everything Taylor already done for you. Documentation is your firend here.
Following the response of #Kyslik for the middleware, you can "include" your own routes file in your RouteServiceProvider like the default routes file, the RouteServiceProvide is located in: app/Providers/RouteServiceProvider.php,
Find the section
require app_path('Http/routes.php');
and just replicate with the name of your routes file want to include

How to defer routes definition in Angular.js?

I have configured some basic routes that are available for all users before they log in:
App.config(function ($routeProvider) {
$routeProvider.
when('/login', { templateUrl: 'views/login.html', controller: PageStartCtrl.Controller }).
otherwise({ redirectTo: '/login' });
});
So the only thing user can do is to log in. After the user logs in, I would like to register additional routes like this:
$http
.post('api/Users/Login', { User: userName, Password: userPassword })
.success(function (response : any) {
App.config(function ($routeProvider) {
$routeProvider
.when('/dashboard',
{ templateUrl: 'part/dashboard.html',
controller: DashboardCtrl.Controller });
});
However, I suppose I should call .config method only once, because the $routeProvider is brand new instance that knows nothing about /login route. Further debugging showed me that the first instance of $resourceProvider is used when resolving view change.
Q: Is there a way how to register routes later?
Solution from Add routes and templates dynamically to $routeProvider might work, but is quite ugly (involved global variable nastyGlobalReferenceToRouteProvider).
Since routes are defined on a provider level, normally new routes can only be defined in the configuration block. The trouble is that in the configuration block all the vital services are still undefined (most notably $http). So, on the surface it looks like w can't define routes dynamically.
Now, it turns out that in practice it is quite easy to add / remove routes at any point of the application life-cycle! Looking at the $route source code we can see that all the routes definition are simply kept in the $route.routes hash which can be modified at any point in time like so (simplified example):
myApp.controller('MyCtrl', function($scope, $route) {
$scope.defineRoute = function() {
$route.routes['/dynamic'] = {templateUrl: 'dynamic.tpl.html'};
};
});
Here is the jsFiddle that demonstrates this in action: http://jsfiddle.net/4zwdf/6/
In reality, if we want to be close to what AngularJS is doing the route definition logic should be a bit more complex as AngularJS is also defining a redirect route to correctly handle routes with / at the end (make it effectively optional).
So, while the above technique will work, we need to note the following:
This technique depends on the internal implementation and might break if the AngularJS team decides to change the way routes are defined / matched.
It is also possible to define the otherwise route using the $route.routes as the default route is stored in the same hash under the null key
I found that the answer by #pkozlowski.opensource works only in angularjs 1.0.1. However, after angular-route.js becomes an independent file in the later version, directly set the $route doesn't work.
After reviewing the code, I find the key of $route.routes is no longer used to match location but $route.route[key].RegExp is used instead. After I copy the origin when and pathRegExp function, route works. See jsfiddle:
http://jsfiddle.net/5FUQa/1/
function addRoute(path, route) {
//slightly modified 'when' function in angular-route.js
}
addRoute('/dynamic', {
templateUrl: 'dynamic.tpl.html'
});