respondo-graphql is the GraphQL sibling of respondo (JSON). It is a standalone gem: it has no dependency on respondo, its own namespace (RespondoGraphql), its own config, and its own generator. Install either one, or both.
It gives your controllers one expressive helper per situation (render_graphql_ok, render_graphql_not_found, render_graphql_unprocessable, …) and always answers in the GraphQL response shape: data, errors, extensions.
# Gemfile
gem "respondo-graphql"bundle install
rails generate respondo_graphql:installThe Railtie includes the helpers into ActionController::Base and ActionController::API for you, so no include is needed: render_graphql_ok, render_graphql_not_found, … always, and the short render_ok, render_not_found, … too when the respondo JSON gem is not installed. (If it is installed, see Using both gems.)
class GraphqlController < ApplicationController
def show
user = User.find_by(id: params[:id])
return render_graphql_not_found(message: "User not found") unless user
render_graphql_ok(data: { user: user }, message: "User fetched")
end
def create
user = User.new(user_params)
if user.save
render_graphql_created(data: { user: user }, message: "Account created")
else
render_graphql_unprocessable(message: "Validation failed", errors: user.errors)
end
end
endSuccess — render_graphql_ok(data: { user: { id: 1 } }, message: "User fetched")
{
"data": { "user": { "id": 1 } },
"extensions": { "message": "User fetched", "timestamp": "2026-10-04T10:30:00Z", "code": 200, "status": "ok" }
}Failure — data is null, and errors[0] is always a summary carrying your message. Field-level errors follow, one entry per message:
{
"errors": [
{ "message": "Validation failed",
"extensions": { "code": "UNPROCESSABLE_CONTENT", "http": { "status": 422 } } },
{ "message": "email is invalid",
"extensions": { "code": "UNPROCESSABLE_CONTENT", "http": { "status": 422 }, "field": "email" } }
],
"data": null,
"extensions": { "message": "Validation failed", "timestamp": "2026-10-04T10:30:00Z", "code": 422, "status": "unprocessable_content" }
}| What you pass | Where it ends up |
|---|---|
data: |
data — always a map or null |
message: |
extensions.message, and errors[0].message on failure |
meta: / pagination: |
extensions |
errors: { field: [...] } |
one errors[] entry per message, tagged extensions.field |
| success vs. failure | presence of the errors array |
GraphQL requires data to be a map, so an Array or scalar payload is wrapped as { "result": [...] } (rename it with config.data_key).
Same helpers, signatures and statuses as the respondo JSON gem, with a graphql_ infix: render_ok (JSON) becomes render_graphql_ok (GraphQL). Nothing is shared between the two gems, so using both in one project is safe (see Using both gems).
| Group | Helpers | Arguments |
|---|---|---|
| 2xx with payload | render_graphql_ok render_graphql_created render_graphql_accepted render_graphql_non_authoritative render_graphql_partial_content render_graphql_multi_status render_graphql_already_reported render_graphql_im_used |
data: message: meta: pagination: |
| 2xx no payload | render_graphql_no_content render_graphql_reset_content |
message: meta: pagination: |
| 4xx / 5xx | render_graphql_bad_request render_graphql_unauthorized render_graphql_forbidden render_graphql_not_found render_graphql_conflict render_graphql_unprocessable render_graphql_too_many_requests render_graphql_server_error render_graphql_service_unavailable … (40 in total) |
message: errors: meta: |
204 and 205 have no body. HTTP forbids a body on 204 No Content and 205 Reset Content, and Rails drops it, so render_graphql_no_content and render_graphql_reset_content reach the client with an empty body. Use render_graphql_ok when the client needs the message, or set config.always_ok = true (every response becomes HTTP 200, body included).
1xx (informational) and 3xx (redirect) helpers are deliberately left out: they are transport-level and have no meaning in a GraphQL response. The full list is in RespondoGraphql::ControllerHelpers::SUCCESS_HELPERS and ERROR_HELPERS.
# config/initializers/respondo_graphql.rb (written by the generator)
RespondoGraphql.configure do |config|
config.default_success_message = "Success"
config.default_error_message = "An error occurred"
config.include_request_id = false # adds request_id to extensions
config.camelize_keys = false # created_at -> createdAt
config.always_ok = false # see below
config.data_key = :result # wrapper for Array / scalar data
config.default_meta = { api_version: "v1" }
config.serializer = ->(obj) { MySerializer.new(obj).as_json }
endalways_ok — many GraphQL clients, Apollo among them, treat non-2xx responses as network failures. Set config.always_ok = true to send every response with HTTP 200 and let the errors array carry the failure. The default (false) keeps each helper's own status (404 for render_graphql_not_found, …).
respondo (JSON) and respondo-graphql can live in the same project. With respondo installed, the Railtie of each gem includes its own helpers into every controller and the render_graphql_* names never overlap with respondo's, so there is nothing to configure:
class ReportsController < ApplicationController
def show
render_ok(data: @report) # JSON — respondo
end
end
class GraphqlController < ApplicationController
def execute
render_graphql_ok(data: @result) # GraphQL — respondo-graphql
end
endrender_ok is always JSON and render_graphql_ok is always GraphQL, in whichever controller you are, because every method this gem defines (public and private) carries the render_graphql_ prefix. Each gem keeps its own configuration: Respondo.configure does not affect GraphQL responses, and RespondoGraphql.configure does not affect JSON ones.
| Your project | render_graphql_ok |
render_ok |
|---|---|---|
only respondo-graphql |
works, GraphQL | works, GraphQL (the short names are included for you) |
respondo-graphql and respondo |
works, GraphQL | respondo's JSON, so use the render_graphql_* names for GraphQL |
The gem checks whether respondo is loaded when Rails boots. There is no setting for it. The prefixed names work in both cases, so code written with them never changes meaning.
To get render_ok as GraphQL in one controller of a project that also has respondo, include the short names there on purpose:
class GraphqlController < ApplicationController
include RespondoGraphql::ShortHelpers # explicit, per controller
def execute
render_ok(data: @result) # = render_graphql_ok
end
end- It is never included automatically next to
respondo, so it cannot change whatrender_okmeans in a controller you did not choose. - If the
respondoJSON helpers are visible in that controller, a warning is printed when the class loads, so the switch is never silent. Userender_graphql_*in any controller that also needs JSON. - Include it after the controller already has the JSON helpers (the normal Rails case:
ApplicationControllerinherits them fromActionController::API). If you includeShortHelpersin a class and then includeRespondo::ControllerHelpersin that same class, the JSON helpers come first andrender_okis JSON again. - It includes
ControllerHelpersfor you, and therender_graphql_*names keep working.
Your GraphQL schema already produces valid responses for queries and mutations. RespondoGraphql is for everything your controller answers outside the schema — authentication failures, rate limits, malformed requests, REST-style endpoints that speak GraphQL-shaped JSON — so those responses look the same as the rest.
git clone https://github.com/spatelpatidar/respondo-graphql
cd respondo-graphql
bundle install
bundle exec rspecReleased under the Apache License 2.0.