Using grunt server, how can I redirect all requests to root url? - node.js

I am building my first Angular.js application and I'm using Yeoman.
Yeoman uses Grunt to allow you to run a node.js connect server with the command 'grunt server'.
I'm running my angular application in html5 mode. According to the angular docs, this requires a modification of the server to redirect all requests to the root of the application (index.html), since angular apps are single page ajax applications.
"Using [html5] mode requires URL rewriting on server side, basically you have to rewrite all your links to entry point of your application (e.g. index.html)"
The problem that I'm trying to solve is detailed in this question.
How can I modify my grunt server to redirect all page requests to the index.html page?

First, using your command line, navigate to your directory with your gruntfile.
Type this in the CLI:
npm install --save-dev connect-modrewrite
At the top of your grunt file put this:
var modRewrite = require('connect-modrewrite');
Now the next part, you only want to add modRewrite into your connect:
modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.png$ /index.html [L]']),
Here is a example of what my "connect" looks like inside my Gruntfile.js. You don't need to worry about my lrSnippet and my ssIncludes. The main thing you need is to just get the modRewrite in.
connect: {
options: {
port: 9000,
// Change this to '0.0.0.0' to access the server from outside.
hostname: '0.0.0.0',
},
livereload: {
options: {
middleware: function (connect) {
return [
modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.png$ /index.html [L]']),
lrSnippet,
ssInclude(yeomanConfig.app),
mountFolder(connect, '.tmp'),
mountFolder(connect, yeomanConfig.app)
];
}
}
},
test: {
options: {
middleware: function (connect) {
return [
mountFolder(connect, '.tmp'),
mountFolder(connect, 'test')
];
}
}
},
dist: {
options: {
middleware: function (connect) {
return [
mountFolder(connect, yeomanConfig.dist)
];
}
}
}
},

FYI Yeoman/Grunt recently changed the default template for new Gruntfiles.
Copying the default middlewares logic worked for me:
middleware: function (connect, options) {
var middlewares = [];
var directory = options.directory || options.base[options.base.length - 1];
// enable Angular's HTML5 mode
middlewares.push(modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.png$ /index.html [L]']));
if (!Array.isArray(options.base)) {
options.base = [options.base];
}
options.base.forEach(function(base) {
// Serve static files.
middlewares.push(connect.static(base));
});
// Make directory browse-able.
middlewares.push(connect.directory(directory));
return middlewares;
}
UPDATE: As of grunt-contrib-connect 0.9.0, injecting middlewares into the connect server is much easier:
module.exports = function (grunt) {
// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);
grunt.initConfig({
// The actual grunt server settings
connect: {
livereload: {
options: {
/* Support `$locationProvider.html5Mode(true);`
* Requires grunt 0.9.0 or higher
* Otherwise you will see this error:
* Running "connect:livereload" (connect) task
* Warning: Cannot call method 'push' of undefined Use --force to continue.
*/
middleware: function(connect, options, middlewares) {
var modRewrite = require('connect-modrewrite');
// enable Angular's HTML5 mode
middlewares.unshift(modRewrite(['!\\.html|\\.js|\\.svg|\\.css|\\.png$ /index.html [L]']));
return middlewares;
}
}
}
}
});
}

There is a pull request I sent for this problem: https://github.com/yeoman/generator-angular/pull/132, but you need to apply it manually.

To deeply simplify #Zuriel's answer, here's what I found to work on my behalf.
Install connect-modrewrite: npm install connect-modrewrite --save
Include it in your grunt file: var rewrite = require( "connect-modrewrite" );
Modify your connect options to use the rewrite:
connect: {
options: {
middleware: function ( connect, options, middlewares ) {
var rules = [
"!\\.html|\\.js|\\.css|\\.svg|\\.jp(e?)g|\\.png|\\.gif$ /index.html"
];
middlewares.unshift( rewrite( rules ) );
return middlewares;
}
},
server: {
options: {
port: 9000,
base: "path/to/base"
}
}
}
Simplified this as much as possible. Because you have access to the middlewares provided by connect, it's easy to set the rewrite to the first priority response. I know it's been a while since the question has been asked, but this is one of the top results of google searching regarding the problem.
Idea came from source code: https://github.com/gruntjs/grunt-contrib-connect/blob/master/Gruntfile.js#L126-L139
Rules string from: http://danburzo.ro/grunt/chapters/server/

I tried all of these, but had no luck. I am writing an angular2 app, and the solution came from grunt-connect pushstate.
All I did was:
npm install grunt-connect-pushstate --save
and in the grunt file:
var pushState = require('grunt-connect-pushstate/lib/utils').pushState;
middleware: function (connect, options) {
return [
// Rewrite requests to root so they may be handled by router
pushState(),
// Serve static files
connect.static(options.base)
];
}
and it all worked like magic.
https://www.npmjs.com/package/grunt-connect-pushstate

Related

How do I add parameters to long-form return requests in module.exports routes?

I'm coding for an API connection area, that's predominately graphql but needs to have some REST connections for certain things, and have equivalent to the following code:
foo.js
module.exports = {
routes: () => {
return [
{
method: 'GET',
path: '/existing_endpoint',
handler: module.exports.existing_endpoint
},
{
method: 'POST',
path: '/new_endpoint',
handler: module.exports.new_endpoint // <--- this not passing variables
}
]
},
existing_endpoint: async () => {
/* endpoint that isn't the concern of this */
},
new_endpoint: async (req, res) => {
console.log({req, res})
return 1
}
}
The existing GET endpoint works fine, but my POST endpoint always errors out with the console of {} where {req, res} should have been passed in by the router, I suspect because the POST isn't receiving. I've tried changing the POST declaration in the routes to module.exports.new_endpoint(req, res), but it tells me the variables aren't found, and the lead-in server.js does have the file (it looks more like this...), and doing similar with the server.js, also getting similar results, implying that's probably wrong too. Also, we have a really strict eslint setup, so I can't really change the format of the call.
Every example I've seen online using these libraries is some short form, or includes the function in the routes call, and isn't some long form like this. How do I do a POST in this format?
/* hapi, environment variables, apollog server, log engine, etc. */
/* preceeding library inclusions */
const foo = require('./routes/foo')
const other_route = require('./routes/other_route')
const startServer = async () => {
const server = Hapi.server({port, host})
server.route(other_route.routes())
server.route(foo.routes())
}
This is a bug with Hapi in node v16. I just opened an issue.
Your current solutions are either:
Upgrade to Hapi v20
Use n or another method to downgrade to node v14.16 for this project. I can confirm that POST requests do not hang in this version.

Can I provide different baseref for Angular Universal SSR and clientside rendering?

I have an angular universal app. All works perfectly when run directly from node (localhost:4000) with the following commands:
npm run build:ssr
npm run serve:ssr
For production, I need to serve the app at a subpath (servername/appname) instead of root. The webserver is apache and I use proxypass as follows:
ProxyPass "/appname/" "http://localhost:4000/"
Now to the problem:
For SSR, the baseref is “/”, but for clientside-rendering, the baseref is “/appname”. This means, either SSR using node/express on root or the client running the app on servername/appname cannot find the files linked in index.html (main.js etc)
Is it possible to provide a different baseref for SSR and CSR?
A hack I can think of would be to host the SSR-app at “localhost:4000/appname”… but I couldn’t figure out how to configure this in my server.ts …
Any help much appreciated!
At least I've now got node running the SSR-app under the same href
const allowed = [
'.js',
'.css',
'.png',
'.jpg',
'.svg',
'.woff',
'.ttf'
];
const disallowed = [
'.map'
]
app.get('/appname/*', (req, res) => {
if (allowed.filter(ext => req.url.indexOf(ext) > 0).length > 0 && disallowed.filter(ext => req.url.indexOf(ext) > 0).length == 0) {
res.sendFile(resolve(DIST_FOLDER + req.url.replace("appname/", "")));
} else {
res.render('index', {req})
//res.sendFile(path.join(__dirname, 'client/dist/client/index.html'));
}
});

How to handle large number of redirects in Node/Vue app?

I am working on migrating an existing app to a new tech stack that uses Node and MongoDB on the backend and Vue on the frontend. I have a fairly large number of pages that will need to be redirected to new URLs (over 50). I know I can do something like this in the frontend:
const appRouter = new Router({
mode: 'history',
routes: [
{ path: '/a', redirect: '/a2' },
{ path: '/b', redirect: '/b2' },
{ path: '/c', redirect: '/c2' },
]
});
However it doesn't strike me as particularly elegant. I could see keeping the redirects in another file and importing them to keep my router file neater, but that seems like just a formatting benefit.
I'm wondering how other people handle a large number of redirects in Vue? Would this be better to do at the server-level with Node?
If boilerplate is the problem, you can use something like:
const router = new VueRouter({
routes: [
{ path: '/([abc])', redirect: to => {
returect to.path + '2'; // to.path will be like '/a'
}}
]
})
Notice that the part inside () is a regex that can be customized.
I have a fairly large number of pages that will need to be redirected to new URLs
When we talk about redirecting a Uniform Resource Locator (URL) in the context of a Single Page Application (SPA) like Vue with Vue Router, hosted by a web server like Node.js, we might mean one of two things:
we've changed the route of a view within our Vue SPA
we've changed the location of our SPA (the resource) from one location to another.
To determine which kind of redirect you need to do, we can examine how the URL will change. URLs are made up of these components:
scheme:[//[user[:password]#]host[:port]][/path][?query][#fragment]
By default, Vue Router uses the #fragment (hash) portion of the URL to change views, so if this changes then we should redirect using Alias or Redirect.
If any other portion of the URL changes, we should have Node.js return an HTTP status code for redirect, like 301 Moved Permanently or 302 Moved Temporarily.
Normally the solution from #acdcjunior is good enough, but sometimes you may prefer hooking beforeRouteUpdate to implement the redirect.
You can check vue-router: dynamic Routing for more details.
Below is one simple sample is from the official document:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
if ( to.match(new RegExp('your_regex_expression'))) {
next('redirect_url')
} else {
// default
next()
}
}
}
Or in main.js by using global guards:
import router from './router'
router.beforeEach((to, from, next) => {
if ( to.match(new RegExp('your_regex_expression'))) {
next('redirect_url')
} else {
// default
next()
}
})

How do I serve static files using Sails.js only in development environment?

On production servers, we use nginx to serve static files for our Sails.js application, however in development environment we want Sails to serve static files for us. This will allow us to skip nginx installation and configuration on dev's machines.
How do I do this?
I'm going to show you how you could solve this using serve-static module for Node.js/Express.
1). First of all install the module for development environment: npm i -D serve-static.
2). Create serve-static directory inside of api/hooks directory.
3). Create the index.js file in the serve-static directory, created earlier.
4). Add the following content to it:
module.exports = function serveStatic (sails) {
let serveStaticHandler;
if ('production' !== sails.config.environment) {
// Only initializing the module in non-production environment.
const serveStatic = require('serve-static');
var staticFilePath = sails.config.appPath + '/.tmp/public';
serveStaticHandler = serveStatic(staticFilePath);
sails.log.info('Serving static files from: «%s»', staticFilePath);
}
// Adding middleware, make sure to enable it in your config.
sails.config.http.middleware.serveStatic = function (req, res, next) {
if (serveStaticHandler) {
serveStaticHandler.apply(serveStaticHandler, arguments);
} else {
next();
}
};
return {};
};
5). Edit config/http.js file and add the previously defined middleware:
module.exports.http = {
middleware: {
order: [
'serveStatic',
// ...
]
}
};
6). Restart/run your application, e.g. node ./app.js and try to fetch one of static files. It should work.

Hapi.js Catbox Redis returning "server.cache is not a function"

So I'm like 99% sure I'm just screwing up something dumb here.
I'm trying to set up catbox to cache objects to redis. I have redis up and running and I can hit it with RDM (sql pro like utility for redis) but Hapi is not cooperating.
I register the redis catbox cache like so:
const server = new Hapi.Server({
cache: [
{
name: 'redisCache',
engine: require('catbox-redis'),
host: 'redis',
partition: 'cache',
password: 'devpassword'
}
]
});
I am doing this in server.js After this block of code I go on to register some more plugins and start the server. I also export the server at the end of the file
module.exports = server;
Then in my routes file, I am attempting to set up a testing route like so:
{
method: 'GET',
path: '/cacheSet/{key}/{value}',
config: { auth: false },
handler: function(req, res) {
const testCache = server.cache({
cache: 'redisCache',
expireIn: 1000
});
testCache.set(req.params.key, req.params.value, 1000, function(e) {
console.log(e);
res(Boom.create(e.http_code, e.message));
})
res(req.params.key + " " + req.params.value);
}
},
Note: My routes are in an external file, and are imported into server.js where I register them.
If I comment out all the cache stuff on this route, the route runs fine and returns my params.
If I run this with the cache stuff, at first I got "server not defined". So I then added
const server = require('./../server.js');
to import the server.
Now when I run this, I get "server.cache is not a function" and a 500 error.
I don't understand what I'm doing wrong. My guess is that I'm importing server, but perhaps it's the object without all the configs set so it's unable to use the .cache method. However this seems wrong because .cache should always be a default method with the default memory cache, so even if my cache registration isn't active yet, server.cache should theoretically still be a method.
I know it has to be something basic I'm messing up, but what?
I was correct. I was doing something stupid. It had to do with how I was exporting my server. I modified my structure to pull out the initial server creation and make it more modular. Now I am simply exporting JUST the server like so:
'use strict';
const Hapi = require('hapi');
const server = new Hapi.Server({
cache: [
{
name: 'redisCache',
engine: require('catbox-redis'),
host: 'redis',
partition: 'cache',
password: 'devpassword'
}
]
});
module.exports = server;
I then import that into my main server file (now index.js previously server.js) and everything runs fine. I can also import this into any other file (in this case my routes file) and access the server for appropriate methods.
Redis is happily storing keys and Hapi is happily not giving me errors.
Leaving here in case anyone else runs into a dumb mistake like this.

Resources