Mounting and Remounting APIs

Grape APIs can be run as standalone Rack applications or mounted within other frameworks like Rails or Sinatra. This flexibility allows you to integrate powerful APIs into existing projects seamlessly.

Pre-loading Routes

By default, Grape compiles routes on the first request. For production environments, it's recommended to pre-load routes at application boot time using the compile! method.

# In config.ru, application.rb, or an initializer
Twitter::API.compile!

Mounting as a Rack Application

A Grape API class is a complete Rack application. You can run it directly from a config.ru file with a server like rackup.

# config.ru
require './my_api'

MyAPI.compile!
run MyAPI

Mounting Alongside Other Frameworks

If you want to run Grape alongside another Rack framework like Sinatra, you can use Rack::Cascade to chain multiple applications together.

Note: The order in Rack::Cascade matters. The Grape application should typically be last if you want it to handle its own 404 errors. If it's not last, a 404 or 405 response will signal Rack::Cascade to try the next application in the chain.

# config.ru
require 'sinatra'
require 'grape'

class MyAPI < Grape::API
  get :hello do
    { hello: 'world' }
  end
end

class MyWebApp < Sinatra::Base
  get '/' do
    'Hello from Sinatra.'
  end
end

# Requests will first go to MyWebApp, then to MyAPI if not handled.
run Rack::Cascade.new [MyWebApp, MyAPI]

Mounting in a Rails Application

Integrating Grape into a Rails application is a common use case.

  1. File Location: Place your API files in app/api/. For a class Twitter::API, the file should be located at app/api/twitter/api.rb.

  2. Autoloading: Ensure Rails autoloads your API directory by adding it to the autoload paths in config/application.rb.

    # config/application.rb
    config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
    config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
  3. Zeitwerk Inflection (Rails 6+): Rails's default autoloader, Zeitwerk, inflects api as Api. To correctly load API, add an acronym inflection in config/initializers/inflections.rb.

    # config/initializers/inflections.rb
    ActiveSupport::Inflector.inflections(:en) do |inflect|
      inflect.acronym 'API'
    end
  4. Mount in Routes: Mount your API in config/routes.rb.

    # config/routes.rb
    Rails.application.routes.draw do
      mount Twitter::API => '/'
    end

Mounting APIs within APIs (Modules)

You can compose a larger API by mounting smaller, modular API implementations inside a primary one. This is useful for organizing your codebase.

class MainAPI < Grape::API
  # Mount two different API versions
  mount V1::API
  mount V2::API

  # You can also mount on a specific path
  mount Users::API => '/users'
end

Callbacks like before, after, and rescue_from defined in the parent API will be inherited by all mounted APIs.

class MainAPI < Grape::API
  before do
    header 'X-Shared-Header', 'This is for all mounted APIs'
  end

  mount Users::API
  mount Statuses::API
end

Remounting

You can mount the same API endpoints in multiple locations. This is powerful for creating reusable components.

class VotingAPI < Grape::API
  namespace 'votes' do
    get { 'List of votes' }
    post { 'Vote created' }
  end
end

class PostsAPI < Grape::API
  mount VotingAPI
end

class CommentsAPI < Grape::API
  mount VotingAPI
end

class MainAPI < Grape::API
  mount PostsAPI => '/posts'
  mount CommentsAPI => '/comments'
end

This setup would create the following endpoints:

  • GET /posts/votes
  • POST /posts/votes
  • GET /comments/votes
  • POST /comments/votes

Mount Configuration

You can configure remountable endpoints to behave differently based on where they are mounted by passing a with hash to mount. The configuration is accessible inside the mounted API via the configuration helper.

class VotingAPI < Grape::API
  namespace 'votes' do
    desc "Vote for your #{configuration[:votable]}"
    get do
      { votable_type: configuration[:votable] }
    end
  end
end

class PostsAPI < Grape::API
  mount VotingAPI, with: { votable: 'Post' }
end

class CommentsAPI < Grape::API
  mount VotingAPI, with: { votable: 'Comment' }
end

Now, GET /posts/votes will return { "votable_type": "Post" }, while GET /comments/votes will return { "votable_type": "Comment" }.