
Grape is a REST-like API framework for Ruby. It's designed to run on Rack or complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily develop RESTful APIs. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.
You're reading the documentation for the stable release of Grape, which should be 4.1.0. The current stable release is 4.0.1.
Available as part of the Tidelift Subscription.
The maintainers of Grape are working with Tidelift to deliver commercial support and maintenance. Save time, reduce risk, and improve code health, while paying the maintainers of Grape. Click here for more details.
Ruby 3.3.1 or newer is required.
Grape is available as a gem, to install it run:
bundle add grape
Grape APIs are Rack applications that are created by subclassing Grape::API.
Below is a simple example showing some of the more common features of Grape in the context of recreating parts of the Twitter API.
module Twitter
class API < Grape::API
version 'v1', using: :header, vendor: 'twitter'
format :json
prefix :api
helpers do
def current_user
@current_user ||= User.authorize!(env)
end
def authenticate!
error!('401 Unauthorized', 401) unless current_user
end
end
resource :statuses do
desc 'Return a public timeline.'
get :public_timeline do
Status.limit(20)
end
desc 'Return a personal timeline.'
get :home_timeline do
authenticate!
current_user.statuses.limit(20)
end
desc 'Return a status.'
params do
requires :id, type: Integer, desc: 'Status ID.'
end
route_param :id do
get do
Status.find(params[:id])
end
end
desc 'Create a status.'
params do
requires :status, type: String, desc: 'Your status.'
end
post do
authenticate!
Status.create!({
user: current_user,
text: params[:status]
})
end
desc 'Update a status.'
params do
requires :id, type: String, desc: 'Status ID.'
requires :status, type: String, desc: 'Your status.'
end
put ':id' do
authenticate!
current_user.statuses.find(params[:id]).update({
user: current_user,
text: params[:status]
})
end
desc 'Delete a status.'
params do
requires :id, type: String, desc: 'Status ID.'
end
delete ':id' do
authenticate!
current_user.statuses.find(params[:id]).destroy
end
end
end
end
Grape's deprecator will be added to your application's deprecators automatically as :grape, so that your application's configuration can be applied to it.
By default Grape will compile the routes on the first request, but it is possible to pre-load routes using the compile! method.
Twitter::API.compile!
This can be added to your config.ru (if using rackup), application.rb (if using rails), or any file that loads your server.
The above sample creates a Rack application that can be run from a rackup config.ru file with rackup:
run Twitter::API
(With pre-loading you can use)
Twitter::API.compile!
run Twitter::API
And would respond to the following routes:
GET /api/statuses/public_timeline
GET /api/statuses/home_timeline
GET /api/statuses/:id
POST /api/statuses
PUT /api/statuses/:id
DELETE /api/statuses/:id
Grape will also automatically respond to HEAD and OPTIONS for all GET, and just OPTIONS for all other routes.
If you wish to mount Grape alongside another Rack framework such as Sinatra, you can do so easily using Rack::Cascade:
# Example config.ru
require 'sinatra'
require 'grape'
class API < Grape::API
get :hello do
{ hello: 'world' }
end
end
class Web < Sinatra::Base
get '/' do
'Hello world.'
end
end
use Rack::Session::Cookie
run Rack::Cascade.new [Web, API]
Note that order of loading apps using Rack::Cascade matters. The grape application must be last if you want to raise custom 404 errors from grape (such as error!('Not Found',404)). If the grape application is not last and returns 404 or 405 response, cascade utilizes that as a signal to try the next app. This may lead to undesirable behavior showing the wrong 404 page from the wrong app.
Place API files into app/api. Rails expects a subdirectory that matches the name of the Ruby module and a file name that matches the name of the class. In our example, the file name location and directory for Twitter::API should be app/api/twitter/api.rb.
Modify config/routes:
mount Twitter::API => '/'
Rails's default autoloader is Zeitwerk. By default, it inflects api as Api instead of API. To make our example work, you need to uncomment the lines at the bottom of config/initializers/inflections.rb, and add API as an acronym:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'API'
end
You can mount multiple API implementations inside another one. These don't have to be different versions, but may be components of the same API.
class Twitter::API < Grape::API
mount Twitter::APIv1
mount Twitter::APIv2
end
You can also mount on a path, which is similar to using prefix inside the mounted API itself.
class Twitter::API < Grape::API
mount Twitter::APIv1 => '/v1'
end
Declarations as before/after/rescue_from can be placed before or after mount. In any case they will be inherited.
class Twitter::API < Grape::API
before do
header 'X-Base-Header', 'will be defined for all APIs that are mounted below'
end
rescue_from :all do
error!({ "error" => "Internal Server Error" }, 500)
end
mount Twitter::Users
mount Twitter::Search
after do
clean_cache!
end
rescue_from ZeroDivisionError do
error!({ "error" => "Not found" }, 404)
end
end
You can mount the same endpoints in two different locations.
class Voting::API < Grape::API
namespace 'votes' do
get do
# Your logic
end
post do
# Your logic
end
end
end
class Post::API < Grape::API
mount Voting::API
end
class Comment::API < Grape::API
mount Voting::API
end
Assuming that the post and comment endpoints are mounted in /posts and /comments, you should now be able to do get /posts/votes, post /posts/votes, get /comments/votes and post /comments/votes.
You can configure remountable endpoints to change how they behave according to where they are mounted.
class Voting::API < Grape::API
namespace 'votes' do
desc "Vote for your #{configuration[:votable]}"
get do
# Your logic
end
end
end
class Post::API < Grape::API
mount Voting::API, with: { votable: 'posts' }
end
class Comment::API < Grape::API
mount Voting::API, with: { votable: 'comments' }
end
Note that if you're passing a hash as the first parameter to mount, you will need to explicitly put () around parameters:
# good
mount({ ::Some::Api => '/some/api' }, with: { condition: true })
# bad
mount ::Some::Api => '/some/api', with: { condition: true }
You can access configuration on the class (to use as dynamic attributes), inside blocks (like namespace)
If you want logic happening based on a configuration, you can use the helper given.
class ConditionalEndpoint::API < Grape::API
given configuration[:some_setting] do
get 'mount_this_endpoint_conditionally' do
configuration[:configurable_response]
end
end
end
If you want a block of logic running every time an endpoint is mounted (within which you can access the configuration Hash)
class ConditionalEndpoint::API < Grape::API
mounted do
YourLogger.info "This API was mounted at: #{Time.now}"
get configuration[:endpoint_name] do
configuration[:configurable_response]
end
end
end
More complex results can be achieved by using mounted as an expression within which the configuration is already evaluated as a Hash.
class ExpressionEndpointAPI < Grape::API
get(mounted { configuration[:route_name] || 'default_name' }) do
# some logic
end
end
class BasicAPI < Grape::API
desc 'Statuses index' do
params((configuration[:entity] || API::Entities::Status).documentation)
end
params do
requires :all, using: (configuration[:entity] || API::Entities::Status).documentation
end
get '/statuses' do
statuses = Status.all
type = current_user.admin? ? :full : :default
present statuses, with: (configuration[:entity] || API::Entities::Status), type: type
end
end
class V1 < Grape::API
version 'v1'
mount BasicAPI, with: { entity: mounted { configuration[:entity] || API::Entities::Status } }
end
class V2 < Grape::API
version 'v2'
mount BasicAPI, with: { entity: mounted { configuration[:entity] || API::Entities::V2::Status } }
end
You have the option to provide various versions of your API by establishing a separate Grape::API class for each offered version and then integrating them into a primary Grape::API class. Ensure that newer versions are mounted before older ones. The default approach to versioning directs
| 日期 | 榜单 | 排名 | 周期新增 | 总 STAR |
|---|