hapi-bookshelf-jsonapi

0.5.1 • Public • Published

hapi-bookshelf-jsonapi

Uses Bookshelf models to produce JSON API server routes on a Hapi server.

Installation

npm install hapi-bookshelf-jsonapi --save

You should also install hapi-json-api to get functionality it provides:

npm install @gar/hapi-json-api --save

Setup

Now we need to register these plugins:

server.register([
  { register: require('@gar/hapi-json-api') },
  {
    register: require('hapi-bookshelf-jsonapi').register,
    options: {
      resources: {
        'author': {
          model: 'Author',          // Model name in registry
          basePath: '/authors',
          specialColumn: {          // Optional info about special columns
            updated: 'updated_at',  // Column set to NOW() on PATCH etc.
            meta: ['created_at', 'updated_at'], // Columns included in "meta"
                                                // instead of "attributes"
            hidden: ['password'],
          },
          relationships: {
            hasOne: {
              publisher: {
                type: 'company',
                column: 'publisher_id',
                readonly: true,     // Do not allow PATCH etc.
              },
            },
            hasMany: {
              books: {
                type: 'book',
              },
            },
          },
        },
      },
    },
  },
], function (err) {
  if (err) throw err;
});

Unfortunately, Bookshelf.js does not make it easy to inspect a Model and get information about its relationships, so we need to re-declare them in the hapi-bookshelf-jsonapi configuration. To avoid having your relationships declared in separate files, we could do:

relationships: server.plugins.bookshelf.model('Author').relationships,

And then declare relationships as a static model attribute:

return baseModel.extend({
  tableName: 'authors',
  // bookshelf relationships
}, {
  relationships: {
    hasOne: { /* jsonapi relationships */ },
    hasMany: { /* jsonapi relationships */ },
  },
});

Error Handling

The @gar/hapi-json-api plugin will handle converting Boom errors and uncaught exceptions into the expected JSON API format. However, to convert things like database uniqueness errors into the appropriate 409 Conflict, we need to intercept them before they get to @gar/hapi-json-api:

server.ext({
  type: 'onPreResponse',
  options: {
    before: '@gar/hapi-json-api',
  },
  method: function (request, reply) {
    const response = request.response;
    if (response.isBoom) {
      const err = response;
      // The following logic is specific to PostgreSQL databases!
      if (err.code === '23505') {
        const newErr = Boom.create(409, err.message);
        Object.assign(response.output, newErr.output);
      } else if (err.code && err.code.match(/^2[23]/)) {
        const newErr = Boom.create(400, err.message);
        Object.assign(response.output, newErr.output);
      } else if (response.output.statusCode === 500) {
        console.error(err);
      }
    }
    reply.continue();
  },
});

Authorization

Sometimes you won't want to give all authenticated user's access to all JSON API resources. You can use Hapi's onPreHandler extension point, in conjunction with some information exposed in the route configuration, to add your own logic:

server.ext('onPreHandler', function (request, reply) {
  try {
    const creds = request.auth.credentials;
    const route = request.route.settings.plugins['hapi-bookshelf-jsonapi'];
    console.log(route);   // To see everything that's available.
    if (creds.role !== 'admin') {
      if (route.action !== 'fetch' && route.action !== 'index') {
        throw Boom.forbidden();
      }
    }
  } catch (err) {
    if (err.isBoom) return reply(err);
  }
  return reply.continue();
});

Readme

Keywords

none

Package Sidebar

Install

npm i hapi-bookshelf-jsonapi

Weekly Downloads

1

Version

0.5.1

License

MIT

Last publish

Collaborators

  • icgood