Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

nodejs hapi single page

I have a single app site (NodeJS) and I want to migrate from Express to Hapi, what I normally do is serve static files and route everything else to a single page which contains the angularjs app and the angular routing configuration.

// Express routing, first the static files
app.use( express.static(__dirname + '/public') );

// Second the api routes
app.get('/api', function(req, res){
    res.send( {api: 'response' } )
});

// Finally everything else maps to the single page app:
app.get('*', function(req, res){
    res.sendfile('./public/html/controllers.index.html')
});

In HapiJS I dont know how to replicate the same code (without using express.static middleware), because:

Hapi = require('hapi');
var server =  new Hapi.Server('localhost', 84);

server.route({
    method: 'GET',
    path: '/{p*}',
    handler: function (request, reply) {
        reply.file('public/html/index.html');
    }
});

In the code above, every request no matter what will be mapped to my single page ('public/html/index.html'), but if I do this, then the js, css, jpg & files will be mapped to the same file instead to the scripts, styles and images (a request to '/images/bg.png' will download the single page instead the image file).

I know that if I set up the path '/' to my single page and then '{p*}' to '{directory: {path: '/public'}}' then I will have the behaviour that I need, but theres one catch, if some user copy and paste an specific url (lets say '/account/login') and then hit enter, that route will be mapped in HapiJS and the response will be 'Not Found (404)', angular routing will never be able to respond.

Does anybody know how to solve this?

The key part of the question is:

  • Use only HapiJS (no express or other middleware)
  • Don't route every angular route (just route everything else not already routed to single page son angular can handle the routing)
like image 336
Amílcar Calles Avatar asked Oct 16 '14 04:10

Amílcar Calles


5 Answers

This answer is invalid. As @Al jey said in the comment. Hapi have their own algorithm to sort the routes, please don't follow this answer.


Similar to expressJS, Hapi handle route by order. Just define routes order by priority:

server.route(
{    // Angular/API Route
    method: 'GET',
    path: '/api',
    handler: function (request, reply) {
         reply( {api: 'response' } )
    }
});

server.route({    // Other assets If you have
    method: 'GET',
    path: '/assets/{param*}',
    handler: {
    directory: {
        path: './assets',
        listing: false,
        index: true
    }
});

server.route({   // Everything else 
    method: 'GET',
    path: '/{p*}',
    handler: function (request, reply) {
        reply.file('public/html/index.html');
    }
});
like image 171
Hereblur Avatar answered Oct 06 '22 00:10

Hereblur


Not sure if this will help you out but your starting code is a bit "odd" for Hapi.js. This is what i use to set up a simple hapi.js SPA.

If you wish to use specific URLS like the Account/Login, you have to direct your path to that specific section.(path: '/account/login')

The difference lies in the fact that i'm pointing to the directory as a whole, incl. the different files, and you simply reply the index.html file. The listing parameter lets you decide if you want to show directory structure or not in your urls. Default is false.

More info here: http://hapijs.com/tutorials/serving-files#directory-handler

var Hapi = require('hapi');
var Path = require('path');

var server = new Hapi.Server(8080, 'localhost');


server.route({
    method: 'GET',
    path: '/{path*}',
    handler: {
 		directory: {
            path: './public',
            listing: false,
            index: true
        }
    }      
});



server.start(function(){
	console.log('server started');
});
like image 37
Jan_dh Avatar answered Oct 05 '22 22:10

Jan_dh


Inspired by the inert documentation, I solved this by serving everything out of a static directory. Then I added an onPostHandler to return the index file whenever a 404 was about to be returned. You client side router could then send a redirect to an existing 404.html file in the static files directory.

// Static Assets
server.route({
  method: 'GET',
  path: '/{param*}',
  handler: {
    directory: {
      path: ['app/static'],
      listing: false,
      index: ['index.html']
    }
  }
});

// return index.html for everything else
server.ext('onPostHandler', (request, reply) => {
  console.log('WORD');
  const response = request.response;
  if (response.isBoom && response.output.statusCode === 404) {
    return reply.file('app/static/index.html');
  }
  return reply.continue();
});
like image 28
Dave Jensen Avatar answered Oct 06 '22 00:10

Dave Jensen


i had success with inert like described here. If you scroll down, you can find the section "Directory handler".

My solution looks like that:

var Hapi = require('hapi');
var Path = require('path');

var server = new Hapi.Server();
server.connection({ 
  host: 'localhost',
  port: 8001 
});

server.register(require('inert'), (err) => {

  if (err) {
    throw err;
  }

  server.route({
    method: 'GET',
    path: '/{param*}',
    handler: {
      directory: {
        path: 'public'
      }
    }
  });


  server.start((err) => {

    if (err) {
      throw err;
    }

    console.log('Server running at:', server.info.uri);
  });
});
like image 36
divramod Avatar answered Oct 06 '22 00:10

divramod


Make sure you have inert as a plugin.

In your server.js (or whatever you named it), configure the relative path to serve static files see below

const server = new Hapi.Server({
  connections: {
    routes: {
      cors: true,
      files: {
        relativeTo: Path.join(__dirname, 'public')
      }
    },
    router: {
      stripTrailingSlash: true
    }
  }
})

then for your route, register a new route plugin like below. This is assuming you entry index.html (for React or Angular) is inside the public directory as configured above

exports.register = (server, options, next) => {
  server.route([{
    method: 'GET',
    path: '/{param*}',
    handler: {
      directory: {
        path: '.',
        redirectToSlash: true,
        listing: false,
        index: true
      }
    }
  }])
  server.ext('onPostHandler', (req, res) => {
    const response = req.response
    if (response.isBoom && response.output.statusCode >= 404) {
      return res.file('index.html')
    }
    return res.continue()
  })
  next()
}

exports.register.attributes = {
  name: 'static-route',
  version: '1.0.0'
}

Now everytime HapiJS throws a 404 error, the route it is forwarded to your React / Angular App, which can then handle the route if it exists.

like image 38
Dee S Avatar answered Oct 06 '22 00:10

Dee S