Rails GraphQL API 2026: graphql-ruby, Subscriptions und Interview-Fragen

Erstellen einer produktionsreifen GraphQL-API mit Rails 8 und graphql-ruby. Schema-Design, Mutations, Subscriptions mit ActionCable und Interview-Vorbereitung.

Rails GraphQL API 2026: graphql-ruby, Subscriptions und Interview-Fragen

GraphQL hat sich als Standardwahl für APIs etabliert, die flexible Datenabfragen benötigen, und graphql-ruby bringt diese Funktionalität mit enger ActiveRecord-Integration zu Rails. Rails 8, veröffentlicht Ende 2024, harmoniert hervorragend mit graphql-ruby 2.4, das eine verbesserte Subscription-Handhabung und bessere Lazy Execution zur N+1-Prävention einführte.

Was graphql-ruby für Rails bietet

Das graphql-ruby Gem bietet eine vollständige GraphQL-Implementierung: Schema-Definition mit einer Ruby-DSL, automatische Typinferenz aus ActiveRecord-Modellen, integrierter DataLoader für Batching und ActionCable-Integration für Echtzeit-Subscriptions.

Einrichtung von graphql-ruby in einer Rails 8 Anwendung

Der Gem-Generator erstellt die initiale Schema-Struktur und bindet den GraphQL-Endpunkt ein. Zunächst wird das Gem hinzugefügt und der Install-Generator ausgeführt, um die Basisdateien zu erstellen.

ruby
# Gemfile
gem 'graphql', '~> 2.4'
bash
# Terminal
bundle install
rails generate graphql:install

Der Generator erstellt app/graphql/ mit der Schema-Datei, Basistypen und einem Mutations-Verzeichnis. Er fügt außerdem eine Route unter /graphql hinzu und bindet optional GraphiQL für die Entwicklung ein.

ruby
# app/graphql/sharpskill_schema.rb
class SharpskillSchema < GraphQL::Schema
  mutation(Types::MutationType)
  query(Types::QueryType)
  subscription(Types::SubscriptionType)

  # Use the built-in DataLoader for N+1 prevention
  use GraphQL::Dataloader

  # Required for subscriptions
  use GraphQL::Subscriptions::ActionCableSubscriptions
end

Die Schema-Datei fungiert als Einstiegspunkt. Sie deklariert, welche Typen Queries, Mutations und Subscriptions handhaben, und registriert jegliche Middleware wie DataLoader.

Definition von Types und Resolvers für Rails-Modelle

Jedes ActiveRecord-Modell, das in der API erscheint, benötigt einen entsprechenden GraphQL-Typ. Der Typ definiert, welche Felder exponiert werden und wie sie aufgelöst werden.

ruby
# app/graphql/types/user_type.rb
module Types
  class UserType < Types::BaseObject
    field :id, ID, null: false
    field :email, String, null: false
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false

    # Association with automatic batching via DataLoader
    field :posts, [Types::PostType], null: false

    def posts
      dataloader.with(Sources::ActiveRecordCollection, Post, :user_id).load(object.id)
    end
  end
end

Der dataloader.with-Aufruf fasst mehrere posts-Abrufe in einer einzigen SQL-Abfrage zusammen. Ohne DataLoader würde eine Abfrage, die 50 Benutzer mit ihren Posts anfordert, 51 Queries ausführen. Mit DataLoader sind es nur 2.

ruby
# app/graphql/sources/active_record_collection.rb
class Sources::ActiveRecordCollection < GraphQL::Dataloader::Source
  def initialize(model, foreign_key)
    @model = model
    @foreign_key = foreign_key
  end

  def fetch(ids)
    records = @model.where(@foreign_key => ids).group_by(&@foreign_key)
    ids.map { |id| records[id] || [] }
  end
end

Dieses Muster erscheint in jedem ernsthaften graphql-ruby-Projekt. Die offizielle DataLoader-Dokumentation behandelt weitere Anwendungsfälle wie Caching und verschachtelte Sources.

Aufbau von Queries mit Argumenten und Filterung

Der QueryType definiert die Root-Felder, die Clients zur Verfügung stehen. Argumente ermöglichen Filterung und Paginierung.

ruby
# app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :users, [Types::UserType], null: false do
      argument :email_contains, String, required: false
      argument :limit, Integer, required: false, default_value: 20
    end

    def users(email_contains: nil, limit:)
      scope = User.all
      scope = scope.where('email ILIKE ?', "%#{email_contains}%") if email_contains
      scope.limit(limit)
    end

    field :user, Types::UserType, null: true do
      argument :id, ID, required: true
    end

    def user(id:)
      User.find_by(id: id)
    end
  end
end

Resolver-Methoden erhalten Keyword-Argumente, die der Felddefinition entsprechen. Die Rückgabe von nil aus einem nullable Feld ist gültig; das Auslösen eines Fehlers aus einem non-null Feld löst eine GraphQL-Fehlerantwort aus.

Bereit für deine Ruby on Rails-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Mutations: Erstellen und Aktualisieren von Datensätzen

Mutations folgen einer strengeren Konvention als Queries. Jede Mutation lebt in ihrer eigenen Klasse und gibt einen Payload-Typ zurück, der sowohl das Ergebnis als auch eventuelle Fehler enthält.

ruby
# app/graphql/mutations/create_post.rb
module Mutations
  class CreatePost < Mutations::BaseMutation
    argument :title, String, required: true
    argument :body, String, required: true

    field :post, Types::PostType, null: true
    field :errors, [String], null: false

    def resolve(title:, body:)
      post = context[:current_user].posts.build(title: title, body: body)

      if post.save
        { post: post, errors: [] }
      else
        { post: nil, errors: post.errors.full_messages }
      end
    end
  end
end

Der context[:current_user] kommt vom Controller. Die Authentifizierung erfolgt vor der GraphQL-Schicht, typischerweise mit Devise oder einer JWT-Bibliothek.

ruby
# app/controllers/graphql_controller.rb
class GraphqlController < ApplicationController
  def execute
    context = {
      current_user: current_user,
      request: request
    }
    result = SharpskillSchema.execute(
      params[:query],
      variables: params[:variables],
      context: context,
      operation_name: params[:operationName]
    )
    render json: result
  end
end

Die Zugriffskontrolle innerhalb von Resolvern prüft context[:current_user] und löst GraphQL::ExecutionError bei fehlender Autorisierung aus. Der graphql-ruby Authorization Guide beschreibt detailliert Feld- und Typ-Level-Autorisierungsmuster.

Echtzeit-Updates mit GraphQL Subscriptions und ActionCable

Subscriptions ermöglichen es Clients, Updates zu erhalten, wenn serverseitige Ereignisse auftreten. Rails ActionCable handhabt die WebSocket-Verbindung, und graphql-ruby integriert sich über GraphQL::Subscriptions::ActionCableSubscriptions.

ruby
# app/graphql/types/subscription_type.rb
module Types
  class SubscriptionType < Types::BaseObject
    field :post_created, Types::PostType, null: false do
      argument :user_id, ID, required: false
    end

    def post_created(user_id: nil)
      object # The object passed from trigger
    end
  end
end

Das Auslösen einer Subscription kann von überall in der Anwendung erfolgen, typischerweise in einem Model-Callback oder Service-Objekt.

ruby
# app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user

  after_create_commit :notify_subscribers

  private

  def notify_subscribers
    SharpskillSchema.subscriptions.trigger(
      :post_created,
      { user_id: user_id },
      self
    )
  end
end

Der ActionCable-Channel, der GraphQL-Subscriptions handhabt, wird mit dem Gem ausgeliefert.

ruby
# app/channels/graphql_channel.rb
class GraphqlChannel < ApplicationCable::Channel
  def subscribed
    @subscription_ids = []
  end

  def execute(data)
    result = SharpskillSchema.execute(
      data['query'],
      variables: data['variables'],
      context: { current_user: current_user, channel: self },
      operation_name: data['operationName']
    )

    payload = { result: result.to_h, more: result.subscription? }

    @subscription_ids << result.context[:subscription_id] if result.subscription?

    transmit(payload)
  end

  def unsubscribed
    @subscription_ids.each do |sid|
      SharpskillSchema.subscriptions.delete_subscription(sid)
    end
  end
end

Auf der Client-Seite verbinden sich Bibliotheken wie Apollo Client oder urql mit dem ActionCable-WebSocket und verwalten den Subscription-Status. Für Interview-Vorbereitung zu ActionCable-Mechanismen siehe das ActionCable & WebSockets Modul.

Testen von GraphQL-APIs mit RSpec

Das Testen einer GraphQL-API erfordert das Ausführen von Queries gegen das Schema und das Überprüfen der Antwortstruktur und Daten.

ruby
# spec/graphql/queries/users_spec.rb
RSpec.describe 'Users query' do
  let!(:user) { create(:user, email: 'test@example.com') }

  let(:query) do
    <<~GRAPHQL
      query {
        users(emailContains: "test") {
          id
          email
        }
      }
    GRAPHQL
  end

  it 'returns users matching the filter' do
    result = SharpskillSchema.execute(query)
    users = result.dig('data', 'users')

    expect(users.length).to eq(1)
    expect(users.first['email']).to eq('test@example.com')
  end
end

Mutation-Tests verifizieren sowohl Erfolgs- als auch Validierungsfehler-Pfade.

ruby
# spec/graphql/mutations/create_post_spec.rb
RSpec.describe Mutations::CreatePost do
  let(:user) { create(:user) }
  let(:context) { { current_user: user } }

  let(:mutation) do
    <<~GRAPHQL
      mutation($title: String!, $body: String!) {
        createPost(input: { title: $title, body: $body }) {
          post { id title }
          errors
        }
      }
    GRAPHQL
  end

  it 'creates a post when valid' do
    result = SharpskillSchema.execute(
      mutation,
      variables: { title: 'Hello', body: 'World' },
      context: context
    )

    data = result.dig('data', 'createPost')
    expect(data['errors']).to be_empty
    expect(data['post']['title']).to eq('Hello')
  end

  it 'returns errors when invalid' do
    result = SharpskillSchema.execute(
      mutation,
      variables: { title: '', body: 'World' },
      context: context
    )

    data = result.dig('data', 'createPost')
    expect(data['errors']).to include("Title can't be blank")
  end
end

Für weitere Rails-Testmuster siehe das Testing with RSpec Modul.

Häufige GraphQL Interview-Fragen für Rails-Entwickler

Interviewer, die GraphQL-Wissen im Rails-Kontext testen, konzentrieren sich oft auf praktische Implementierungsaspekte statt auf abstrakte Typentheorie.

Wie verhindert man N+1-Queries in graphql-ruby?

Mit DataLoader und benutzerdefinierten Sources. Eine Source-Klasse wird definiert, die Datenbankaufrufe bündelt, und dataloader.with(SourceClass, args).load(id) wird in Resolvern aufgerufen. DataLoader sammelt alle IDs, die in einer einzelnen GraphQL-Ausführung angefordert werden, und ruft sie in einer Abfrage ab.

Was ist der Unterschied zwischen einer Query und einer Mutation?

Die Semantik: Queries lesen Daten und sollten keine Seiteneffekte haben. Mutations modifizieren Daten und können Seiteneffekte haben. GraphQL garantiert parallele Ausführung für Query-Felder und sequentielle Ausführung für Mutation-Felder, sodass zwei Mutations in derselben Anfrage der Reihe nach ausgeführt werden.

Wie funktionieren Subscriptions in Rails?

Subscriptions nutzen ActionCable für WebSocket-Verbindungen. Der Client sendet eine Subscription-Query über das WebSocket. Der Server speichert die Subscription und pusht bei Aufruf von trigger das Ergebnis an alle passenden Clients. Die Subscription-ID ermöglicht es Clients, sich abzumelden.

Wann würde man GraphQL gegenüber REST für eine Rails-API wählen?

GraphQL reduziert Over-Fetching, wenn Clients unterschiedliche Datenuntermengen benötigen, was bei mobilen Apps mit variierenden Bildschirmgrößen häufig vorkommt. Es eliminiert auch die Notwendigkeit mehrerer REST-Endpunkte, wenn eine einzelne Ansicht Daten von mehreren Modellen benötigt. REST bleibt einfacher für CRUD-lastige APIs mit konsistenten Datenformen und für öffentliche APIs, bei denen Caching kritisch ist.

Wie handhabt man Authentifizierung und Autorisierung in graphql-ruby?

Die Authentifizierung erfolgt im Controller vor der GraphQL-Ausführung, typischerweise über Devise, Warden oder JWT-Verifizierung. Der authentifizierte Benutzer wird im context übergeben. Die Autorisierung erfolgt in Resolvern oder über den authorized?-Hook von graphql-ruby auf Types und Mutations, wobei GraphQL::ExecutionError bei verweigertem Zugriff ausgelöst wird.

Für weitere Ruby on Rails Interview-Vorbereitung siehe das Rails API Mode Modul, das REST-API-Designmuster behandelt, die GraphQL-Wissen ergänzen.

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Produktionsreife GraphQL-APIs mit Rails: Zusammenfassung

  • graphql-ruby mit rails generate graphql:install installieren, um Schema, Types und Routing zu erstellen
  • DataLoader Sources für alle Assoziationen verwenden, um Datenbankabfragen zu bündeln und N+1-Performance-Probleme zu verhindern
  • Mutations in separaten Klassen mit expliziten Fehlerfeldern im Rückgabetyp definieren
  • Subscriptions über ActionCable für Echtzeit-Updates integrieren, ausgelöst durch Model-Callbacks oder Service-Objekte
  • Queries und Mutations testen, indem sie direkt gegen das Schema in RSpec ausgeführt werden
  • Authentifizierung gehört in den Controller; Autorisierungsprüfungen erfolgen innerhalb der Resolver mit context[:current_user]
  • GraphQL wählen, wenn Clients flexible Datenabfragen benötigen; bei einfachen CRUD-APIs mit einheitlichen Antworten bei REST bleiben
Tägliche Challenge

Findest du den Bug in Ruby on Rails?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 19. September 2026

Teilen

Verwandte Artikel