Rails GraphQL API w 2026: graphql-ruby, subskrypcje i pytania rekrutacyjne

Kompletny przewodnik po budowie produkcyjnego GraphQL API z Rails 8 i graphql-ruby, obejmujący projektowanie schematu, mutacje, subskrypcje oraz przygotowanie do rozmowy kwalifikacyjnej.

Rails GraphQL API w 2026: graphql-ruby, subskrypcje i pytania rekrutacyjne

GraphQL stał się domyślnym wyborem dla API wymagających elastycznego pobierania danych, a graphql-ruby wprowadza tę funkcjonalność do Rails z pełną integracją z ActiveRecord. Rails 8, wydany pod koniec 2024 roku, doskonale współpracuje z graphql-ruby 2.4, który wprowadził ulepszoną obsługę subskrypcji oraz lepsze leniwe wykonywanie zapobiegające problemom N+1.

Co graphql-ruby wnosi do Rails

Gem graphql-ruby zapewnia kompletną implementację GraphQL: definicję schematu z Ruby DSL, automatyczne wnioskowanie typów z modeli ActiveRecord, wbudowany DataLoader do grupowania zapytań oraz integrację z ActionCable dla subskrypcji czasu rzeczywistego.

Konfiguracja graphql-ruby w aplikacji Rails 8

Generator gemu tworzy początkową strukturę schematu i montuje endpoint GraphQL. Rozpoczynamy od dodania gemu i uruchomienia generatora instalacyjnego, który tworzy bazowe pliki.

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

Generator tworzy katalog app/graphql/ z plikiem schematu, podstawowymi typami oraz katalogiem mutacji. Dodaje również ścieżkę routingu pod /graphql i opcjonalnie montuje GraphiQL dla środowiska deweloperskiego.

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

  # Użycie wbudowanego DataLoadera do zapobiegania N+1
  use GraphQL::Dataloader

  # Wymagane dla subskrypcji
  use GraphQL::Subscriptions::ActionCableSubscriptions
end

Plik schematu stanowi punkt wejściowy aplikacji. Deklaruje, które typy obsługują zapytania, mutacje i subskrypcje oraz rejestruje middleware, taki jak DataLoader.

Definiowanie typów i resolverów dla modeli Rails

Każdy model ActiveRecord eksponowany w API wymaga odpowiadającego mu typu GraphQL. Typ definiuje, które pola są udostępniane i jak są rozwiązywane.

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

    # Asocjacja z automatycznym grupowaniem przez DataLoader
    field :posts, [Types::PostType], null: false

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

Wywołanie dataloader.with grupuje wiele pobrań postów w jedno zapytanie SQL. Bez DataLoadera zapytanie żądające 50 użytkowników z ich postami wykonałoby 51 zapytań. Z DataLoaderem wykonuje tylko 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

Ten wzorzec pojawia się w każdym poważnym projekcie graphql-ruby. Oficjalna dokumentacja DataLoadera opisuje dodatkowe przypadki użycia, takie jak cache'owanie i zagnieżdżone źródła.

Budowanie zapytań z argumentami i filtrowaniem

QueryType definiuje pola root dostępne dla klientów. Argumenty umożliwiają filtrowanie i paginację.

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

Metody resolverów otrzymują argumenty nazwane odpowiadające definicji pola. Zwrócenie nil z pola nullable jest prawidłowe; rzucenie błędu z pola non-null wywołuje odpowiedź błędu GraphQL.

Gotowy na rozmowy o Ruby on Rails?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Mutacje: tworzenie i aktualizacja rekordów

Mutacje podlegają ściślejszym konwencjom niż zapytania. Każda mutacja znajduje się we własnej klasie i zwraca typ payload zawierający zarówno wynik, jak i ewentualne błędy.

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

context[:current_user] pochodzi z kontrolera. Uwierzytelnianie następuje przed warstwą GraphQL, zazwyczaj przy użyciu Devise lub biblioteki JWT.

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

Kontrola dostępu wewnątrz resolverów sprawdza context[:current_user] i rzuca GraphQL::ExecutionError w przypadku braku autoryzacji. Przewodnik autoryzacji graphql-ruby szczegółowo opisuje wzorce autoryzacji na poziomie pól i typów.

Aktualizacje w czasie rzeczywistym z subskrypcjami GraphQL i ActionCable

Subskrypcje pozwalają klientom otrzymywać aktualizacje, gdy występują zdarzenia po stronie serwera. Rails ActionCable obsługuje połączenie WebSocket, a graphql-ruby integruje się poprzez 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 # Obiekt przekazany z triggera
    end
  end
end

Wyzwalanie subskrypcji może nastąpić z dowolnego miejsca w aplikacji, zazwyczaj w callbacku modelu lub obiekcie serwisowym.

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

Kanał ActionCable obsługujący subskrypcje GraphQL jest dostarczany wraz z gemem.

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

Po stronie klienta biblioteki takie jak Apollo Client lub urql łączą się z WebSocketem ActionCable i zarządzają stanem subskrypcji. Przygotowując się do rozmowy kwalifikacyjnej dotyczącej mechanizmów ActionCable, warto zapoznać się z modułem ActionCable & WebSockets.

Testowanie GraphQL API z RSpec

Testowanie GraphQL API wymaga wykonywania zapytań względem schematu i asercji struktury oraz danych odpowiedzi.

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

Testy mutacji weryfikują zarówno ścieżkę sukcesu, jak i błędów walidacji.

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

Więcej o wzorcach testowania w Rails można znaleźć w module Testowanie z RSpec.

Popularne pytania rekrutacyjne GraphQL dla programistów Rails

Rekruterzy sprawdzający wiedzę o GraphQL w kontekście Rails często koncentrują się na praktycznych aspektach implementacji, a nie na abstrakcyjnej teorii typów.

Jak zapobiegać zapytaniom N+1 w graphql-ruby?

Należy używać DataLoadera z niestandardowymi źródłami. Definiuje się klasę źródła, która grupuje wywołania bazy danych, i wywołuje dataloader.with(SourceClass, args).load(id) w resolverach. DataLoader zbiera wszystkie ID żądane w jednym wykonaniu GraphQL i pobiera je w jednym zapytaniu.

Jaka jest różnica między zapytaniem a mutacją?

Semantyka: zapytania odczytują dane i nie powinny mieć efektów ubocznych. Mutacje modyfikują dane i mogą mieć efekty uboczne. GraphQL gwarantuje równoległe wykonanie pól zapytań i sekwencyjne wykonanie pól mutacji, więc dwie mutacje w tym samym żądaniu wykonują się po kolei.

Jak działają subskrypcje w Rails?

Subskrypcje używają ActionCable do połączeń WebSocket. Klient wysyła zapytanie subskrypcji przez WebSocket. Serwer przechowuje subskrypcję i gdy wywoływany jest trigger, wysyła wynik do wszystkich pasujących klientów. Identyfikator subskrypcji pozwala klientom na wypisanie się.

Kiedy wybrać GraphQL zamiast REST dla Rails API?

GraphQL redukuje nadmierne pobieranie danych, gdy klienci potrzebują różnych podzbiorów danych, co jest powszechne w aplikacjach mobilnych o różnych rozmiarach ekranu. Eliminuje również potrzebę wielu endpointów REST, gdy pojedynczy widok wymaga danych z kilku modeli. REST pozostaje prostszy dla API z dużą ilością operacji CRUD o spójnych kształtach danych oraz dla publicznych API, gdzie cache'owanie jest krytyczne.

Jak obsługiwać uwierzytelnianie i autoryzację w graphql-ruby?

Uwierzytelnianie następuje w kontrolerze przed wykonaniem GraphQL, zazwyczaj poprzez Devise, Warden lub weryfikację JWT. Uwierzytelniony użytkownik jest przekazywany w context. Autoryzacja następuje w resolverach lub przez hook authorized? graphql-ruby na typach i mutacjach, rzucając GraphQL::ExecutionError gdy dostęp jest zabroniony.

Więcej o przygotowaniu do rozmowy kwalifikacyjnej Ruby on Rails można znaleźć w module Rails API Mode, który opisuje wzorce projektowania REST API uzupełniające wiedzę o GraphQL.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Budowanie produkcyjnych GraphQL API z Rails: kluczowe wnioski

  • Instalacja graphql-ruby z rails generate graphql:install tworzy schemat, typy i routing
  • Używanie źródeł DataLoadera dla wszystkich asocjacji pozwala grupować zapytania do bazy danych i zapobiegać problemom wydajnościowym N+1
  • Mutacje definiuje się w oddzielnych klasach z jawnym polem błędów w typie zwracanym
  • Integracja subskrypcji przez ActionCable zapewnia aktualizacje w czasie rzeczywistym, wyzwalane z callbacków modelu lub obiektów serwisowych
  • Testowanie zapytań i mutacji odbywa się przez wykonywanie ich bezpośrednio względem schematu w RSpec
  • Uwierzytelnianie należy do kontrolera; sprawdzanie autoryzacji następuje wewnątrz resolverów z użyciem context[:current_user]
  • GraphQL jest wybierany, gdy klienci potrzebują elastycznego pobierania danych; REST pozostaje lepszy dla prostych API CRUD z jednolitymi odpowiedziami
Wyzwanie dnia

Znajdziesz błąd w Ruby on Rails?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 19 września 2026

Udostępnij

Powiązane artykuły