Blog

- January 12, 2016

In a previous post, we discussed what APISpark can bring to existing Web APIs and more specifically to those implemented with the Restlet Framework. This leverages the Connector feature that allows you to proxy existing Web APIs hosted outside the APISpark platform to easily apply services like API call monitor, analytics, rate limitations, etc.

Since this feature relies on the definition of Web APIs, it’s independant from the technologies used to implement them. In this post, we will focus on Node applications. The key feature remains the ability to produce the corresponding definitions in Swagger (or RAML).

Web frameworks for Node applications allow you to define processing for paths. This means you can easily deduce the different resources and methods supported by applications. It’s quite different regarding exchanged data. JavaScript is a flexible language without a “true” concept of class. Everything is an object. For this reason, it’s difficult to deduce the structure of exchanged data. The only existing applications that can introspect them are the ones that leverage a framework that integrates data structure validations.

In this article, we will describe how to leverage the Connector feature of APISpark with Web applications implemented with Node. We will focus on the two most popular Web frameworks available in this community:

  • Express is a very popular Web framework. As described on its website, it’s “a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. With myriad of HTTP utility methods and middleware at your disposal, creating a robust API is quick and easy.””
  • Hapi is another popular Web framework for Node. As described on its website, Hapi is “a rich framework for building applications and services. It enables developers to focus on writing reusable application logic instead of spending time building infrastructure.””

Let’s start by getting Swagger definitions from existing Node applications. These definitions could then be imported into Connectors to configure their contracts. For more details about how to do that, you can refer to our previous post.

Generating Swagger definitions from existing applications

Such a feature is not supported with all Web frameworks. As a matter of fact, for reasons previously mentioned, routing isn’t enough to deduce the complete definition of existing Web APIs. The feasibility mainly depends on the built-in validation support of the Web frameworks at use.

This approach won’t be possible with Express where validations have to be implemented within route handlers on your own. On the contrary, Hapi provides content validation support making the complete introspection possible.

Let’s deal now with Web APIs implemented using the Hapi framework.

With the Hapi framework

In addition to route definitions, Hapi allows you to specify validation rules to apply to elements in both requests and responses. In addition to routes, the hapi-swaggered plugin will leverage them to deduce definitions from the defined routes and validations. It relies on version 2 of Swagger.

The first thing is to install it using NPM:

$ npm install hapi-swaggered

We then need to register it into our Hapi application through the register method of the server:

server.register([
  {
    register: require('hapi-swaggered'),
    options: {
      tags: {
        contact: 'A Web API to handle contacts'
      },
      info: {
        title: Contact API',
        description: 'Web API with Swagger support',
        version: '1.0'
      }
    }
  }, function (err) {
    (...)
  });

During registration, you can provide general hints describing the Web API and tags.

Let’s reuse the contact manager sample from the previous post. We will focus on the methods that respectively return a list of contacts and add a contact. The first one simply corresponds to a GET method that accepts a set of parameters and returns a JSON payload. For inputs, we can leverage the query attribute within the validate attribute and define usable query parameters. To define a structure that can be returned, we can use the schema attribute within the response attribute. The Joi library that implements object schema description language and validator for JavaScript objects will help us define these structures.

server.route({
  path: '/contacts/',
  method: 'GET',
  config: {
    tags: ['api'],
    description: 'Get contact list',
    handler: function (request, reply) {
      var contacts = (...)
      return reply(contacts);
    },
    validate: {
      query: {
        limit: Joi.number().integer().min(1).max(100).default(10)
        (...)
      }
    },
    response: {
      schema: Joi.array().items(
        Joi.object().keys({
          id: Joi.string().required(),
          lastName: Joi.string().required(),
          firstName: Joi.string().required(),
          age: Joi.number().integer().optional()
        }).meta({ className: 'OutputContact' })
      )
    }
  }
});

You can also specify hints regarding other kinds of responses, in the case of server errors (status code 500) for example:

server.route({
  (...)
  config: {
    (...)
    response: {
      schema: (...),
      status: {
        500: Joi.object({
          code: Joi.string().description('error code).required(),
          message: Joi.string().description('error code).required()
        })
      }
    }
  }
});

Now that we tackled the GET method, let’s deal with the POST one that adds a contact. In this case, we add some validations for the request payload in the same way as previously. In this case, we leverage the payload attribute under the validate one. You can note that we don’t need the id attribute here.

server.route({
  path: '/contacts/',
  method: POST',
  config: {
    tags: ['api'],
    description: Add contact',
    handler: function (request, reply) {
      (...)
    },
    validate: {
      payload: Joi.object().keys({
          lastName: Joi.string().required(),
          firstName: Joi.string().required(),
          age: Joi.number().integer().optional()
        }).meta({ className: 'InputContact' })
      }
    },
    response: {
      (...)
    }
  }
});

After having started the server application, the Swagger API definition can be reached at the address http://localhost:8000/swagger. We can then directly import it into our Connector in the APISpark platform. The Connector will have the same contract as the Hapi application.

To finish, note that hapi-swaggered provides another plugin to display an embedded Swagger UI in our application.

Now let’s have a look at an approach that leverages Swagger definitions to build applications.

Using Swagger definitions to build Web APIs

When there is no suitable support to deduce the Swagger definitions from your existing Web APIs, you can consider the use of a design-driven approach. In this context you can leverage the Restlet Studio to visually craft Web APIs. In this case, it’s easiest to configure Connectors from the APISpark platform since you have the Swagger definitions at your disposal.

If you start the implementation of a Web API from scratch, several approaches are possible to implement Node applications from Web API definitions:

  • Defining the Swagger elements in JSDoc to provide an object describing the Swagger content per route when defining them.
  • Using a library enabling to attach processing on the paths defined in the definitions.

The “swaggerize*” projects also provide support for the second use case by leveraging the Swagger definition to configure routes and validation under the hood. Now you only need to implement handlers for these routes.

This approach is available for both Express and Hapi. Let’s try this approach with Express.

With Express

The support corresponds to the swaggerize-express module that can be installed using NPM:

$ npm install swaggerize-express

After having included it into the application, you can register the swaggerize function and register its return as a middleware in your Express application. At this level, you need to specify the following hints:

  • The local path to the Swagger file
  • The path to access the Swagger definition through the application
  • The root folder containing the JS files of handlers

Here is the piece of code that creates and initializes an Express application from a Swagger file:

var http = require('http');
var express = require('express');
var swaggerize = require('swaggerize-express');

app = express();

var server = http.createServer(app);

app.use(swaggerize({
  api: require('./contact-api.json'),
  docspath: '/api-docs',
  handlers: './handlers'
}));

server.listen(port, 'localhost', function () {
  app.swagger.api.host = server.address().address + ':' + server.address().port;
});

Folders and files under the handlers folder follow the paths defined within the Swagger definition file as described below.

handlers
  |-- contacts.js
  |-- contacts
  |    |--{id}.js

JavaScript files contain processing for routes. Corresponding Node modules need to return an object with one entry per supported method. The following file describes a typical handler file. The req and res parameters correspond to request and response objects of Express.

module.exports = {
  get: function (req, res) {
    var contacts = (...)
    res.json(contacts);
  },
  put: function (req, res) {
    (...)
  },
  (...)
};

Let’s have a look now at the twin project for the Hapi framework.

With the Hapi framework

The swaggerize-hapi project provides a similar approach but for the Hapi framework. This module can be installed similarly using NPM:

$ npm install swaggerize-hapi

This tool can be registered as a plugin within the Hapi server created from your application. As for Express, the path of the Swagger definition file and the handler root folder must be specified.

var Hapi = require('hapi'),
    Swaggerize = require('swaggerize-hapi');

var server = new Hapi.Server();

server.pack.register({
  plugin: Swaggerize,
  options: {
    api: require('./contact-api.json'),
    handlers: Path.join(__dirname, './handlers')
  }
});

For handlers, corresponding files must be created following the same locations as with Express and their contents are similar. The req and reply parameters correspond to Hapi objects for the request and to send back a response.

module.exports = {
  get: function (req, reply) {
    var contacts = (...)
    return reply(contacts);
  },
  put: function (req, reply) {
    (...)
  },
  (...)
}

Conclusion

The Connector feature of APISpark aims at proxying target applications using HTTP and relies on definition formats like Swagger and RAML. This makes this feature independant from any technology for target applications. In a previous article, we dealt with the Java technology with Restlet Framework-based applications.

We have focused here on the Node technology for applications using the popular Node Web frameworks: Express and Hapi. The challenge is to get a definition of Web APIs for such applications. In this context, some plugins can help you work with definition languages like Swagger. Two different approaches are supported within the Node community thanks to different plugins: the application-driven one that lets you generate the definition from existing applications and the design-driven one for applications based on existing definitions.