More Advanced Features for Our Grape API

After we got our intitial first Grape API up and running (including the nice swagger documentation) we started adding more features.

  • Entities & Doc configuration
  • authentication via token
  • CORS configuration
  • JSON:API
  • caching

Configuring the Swagger documentation

As nice as the default generated documentation is one always wants to change it around a little. Some stuff we found unnecessary hard to do other was easy.\ For instance the list of API endpoints is headed by a line autogenerated from the API class name. This was okay until we modularized everything and added a base API class. Suddenly the heading read “api: Operations about apis” which does not make a lot of sense. This can be configured by adding tags to EVERY route description. Below the example in which we tagged all endpoints except for one:

grape-swagger-tags-example.png

Futhermore details and tags do not play along well. So we had to drop the detailed explaination on one endpoint - will have to investigate this bug later.\ Other stuff such as hiding the swagger json url or the token field is simple and done by adding options to the ‘add_swagger_documentation’ call.

Grape::Entity

We then added an endpoint which returns not only a simple list of values but a more complex model. This seemed the right time to add grape_entity and the corresponding swagger gem. Entities are a kind of decorator, that is a class in which you define what attributes/methods are to be included and in which you also may do some formatting or rename attributes to something your API consumers may understand.

module Entities
  class ServiceProvider < Grape::Entity
    expose :full_name, as: :name, documentation: { type: 'string',
            desc: 'Service provider company name in latin. If applicable may be followed by company name in non latin spelling.'}
    expose :slug, documentation: { type: 'string', desc: 'Unique identifier in human readable form.' }
    expose :url, documentation: { type: 'url', desc: 'The URL for this service provider in our application.'}

    private
    def url
       Rails.application.routes.url_helpers.company_url(object, host: Figaro.env.base_url)
    end
  end
end

As you can se we renamed some attributes and included the URL under which a user may visit our entity. Next we used our grape entity to present our actual ActiveRecord objects ‘present service_providers, with: Entities::service_providers’ which gave us a nice and compact json response. We also hoped to easily add the documentation to our Swar UI. This is mainly done by providing ‘entity: Nirvana::Entities::ServiceProvider’ as a description parameter.

Authentication via Token

We already use a token based authentication for other parts of our existing application. So naturally we want to reuse this feature. The Swagger UI already has a placeholder for an api-key to be entered in the navigation bar, so activating this feature should be easy. And right enough it is simple to configure the Swagger-UI to always add the current_user token to each api request.

GrapeSwaggerRails.options.before_action do
  GrapeSwaggerRails.options.api_key_name = 'api_token'
  GrapeSwaggerRails.options.api_key_type = 'query'
  GrapeSwaggerRails.options.api_key_default_value = current_user.authentication_token
end

So by now the Swagger-UI adds the user specific api_token to every request triggered inside the UI. Of course we now have to ensure the token matches a user. For this we add a helpers block to our base API class with appropiate methods. These can now be used in any mounted API endpoint to authenticate our users.

  class API < Grape::API
    helpers do
      def current_user
        User.authenticate_from_token!(params[:api_token])
      end

      def authenticate!
        error!('401 Unauthorized', 401) unless current_user
      end
    end
  end

  class Cities < Grape::API
    resource :cities do
      get :all do
        authenticate!
        City.all
      end
  end

I would like to add the authenticate! call to all endpoints as a default, similar to before filters in rails controllers but there does not seem to exist a easy way of doing this so we will live with it for now.\ Also currently the documentation does not say anything about the authorization schema and how to use it. A potential user might miss the api_token parameter. In Grape there exists a security_definition schema which can be passed to the generated swagger documentation. Unfortunately this definition is not reflected in Swagger-UI, at least not in the version we use (bundled with grape-swagger-rails version 0.3.0) which is a pitty. Next try was to document the token as additional query parameter. This for one turns out to be a bit much for a nice clean documentation and also the parameter definition has to again be added to each endpoint which generates a lot of code (again some kind of default params for all endpoints would be nice).\ I am not yet decided but for now we will instead put some authentication documentation in the general API documentation which is shown in Swagger UI.

CORS

To allow CORS (cross origin resource sharing) we add the rack-cors gem and set our headers apropriately on the path under which our api is served (this is done in the host rails project’s application.rb).

#In config/application.rb

    config.middleware.insert_before 0, "Rack::Cors" do
      allow do
        origins '*'
        resource '/api/*', :headers => :any, :methods => [:get, :post, :options]
      end
    end

To ensure we do not remove our CORS configuration by accident a small integration test (yes you need integration because otherwise rack is not invoked) is added

class ApiConfigurationTest < ActionDispatch::IntegrationTest
  test "CORS is set up only for our api" do
    get '/api/something', nil, 'HTTP_ORIGIN' => '*'
    assert_equal '*', response.headers['Access-Control-Allow-Origin']

    get '/no_corse_for_other_pahts', nil, 'HTTP_ORIGIN' => '*'
    refute_equal '*', response.headers['Access-Control-Allow-Origin']
  end
end

And now?

So we still had JSON:API and caching on our todo list but the above took long enough so i will call it a day.\ Seems that JSON:API is not that easily integrated into grape. There exists some basic implementations which are quite limited (for instance do not support paging). So we will leave this for some other time.\ And other usefull stuff which you really should use in production (caching, throtteling, loggging) will be left for some other time.

Published: December 08 2016

  • category:
blog comments powered by Disqus