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.
-
File Location: Place your API files in
app/api/
. For a classTwitter::API
, the file should be located atapp/api/twitter/api.rb
. -
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', '*')]
-
Zeitwerk Inflection (Rails 6+): Rails's default autoloader, Zeitwerk, inflects
api
asApi
. To correctly loadAPI
, add an acronym inflection inconfig/initializers/inflections.rb
.# config/initializers/inflections.rb ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.acronym 'API' end
-
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" }
.