Grape — Ruby REST API 专用微框架
Grape 是 Ruby 世界里专门为 REST API 设计的微框架。它的目标和 Python 的 FastAPI 一样:让你用最少的代码写出规范的 RESTful API,自动处理参数验证、版本控制、错误格式化和文档生成。如果 Sinatra 是 Ruby 的 Flask,那 Grape 就是 Ruby 的 FastAPI。
Sinatra 能做 API,但它是一个通用 Web 框架,做 API 时很多事需要手动处理。Grape 从一开始就只为 API 而生:内置版本管理、自动 JSON 序列化、参数验证、Swagger 文档生成。它的 DSL 围绕 REST 资源设计,写出来的代码天然符合 REST 规范。
为什么选择 Grape?
- API First 设计:所有 API 围绕
resource分组,天然符合 REST 规范 - 版本管理内置:
versionDSL 一行搞定 v1/v2 并行 - 参数验证声明式:
requires/optional声明参数规则,自动校验并返回 422 - 错误格式统一:
rescue_from统一捕获异常,返回结构化 JSON 错误 - Swagger 文档自动生成:集成
grape-swagger后,访问/swagger.json即可得到完整的 OpenAPI 文档 - 可独立运行,也可嵌入 Sinatra/Rails:作为 Rack 应用挂载到任何 Ruby Web 框架中
安装依赖
# Gemfile
gem "grape"
gem "grape-swagger" # 自动生成 Swagger 文档
gem "grape-swagger-entity" # 实体文档支持(可选)
gem "puma" # 生产服务器
gem "sequel" # ORM
gem "sqlite3" # 数据库驱动
bundle install
API 类结构
Grape 的核心是继承 Grape::API 的类。每个 API 模块是一个独立的 Rack 应用:
# typed: true
# frozen_string_literal: true
require "grape"
class API < Grape::API
prefix "api"
format :json
content_type :json, "application/json"
rescue_from :all do |e|
error!(
{
error: e.class.name,
message: e.message
},
500
)
end
mount API::V1::Root
end
几个关键点:
prefix "api"给所有路由加/api前缀format :json让 Grape 自动处理 JSON 请求和响应rescue_from :all捕获所有未处理的异常,返回结构化错误
版本管理
Grape 的版本管理是它区别于其他 API 框架的核心特性。你可以并行运行多个 API 版本:
# lib/api/v1/root.rb
# typed: true
# frozen_string_literal: true
require_relative "users"
require_relative "posts"
module API
module V1
class Root < Grape::API
version "v1", using: :path
mount API::V1::Users
mount API::V1::Posts
get "/health" do
{
status: "ok",
version: "v1",
timestamp: Time.now.iso8601
}
end
end
end
end
# lib/api/v2/root.rb
module API
module V2
class Root < Grape::API
version "v2", using: :path
mount API::V2::Users
mount API::V2::Posts
get "health" do
{
status: "ok",
version: "v2",
timestamp: Time.now.iso8601
}
end
end
end
end
在根 API 中挂载两个版本:
class API < Grape::API
prefix "api"
format :json
mount API::V1::Root
mount API::V2::Root
end
访问路径:
GET /api/v1/healthGET /api/v2/health
Grape 也支持其他版本方式:using: :header(通过 Accept 头)、using: :param(通过 ?v=1 参数)。
参数验证
Grape 的参数验证 DSL 是最常用的功能之一。它比 Sinatra 中手动解析请求体优雅得多:
# lib/api/v1/users.rb
module API
module V1
class Users < Grape::API
resource :users do
# GET /api/v1/users?page=1&per_page=20
params do
optional :page, type: Integer, default: 1, range: 1..100
optional :per_page, type: Integer, default: 20, range: 1..100
optional :status, type: String, values: %w[active inactive banned]
end
get do
users = User.all
users = users.where(status: params[:status]) if params[:status]
{
users: users.paginate(page: params[:page], per_page: params[:per_page]),
total: users.count
}
end
# POST /api/v1/users
params do
requires :name, type: String, length: 2..50
requires :email, type: String, format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
optional :age, type: Integer, range: 0..150
optional :role, type: String, values: %w[user admin moderator], default: "user"
end
post do
user = User.create!(
name: params[:name],
email: params[:email],
age: params[:age],
role: params[:role]
)
status 201
present user, with: API::V1::Entities::User
end
# PUT /api/v1/users/:id
params do
requires :id, type: String
optional :name, type: String, length: 2..50
optional :email, type: String, format: EMAIL_REGEX
mutually_exclusive :name, :email, allow_blank: true
end
put ":id" do
user = User.find(params[:id])
user.update!(params.to_hash)
present user, with: API::V1::Entities::User
end
# DELETE /api/v1/users/:id
delete ":id" do
user = User.find(params[:id])
user.destroy
status 204
end
end
end
end
end
验证 DSL 的关键功能:
requiresvsoptional:声明必填或可选参数type:自动类型转换,字符串"123"转为 Integer123range/values:范围限制和枚举约束format:正则表达式验证,比如邮箱mutually_exclusive:参数互斥,比如不能同时传name和email更新group/as:参数分组,配合as: :update复用同一组验证逻辑
RESTful 路由
Grape 围绕 resource :name 组织路由,这和 Rails 的 resources 思维一致:
resource :posts do
# GET /api/v1/posts
get do
present Post.all, with: API::V1::Entities::Post
end
# GET /api/v1/posts/:id
route_param :id do
get do
present Post.find(params[:id]), with: API::V1::Entities::Post
end
# GET /api/v1/posts/:id/comments
resource :comments do
get do
present Comment.where(post_id: params[:id]),
with: API::V1::Entities::Comment
end
params do
requires :body, type: String, length: 1..1000
end
post do
comment = Comment.create!(
post_id: params[:id],
body: params[:body]
)
status 201
present comment, with: API::V1::Entities::Comment
end
end
end
end
嵌套资源通过 route_param 和嵌套 resource 实现。参数通过 params[:id] 自动从 URL 路径中提取。
响应格式化
Grape 支持多种格式,并可通过 HTTP Accept 头自动协商:
class API < Grape::API
format :json # 默认 JSON
content_type :xml, "application/xml"
content_type :yaml, "text/yaml"
# 也可以针对特定端点指定格式
get "/data", produces: [:json, :xml] do
{ name: "hello", items: [1, 2, 3] }
end
end
客户端控制响应格式:
# 默认 JSON
$ curl http://localhost:9292/api/data
# 要求 XML
$ curl -H "Accept: application/xml" http://localhost:9292/api/data
使用 Entity 构建结构化响应:
# lib/api/v1/entities/user.rb
module API
module V1
module Entities
class User < Grape::Entity
expose :id, :name, :email, :role
expose :created_at, format: :iso8601
expose :avatar_url do |user, options|
"https://cdn.example.com/avatars/#{user.id}.jpg"
end
# 嵌套关联
expose :latest_post, using: API::V1::Entities::Post, if: -> (*) {
object.respond_to?(:latest_post) && object.latest_post
}
end
end
end
end
Entity 的作用是把数据库模型转为 API 需要的 JSON 结构,避免直接暴露数据库字段。在 action 中通过 present object, with: Entity 自动序列化。
错误处理
Grape 的错误处理通过 rescue_from 实现,可以按异常类型精确捕获:
class API < Grape::API
prefix "api"
format :json
# 捕获特定异常
rescue_from Grape::Exceptions::ValidationErrors do |e|
error!({
error: "validation_error",
messages: e.errors
}, 422)
end
rescue_from Sequel::NotFound do
error!({
error: "not_found",
message: "资源不存在"
}, 404)
end
rescue_from Sequel::UniqueConstraintViolation do |e|
error!({
error: "duplicate",
message: "记录已存在"
}, 409)
end
# 兜底捕获所有异常
rescue_from :all do |e|
error!({
error: "internal_error",
message: "服务器内部错误"
}, 500)
end
get "/users/:id" do
user = User.find(params[:id]) # Sequel::NotFound 会被自动捕获
present user, with: API::V1::Entities::User
end
end
错误响应的格式统一为:
{
"error": "validation_error",
"messages": {
"email": ["is invalid"]
}
}
自定义错误处理函数:
helpers do
def authenticate!
token = request.env["HTTP_AUTHORIZATION"]&.split(" ")&.last
unless token
error!({ error: "unauthorized", message: "需要认证" }, 401)
end
@current_user = User.find_by(auth_token: token)
error!({ error: "unauthorized", message: "无效的认证令牌" }, 401) unless @current_user
end
def require_admin!
error!({ error: "forbidden", message: "需要管理员权限" }, 403) unless @current_user&.admin?
end
end
# 在资源中使用 before 过滤器
resource :admin do
before { authenticate!; require_admin! }
get "/stats" do
{
users: User.count,
posts: Post.count,
comments: Comment.count
}
end
end
Swagger / OpenAPI 文档
grape-swagger 为你的 API 自动生成 Swagger UI,这是 Grape 最强大的功能之一:
require "grape-swagger"
class API < Grape::API
prefix "api"
format :json
# ... 你的 API 定义
add_swagger_documentation(
mount_path: "swagger",
api_version: "v1",
hide_documentation_path: true,
info: {
title: "My API",
version: "v1",
description: "用户和文章管理 API"
},
security_definitions: {
api_key: {
type: "apiKey",
name: "Authorization",
in: "header"
}
}
)
end
访问 http://localhost:9292/api/swagger 即可看到交互式文档。
更精细的端点文档:
desc "获取用户列表", {
detail: "支持分页和状态筛选",
success: API::V1::Entities::User,
is_array: true
}
params do
optional :page, type: Integer, default: 1, desc: "页码"
optional :per_page, type: Integer, default: 20, desc: "每页数量"
optional :status, type: String, values: %w[active inactive], desc: "用户状态"
end
get do
present User.all, with: API::V1::Entities::User
end
desc "创建新用户", {
detail: "需要 name 和 email,age 可选",
success: API::V1::Entities::User,
failure: [
[422, "验证失败", API::V1::Entities::Error],
[409, "重复记录", API::V1::Entities::Error]
]
}
params do
requires :name, type: String, length: 2..50, desc: "用户名"
requires :email, type: String, desc: "邮箱地址"
optional :age, type: Integer, range: 0..150, desc: "年龄"
end
post do
# ...
end
生成的 Swagger 文档包含端点描述、参数说明、返回类型、错误码,可以直接用于前端联调和客户端代码生成。
与 Rack 集成
Grape 应用是完整的 Rack 应用,可以独立运行,也可以嵌入其他框架:
独立运行:
# config.ru
require_relative "./lib/api"
run API
$ bundle exec rackup config.ru
# Puma 启动在 http://localhost:9292
嵌入 Rails:
# config/routes.rb
Rails.application.routes.draw do
mount API => "/api"
end
嵌入 Sinatra:
require "sinatra/base"
require_relative "./lib/api"
class MainApp < Sinatra::Base
mount API => "/api"
get "/" do
"Hello from Sinatra!"
end
end
这种灵活性让 Grape 可以作为 Rails 应用中的 API 模块存在,同时保留 REST API 专用 DSL 的优势。
完整示例结构
my_api/
├── config.ru # Rack 入口
├── Gemfile
├── lib/
│ └── api.rb # 根 API 类,挂载所有版本
│ ├── api/
│ │ ├── v1/
│ │ │ ├── root.rb # V1 入口
│ │ │ ├── users.rb # 用户资源
│ │ │ ├── posts.rb # 文章资源
│ │ │ └── entities/
│ │ │ ├── user.rb # 用户响应格式
│ │ │ └── post.rb # 文章响应格式
│ │ └── v2/
│ │ ├── root.rb # V2 入口
│ │ └── users.rb # V2 用户 API(可能有新字段)
│ ├── models/
│ │ ├── user.rb
│ │ └── post.rb
│ └── db.rb # 数据库连接
本章要点
- Grape 是 Ruby 专用的 REST API 微框架,围绕
resource组织路由 versionDSL 支持 v1/v2 多版本并行,通过路径、Header 或参数切换requires/optional声明参数验证规则,自动返回结构化错误- Entity 层负责将数据库对象序列化为 JSON,控制 API 响应格式
rescue_from按异常类型统一处理错误,避免重复的begin/rescueadd_swagger_documentation自动生成 OpenAPI/Swagger 文档- 可以作为独立 Rack 应用运行,也可以嵌入 Rails/Sinatra
- 适合需要规范 REST API、版本管理和自动文档的后端服务
继续学习
- Grape 官方文档: ruby-grape.github.io/grape
- grape-swagger 文档: ruby-grape.github.io/grape-swagger
- Grape::Entity: ruby-grape.github.io/entity
- Sinatra 基础: Sinatra 微服务框架
- 现代 Web 框架: Hanami — 干净架构
💡 提示:Grape 的哲学是"API 应该像 API 一样被设计"。它不是为了替代 Sinatra 或 Rails 而生,而是补足了它们在 API 场景下的不足:版本管理、参数验证、自动文档。如果你的项目需要对外提供 REST API,Grape 是比 Sinatra 更专业的选择。