Categories
Hapi

Server-Side Development with Hapi.js — Content-Type and Crypto

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.

Parsing Content-Type Header

We can parse the Content-Type request header by using the @hapi/content module.

For instance, we can do this by writing:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      const type = Content.type('application/json; some=property; and="another"');
      return type
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

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

We call Content.type with the Content-Type request header string to parse it.

Then we get:

{"mime":"application/json"}

as the value of type .

Parse Content-Disposition Header

Also, we can use the Content.disposition method to generate an object from the Content-Disposition request header value.

To do this, we write:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      const disp = Content.disposition('form-data; name="file"; filename=file.jpg');
      return disp
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

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

Then we get:

{"name":"file","filename":"file.jpg"}

as the value of disp .

CSRF Crumb Generation and Validation

We can generate the CSRF crumb and validate it with the @hapi/crumb module.

For example, we can use it by writing:

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

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

  await server.register({
    plugin: Crumb,
    options: {}
  });

  server.route({
    path: '/login',
    method: 'GET',
    options: {
      plugins: {
        crumb: {}
      },
      handler(request, h) {
        return 'success'
      }
    }
  });
  await server.start();
  console.log('Server running at:', server.info.uri);
};

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

We register the plugin with the server.register method.

Then we add a /login route that sets the crumbn property to accept the crumb.

Crypto

We can create random strings to use with our Hapi app with the @hapi/cryptiles module.

For instance, we can write:

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

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });
  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return cryptiles.randomString(10)
    }
  });
  await server.start();
  console.log('Server running at:', server.info.uri);
};

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

to return a random string response.

We generate the random string with the length 10 with cryptiles.randomString(10) .

Also, we can generate an alphanumeric string with cryptiles.randomAlphanumString:

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

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });
  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return cryptiles.randomAlphanumString(10)
    }
  });
  await server.start();
  console.log('Server running at:', server.info.uri);
};

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

And we can generate a random number with cryptiles.randomDigits:

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

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0'
  });
  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      return cryptiles.randomDigits(10)
    }
  });
  await server.start();
  console.log('Server running at:', server.info.uri);
};

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

Conclusion

We can parse the Content-Type and Content-Disposition header with the @hapi/content module.

And we can create random strings with the @hapi/cryptiles module.

Categories
Hapi

Server-Side Development with Hapi.js — 400 Series Errors

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.

Conflict

We can return a 409 response ith @hapi/boom with the Boom.conflict method by writing:

const Hapi = require('@hapi/hapi');  
const Boom = require('@hapi/boom');  
const init = async () => {  
  const server = new Hapi.Server({  
    port: 3000,  
    host: '0.0.0.0'  
  });  
  server.route({  
    method: 'GET',  
    path: '/',  
    handler(request, h) {        
      throw Boom.conflict('conflict');  
    }  
  });  
  await server.start();  
  console.log('Server running at:', server.info.uri);  
};  
process.on('unhandledRejection', (err) => {  
  console.log(err);  
  process.exit(1);  
});  
init();

Then when we go to the / route, we get:

{"statusCode":409,"error":"Conflict","message":"conflict"}

as the response.

Resource Gone Error

We can return the resource gone error by writing:

const Hapi = require('@hapi/hapi');  
const Boom = require('@hapi/boom');  
const init = async () => {  
  const server = new Hapi.Server({  
    port: 3000,  
    host: '0.0.0.0'  
  });  
  server.route({  
    method: 'GET',  
    path: '/',  
    handler(request, h) {        
      throw Boom.resourceGone('gone');  
    }  
  });  
  await server.start();  
  console.log('Server running at:', server.info.uri);  
};  
process.on('unhandledRejection', (err) => {  
  console.log(err);  
  process.exit(1);  
});  
init();

We call Boom.resourceGone to create the error object. The argument we pass in is the message.

Then we get:

{"statusCode":410,"error":"Gone","message":"gone"}

as the response when we go to / .

Length Required

We call the Boom.lengthRequired method to return a 411 response.

For example, we can write:

const Hapi = require('@hapi/hapi');  
const Boom = require('@hapi/boom');  
const init = async () => {  
  const server = new Hapi.Server({  
    port: 3000,  
    host: '0.0.0.0'  
  });  
  server.route({  
    method: 'GET',  
    path: '/',  
    handler(request, h) {        
      throw Boom.lengthRequired('length required');  
    }  
  });  
  await server.start();  
  console.log('Server running at:', server.info.uri);  
};  
process.on('unhandledRejection', (err) => {  
  console.log(err);  
  process.exit(1);  
});  
init();

We pass in the message as the argument.

Then we get:

{"statusCode":411,"error":"Length Required","message":"length required"}

as the response.

Precondition Failed Error

We can call Boom.preconditionFailed to returns a 412 response.

For instance, we can write:

const Hapi = require('@hapi/hapi');  
const Boom = require('@hapi/boom');  
const init = async () => {  
  const server = new Hapi.Server({  
    port: 3000,  
    host: '0.0.0.0'  
  });  
  server.route({  
    method: 'GET',  
    path: '/',  
    handler(request, h) {        
      throw Boom.preconditionFailed('precondition failed');  
    }  
  });  
  await server.start();  
  console.log('Server running at:', server.info.uri);  
};  
process.on('unhandledRejection', (err) => {  
  console.log(err);  
  process.exit(1);  
});  
init();

to pass in a message.

Entity Too Large

To return a 413 entity too large response, we call Boom.entityTooLarge .

For instance, we can write:

const Hapi = require('@hapi/hapi');  
const Boom = require('@hapi/boom');  
const init = async () => {  
  const server = new Hapi.Server({  
    port: 3000,  
    host: '0.0.0.0'  
  });  
  server.route({  
    method: 'GET',  
    path: '/',  
    handler(request, h) {        
      throw Boom.entityTooLarge('too big');  
    }  
  });  
  await server.start();  
  console.log('Server running at:', server.info.uri);  
};  
process.on('unhandledRejection', (err) => {  
  console.log(err);  
  process.exit(1);  
});  
init();

to throw the error.

And we get:

{"statusCode":413,"error":"Request Entity Too Large","message":"too big"}

as the response.

Unsupported Media Type

We can call Boom.unsupportedMediaType to throw a 415 unsupported media type error:

const Hapi = require('@hapi/hapi');  
const Boom = require('@hapi/boom');  
const init = async () => {  
  const server = new Hapi.Server({  
    port: 3000,  
    host: '0.0.0.0'  
  });  
  server.route({  
    method: 'GET',  
    path: '/',  
    handler(request, h) {        
      throw Boom.unsupportedMediaType('that media is not supported');  
    }  
  });  
  await server.start();  
  console.log('Server running at:', server.info.uri);  
};  
process.on('unhandledRejection', (err) => {  
  console.log(err);  
  process.exit(1);  
});  
init();

Then we get:

{"statusCode":415,"error":"Unsupported Media Type","message":"that media is not supported"}

as the response.

Conclusion

We can return kinds of error responses with Hapi Boom.

Categories
Hapi

Server-Side Development with Hapi.js — Throw 400-Series Errors

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.

Method Not Allowed Error

We can throw a method not allowed error with @hapi/boom ‘s methodNotAllowed method.

For instance, we can write:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.methodNotAllowed('that method is not allowed');
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

Then we get:

{"statusCode":405,"error":"Method Not Allowed","message":"that method is not allowed"}

as the response.

We can set the Allow header with the 3rd argument.

To do this, we write:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.methodNotAllowed('that method is not allowed', 'data', ['foo', 'bar']);
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

Now we should see that Allow response header has foo, bar as its value.

Not Acceptable Error

We can call the Boom.notAcceptable method to return a 406 error.

For instance, we can write:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.notAcceptable('unacceptable');
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

Then we get:

{
    "statusCode": 406,
    "error": "Not Acceptable",
    "message": "unacceptable"
}

as the response.

We can also pass in extra data into the 2nd argument.

Proxy Required Error

We call the Boom.proxyAuthRequired method to return a 407 response.

For instance, we can write:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.proxyAuthRequired('auth missing');
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

Then we get:

{
    "statusCode": 407,
    "error": "Proxy Authentication Required",
    "message": "auth missing"
}

as the response.

We can also pass in more data as the 2nd argument.

Request Timeout

We can throw a request timeout error with the Boom.clientTimeout method:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.clientTimeout('timed out');
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

Then we get:

{"statusCode":408,"error":"Request Time-out","message":"timed out"}

as the response.

We can also pass in more data as the 2nd argument.

Conclusion

We can throw many types of errors easily with the @hapi/boom module.

Categories
Hapi

Server-Side Development with Hapi.js — Throwing Errors

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.

Throw Unauthorized Errors

We can throw unauthorized errors with the @hapi/boom module.

It has the Boom.unauthorized method to throw the error.

For instance, we can write:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.unauthorized('invalid password');
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We call Boom.unauthorized method with a message to create the 401 response.

Now we should get:

{"statusCode":401,"error":"Unauthorized","message":"invalid password"}

when we make a GET request to the / route.

We can pass in extra data in the 3rd argument.

For example, we can write:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.unauthorized('invalid password', 'sample', { ttl: 0, cache: null, foo: 'bar' });
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

The object in the 3rd argument will be added to the attributes property of the response.

So we get:

{"statusCode":401,"error":"Unauthorized","message":"invalid password","attributes":{"ttl":0,"cache":"","foo":"bar","error":"invalid password"}}

as the response.

The 2nd argument will be in the Www-Authenticate response header.

We get:

sample ttl="0", cache="", foo="bar", error="invalid password"

as its value.

Payment Required Response

We can return a 402 response with the Boom.paymentRequired method.

For instance, we can write:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.paymentRequired('bandwidth used');
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We call Boom.paymentRequied with the message that we want to return.

Then we get:

{"statusCode":402,"error":"Payment Required","message":"bandwidth used"}

as the response.

Forbidden Error

We can throw a forbidden error with the Boom.forbidden method:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.forbidden('try again some time');
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

Then we get:

{"statusCode":403,"error":"Forbidden","message":"try again some time"}

as the response.

Not Found Error

We can return a 404 error with the Boom.notFound method:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.notFound('missing');
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

Then we should get:

{"statusCode":404,"error":"Not Found","message":"missing"}

as the response.

Conclusion

We can throw various kinds of errors easily with the @hapi/boom module.

Categories
Hapi

Server-Side Development with Hapi.js — Base64 and Errors

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.

Base64 Encoding

We can handle base64 encoding and decoding with the @hapi/b64 module.

For instance, we can write:

const Path = require('path');
const Hapi = require('@hapi/hapi');
const Fs = require('fs');
const B64 = require('@hapi/b64');

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0',
    debug: { request: ['error'] }
  });

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      const stream = Fs.createReadStream(Path.join(__dirname, 'package.json'));
      const encoder = new B64.Encoder();
      stream.pipe(encoder).pipe(process.stdout);
      return 'success'
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We call Fs.createReadStream to create the srean.

Then we create the encoder object with the B64.Encoder constructor.

And then we pass the encoder to the stream.pipe method to encode the stream into a base64 string.

And then we pass that into stdout with another pipe call.

Base64 Decoding

We can decode base64 text into its original content with the @hapi/b64 module.

To do this, we write:

const Path = require('path');
const Hapi = require('@hapi/hapi');
const Fs = require('fs');
const B64 = require('@hapi/b64');

const init = async () => {
  const server = new Hapi.Server({
    port: 3000,
    host: '0.0.0.0',
    debug: { request: ['error'] }
  });

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      const stream = Fs.createReadStream(Path.join(__dirname, 'encodedfile.b64'));
      const decoder = new B64.Decoder();
      stream.pipe(decoder).pipe(process.stdout);
      return 'success'
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We call the Fs.createReadStream to create the read stream and read the file with the given path.

Then we create the decoder object with the B64.Decoder constructor.

And then we call stream.pipe with the decoder to do the decoding.

Then we call pipe again to pipe the decoded content to stdout .

Error Response

We can add a login route easily with the @hapi/boom module.

To use it, we write:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      throw Boom.badRequest('invalid query');
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We call Boom.badRequest to create an Error object that we can throw.

It takes the error message as the argument.

Then we throw that and we should see:

{"statusCode":400,"error":"Bad Request","message":"invalid query"}

returned as the response.

We can check if an object is an instance of the Boom.Boom constructor by writing:

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

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

  server.route({
    method: 'GET',
    path: '/',
    handler(request, h) {
      const err = Boom.badRequest('invalid query');
      return Boom.isBoom(err);
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We call Boom.isBoom to do the check.

It should return true since err is an instance of Boom.Boom .

Conclusion

We can return error responses easily with the @hapi/boom module.

The @hapi/base64 module lets us encode and decode base64 content.