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.

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.
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.
# Gemfile
gem 'graphql', '~> 2.4'# Terminal
bundle install
rails generate graphql:installGenerator 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.
# 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
endPlik 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.
# 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
endWywoł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.
# 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
endTen 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ę.
# 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
endMetody 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.
# 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
endcontext[:current_user] pochodzi z kontrolera. Uwierzytelnianie następuje przed warstwą GraphQL, zazwyczaj przy użyciu Devise lub biblioteki JWT.
# 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
endKontrola 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.
# 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
endWyzwalanie subskrypcji może nastąpić z dowolnego miejsca w aplikacji, zazwyczaj w callbacku modelu lub obiekcie serwisowym.
# 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
endKanał ActionCable obsługujący subskrypcje GraphQL jest dostarczany wraz z gemem.
# 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
endPo 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.
# 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
endTesty mutacji weryfikują zarówno ścieżkę sukcesu, jak i błędów walidacji.
# 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
endWię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:installtworzy 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
Znajdziesz błąd w Ruby on Rails?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZał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

Zadania w Tle w Rails 2026: Sidekiq vs Good Job — Porównanie i Pytania Rekrutacyjne
Kompleksowe porównanie Sidekiq, Good Job i Solid Queue w Rails 8. Analiza wydajności, funkcjonalności oraz najczęstsze pytania rekrutacyjne dotyczące background jobs w Ruby on Rails.

Rails Active Storage w 2026: Przesyłanie Plików, Integracja z S3 i Pytania Rekrutacyjne
Kompletny przewodnik po Rails Active Storage w 2026 roku. Konfiguracja S3, bezpośrednie przesyłanie, przetwarzanie obrazów i najczęściej zadawane pytania na rozmowach kwalifikacyjnych.

Rails Stimulus i Importmaps w 2026: Nowoczesny JavaScript bez narzędzi do budowania
Kompleksowy przewodnik po Stimulus i Importmaps w Rails 8 - jak tworzyć interaktywne aplikacje webowe bez webpack, esbuild ani żadnych bundlerów.