Categories
Hapi

Server-Side Development with Hapi.js — Server Method Cache

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Cache

We can cache method results with Hapi.

This is a great benefit that doesn’t come with regular functions.

For example, we can write:

const Hapi = require('@hapi/hapi');

const add = (x, y) => {
  return x + y;
};

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.method('add', add, {
    cache: {
      expiresIn: 60000,
      staleIn: 30000,
      staleTimeout: 10000,
      generateTimeout: 100
    }
  });

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return server.methods.add(1, 2);
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We add the cache property with the expiresIn property to set the expiry time. The time is in milliseconds.

staleIn sets when the data is invalidated. The time is in milliseconds.

stateTimeout sets the number of milliseconds before returning a stale value while a fresh value is generated.

generateTimeout is the number of milliseconds to wait before returning a timeout error.

We can replace expiresIn with expiresAt :

const Hapi = require('@hapi/hapi');

const add = (x, y) => {
  return x + y;
};

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.method('add', add, {
    cache: {
      expiresAt: '20:30',
      staleIn: 30000,
      staleTimeout: 10000,
      generateTimeout: 100
    }
  });

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return server.methods.add(1, 2);
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

expiresAt is the time of day expressed in 24h notation in HH:MM format.

It’s time art which the cache records for the route expires.

It can’t be used together with expiresIn .

We can override the ttl of as a server per-invocation by setting the flags.ttl property.

For example, we can write:

const Hapi = require('@hapi/hapi');

const add = (x, y, flags) => {
  flags.ttl = 5 * 60 * 1000;
  return x + y;
};

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.method('add', add, {
    cache: {
      expiresAt: '20:30',
      staleIn: 30000,
      staleTimeout: 10000,
      generateTimeout: 100
    }
  });

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return server.methods.add(1, 2);
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

to set the flags.ttl property to the timeout we want.

Generate a Custom Key

We can create a custom cache key for the cached results.

To do this, we write:

const Hapi = require('@hapi/hapi');

const add = (arr) => {
  return arr.reduce((a, b) => a + b);
};

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.method('add', add, {
    generateKey: (array) => array.join(',')
  });

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return server.methods.add([1, 2, 3]);
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

to add the generateKey method to generate a unique cache key with our own function.

Conclusion

We can change various cache options with Hapi server methods.

Categories
Hapi

Server-Side Development with Hapi.js — 404 Handling and Shared Methods

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

404 Handling

We can handle 404 errors easily with Hapi.

For example, we can write:

const Hapi = require('@hapi/hapi');
const Joi = require("@hapi/joi")

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    method: '*',
    path: '/{any*}',
    handler (request, h) {
        return '404 Error! Page Not Found!';
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We set the method to '*' to allow for all methods.

path is set to ‘/{any*}’ to allow the route handler to watch for any path.

Server Methods

The server.method method lets us add methods to our app.

Once we add the method, we can share them throughout the whole app.

For instance, we can write:

const Hapi = require('@hapi/hapi');

const add = (x, y) => {
  return x + y;
};

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.method('add', add, {});

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return server.methods.add(1, 2);
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We have the add function that adds 2 numbers.

Then we call the server.method method with it to register the method.

The first argument is the name. The 2nd argument is the function reference.

The 3rd argument is options, we can pass in.

Then we called it in the handler with server.methods.add(1, 2); .

So we get 3 as the response if we go to the / route.

We can also write:

const Hapi = require('@hapi/hapi');

const add = (x, y) => {
  return x + y;
};

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.method({
    name: 'add',
    method: add,
    options: {}
  });

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return server.methods.add(1, 2);
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

to do the same thing.

server.method is called with an object with the same data but they are property values instead of arguments.

Also, we can namespace our methods with a namespace string as the first argument.

For instance, we can write:

const Hapi = require('@hapi/hapi');

const add = (x, y) => {
  return x + y;
};

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.method('math.add', add);

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return server.methods.math.add(1, 2);
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We register our math.add method with:

server.method('math.add', add);

Then we call it with:

server.methods.math.add(1, 2)

Conclusion

We can handle 404 errors with a route and also add app-wide methods with server.methods with Hapi.

Categories
Hapi

Server-Side Development with Hapi.js — Query Parameters and Request Payloads

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Query Parameters

We can get query parameters in our route handler with the request.query property.

For example, we can write:

const Hapi = require('@hapi/hapi');
const Hoek = require('@hapi/hoek');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
  });

  server.route({
    method: 'GET',
    path: '/',
    handler: function (request, h) {
      return `Hello ${request.query.name}!`;
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

In the route handler, we use the request.query.name property to get the name property value.

Then when we make a GET request to /?name=foo , we see:

Hello foo!

returned as the response.

We can parse query strings with the query string parser of our choice.

For example, we can write:

const Hapi = require('@hapi/hapi');
const Qs = require('qs');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
    query: {
      parser: (query) => Qs.parse(query)
    }
  });

  server.route({
    method: 'GET',
    path: '/',
    handler: function (request, h) {
      return request.query;
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We add the query property to the object we passed into the Hapi.server function.

Then we set the parser method to return the object parsed by the Qs.parse method.

Request Payload

We can get the request body with the request.payload property.

For instance, we can write:

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    method: 'POST',
    path: '/',
    handler: function (request, h) {
      return request.payload;
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We returned the request.payload property to return the request body as the response.

So when we make a POST request to the / route with the body:

{
    "foo": "bar"
}

Then we get the same thing back as the response.

Options

We can add some route options to configure our routes.

For example, we can write:

const Hapi = require('@hapi/hapi');
const Joi = require("@hapi/joi")

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    method: 'POST',
    path: '/',
    options: {
      auth: false,
      validate: {
        payload: Joi.object({
          username: Joi.string().min(1).max(20),
          password: Joi.string().min(7)
        })
      }
    },
    handler: function (request, h) {
      return request.payload;
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We add the @hapi/joi module with the validate.payload property.

We set that to the Joi.object property with an object that has the fields for the object.

username field is a string with a min length of 1 and a max length of 20 as specified by:

Joi.string().min(1).max(20)

And password is a string with a min length of 7 as specified by:

Joi.string().min(7)

auth is set to false to disable auth on the route.

Conclusion

Hapi routes can accept query parameters and request bodies.

Categories
Hapi

Server-Side Development with Hapi.js — Routing

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Routing

We can add routes easily to our Hapi app.

For example, we can write:

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
  });

  server.route({
    method: 'GET',
    path: '/',
    handler (request, h) {
      return 'Hello World!';
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

to add a simple route with:

server.route({
  method: 'GET',
  path: '/',
  handler(request, h) {
    return 'Hello World!';
  }
});

The method has the request method the route accepts.

path has the route path.

handler has the route handler to handle the request.

request has the request data.

One route can handle multiple request methods.

For example, we can write:

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
  });

  server.route({
    method: ['PUT', 'POST'],
    path: '/',
    handler (request, h) {
      return 'Hello World!';
    }
  });

await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We have a route that handles both 'PUT' and 'POST' requests.

Path

A route can accept URL parameters if we put a parameter placeholder in our route path.

For example, we can write:

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
  });

  server.route({
    method: 'GET',
    path: '/hello/{user}',
    handler: function (request, h) {
      return `Hello ${request.params.user}!`;
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We have a route that takes the user request parameter.

Then we can get the route parameter value from the request.params.user property.

Now if we make a GET request to the /hello/bob route, we get Hello bob! .

The parameter value isn’t escaped by default. We can escape the string that’s passed by writing:

const Hapi = require('@hapi/hapi');
const Hoek = require('@hapi/hoek');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
  });

  server.route({
    method: 'GET',
    path: '/hello/{user}',
    handler: function (request, h) {
      return `Hello ${Hoek.escapeHtml(request.params.user)}!`;
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We required the @hapi/hoek package and then call Hapi.escapeHtml on the request.params.user string to escape it.

This will prevent any cross-site scripting attacks since any code strings will be sanitized.

Optional Parameters

We can make a parameter optional with the ? .

For instance, we can write:

const Hapi = require('@hapi/hapi');
const Hoek = require('@hapi/hoek');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
  });

  server.route({
    method: 'GET',
    path: '/hello/{user?}',
    handler: function (request, h) {
      const user = request.params.user || 'anonymous'
      return `Hello ${user}!`;
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We have {user?} in the route string to make the user parameter optional.

Then when we go to /hello , we get Hello anonymous!

And when we go to /hello/bob , we get Hello bob! .

Conclusion

We can add various kinds of routes with Hapi.

Categories
Hapi

Server-Side Development with Hapi.js — Plugins

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Create a Plugin

We can create plugins and use them with Hapi.

For example, we can write:

index.js

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
  });

  await server.register(require('./myPlugin'));

  server.route({
    path: '/',
    method: 'GET',
    handler(request, h) {
      return h.response('hello')
    },
  });
  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

myPlugin.js

const myPlugin = {
  name: 'myPlugin',
  version: '1.0.0',
  register: async function (server, options) {
    server.route({
        method: 'GET',
        path: '/test',
        handler: function (request, h) {
          return 'hello, world';
        }
    });
  }
};

module.exports = myPlugin

We have the myPlugin object with the register method to add the routes we want in the plugin.

We specify the name and version of the plugin to set the metadata.

Then in index.js , we have:

await server.register(require('./myPlugin'));

to register the plugin we added.

We can pass in some options by writing:

index.js

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
  });

  await server.register({
    plugin: require('./myPlugin'),
    options: {
      name: 'Bob'
    }
  });

  server.route({
    path: '/',
    method: 'GET',
    handler(request, h) {
      return h.response('hello')
    },
  });
  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

myPlugin.js

const myPlugin = {
  name: 'myPlugin',
  version: '1.0.0',
  register: async function (server, options) {
    server.route({
      method: 'GET',
      path: '/test',
      handler: function (request, h) {
        return options.name;
      }
    });
  }
};

module.exports = myPlugin

We get the option.name to get the name option in myPlugin.js .

Then to register the plugin, we write:

await server.register({
  plugin: require('./myPlugin'),
  options: {
    name: 'Bob'
  }
});

to register it with the plugins property to require the plugin file.

options lets us pass in the options.

We can also set a route prefix for the plugin.

For instance, we can write:

index.js

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0',
  });

  await server.register(require('./myPlugin'),{
    routes: {
      prefix: '/plugins'
    }
  });

  server.route({
    path: '/',
    method: 'GET',
    handler(request, h) {
      return h.response('hello')
    },
  });
  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

myPlugin.js

const myPlugin = {
  name: 'myPlugin',
  version: '1.0.0',
  register: async function (server, options) {
    server.route({
      method: 'GET',
      path: '/test',
      handler: function (request, h) {
        return 'hello world'
      }
    });
  }
};

module.exports = myPlugin

We create the plugin the same way as in the first plugin example.

Then in index.js , we write:

await server.register(require('./myPlugin'), {
  routes: {
    prefix: '/plugins'
  }
});

to set the route prefix.

Now when we go to the /plugins/test route, we see hello world as the response.

Conclusion

We can create plugins to modularize our Hapi app code.