Rails GraphQL API di 2026: graphql-ruby, Subscriptions, dan Pertanyaan Interview

Bangun GraphQL API siap produksi dengan Rails 8 dan graphql-ruby. Desain schema, mutations, subscriptions dengan ActionCable, dan persiapan interview.

Rails GraphQL API di 2026: graphql-ruby, Subscriptions, dan Pertanyaan Interview

GraphQL telah menjadi pilihan utama untuk API yang membutuhkan pengambilan data fleksibel, dan graphql-ruby menghadirkan kemampuan ini ke Rails dengan integrasi ActiveRecord yang erat. Rails 8, yang dirilis akhir 2024, berpasangan dengan baik bersama graphql-ruby 2.4, yang memperkenalkan penanganan subscription yang lebih baik dan lazy execution yang ditingkatkan untuk pencegahan N+1.

Apa yang dibawa graphql-ruby ke Rails

Gem graphql-ruby menyediakan implementasi GraphQL lengkap: definisi schema dengan DSL Ruby, inferensi tipe otomatis dari model ActiveRecord, DataLoader bawaan untuk batching, dan integrasi ActionCable untuk subscriptions real-time.

Menyiapkan graphql-ruby di Aplikasi Rails 8

Generator gem membuat struktur schema awal dan memasang endpoint GraphQL. Mulai dengan gem dan jalankan generator install untuk membuat scaffold file dasar.

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

Generator membuat app/graphql/ dengan file schema, base types, dan direktori mutations. Ini juga menambahkan route di /graphql dan secara opsional memasang GraphiQL untuk pengembangan.

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

  # Gunakan DataLoader bawaan untuk pencegahan N+1
  use GraphQL::Dataloader

  # Diperlukan untuk subscriptions
  use GraphQL::Subscriptions::ActionCableSubscriptions
end

File schema bertindak sebagai titik masuk. Ini mendeklarasikan tipe mana yang menangani queries, mutations, dan subscriptions, serta mendaftarkan middleware seperti DataLoader.

Mendefinisikan Types dan Resolvers untuk Model Rails

Setiap model ActiveRecord yang muncul di API membutuhkan tipe GraphQL yang sesuai. Tipe mendefinisikan field mana yang diekspos dan bagaimana mereka di-resolve.

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

    # Asosiasi dengan batching otomatis via DataLoader
    field :posts, [Types::PostType], null: false

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

Panggilan dataloader.with mengelompokkan beberapa pengambilan posts ke dalam satu query SQL. Tanpa DataLoader, query yang meminta 50 user beserta posts mereka akan mengeksekusi 51 query. Dengan DataLoader, hanya 2 query yang dijalankan.

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

Pola ini muncul di setiap proyek graphql-ruby yang serius. Dokumentasi resmi DataLoader mencakup kasus penggunaan tambahan seperti caching dan nested sources.

Membangun Queries dengan Arguments dan Filtering

QueryType mendefinisikan field root yang tersedia untuk klien. Arguments memungkinkan filtering dan pagination.

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

Metode resolver menerima keyword arguments yang cocok dengan definisi field. Mengembalikan nil dari field nullable adalah valid; melempar error dari field non-null memicu respons error GraphQL.

Siap menguasai wawancara Ruby on Rails Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Mutations: Membuat dan Memperbarui Record

Mutations mengikuti konvensi yang lebih ketat daripada queries. Setiap mutation berada di kelasnya sendiri dan mengembalikan tipe payload yang menyertakan hasil dan error apa pun.

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] berasal dari controller. Autentikasi terjadi sebelum layer GraphQL, biasanya menggunakan Devise atau library 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

Kontrol akses di dalam resolvers memeriksa context[:current_user] dan melempar GraphQL::ExecutionError ketika tidak diizinkan. Panduan otorisasi graphql-ruby menjelaskan pola otorisasi tingkat field dan tingkat tipe.

Update Real-Time dengan GraphQL Subscriptions dan ActionCable

Subscriptions memungkinkan klien menerima pembaruan ketika event sisi server terjadi. Rails ActionCable menangani koneksi WebSocket, dan graphql-ruby berintegrasi melalui 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 # Object yang diteruskan dari trigger
    end
  end
end

Memicu subscription terjadi dari mana saja di aplikasi, biasanya dalam model callback atau service object.

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

Channel ActionCable yang menangani subscriptions GraphQL disertakan bersama gem.

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

Di sisi klien, library seperti Apollo Client atau urql terhubung ke WebSocket ActionCable dan mengelola state subscription. Untuk persiapan interview tentang mekanika ActionCable, lihat modul ActionCable & WebSockets.

Testing GraphQL API dengan RSpec

Menguji GraphQL API memerlukan eksekusi queries terhadap schema dan assertion pada struktur dan data respons.

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

Tes mutation memverifikasi jalur sukses dan error validasi.

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

Untuk lebih lanjut tentang pola testing Rails, lihat modul Testing dengan RSpec.

Pertanyaan Interview GraphQL Umum untuk Developer Rails

Pewawancara yang menguji pengetahuan GraphQL dalam konteks Rails sering fokus pada masalah implementasi praktis daripada teori tipe abstrak.

Bagaimana cara mencegah query N+1 di graphql-ruby?

Gunakan DataLoader dengan custom sources. Definisikan kelas source yang mengelompokkan panggilan database, dan panggil dataloader.with(SourceClass, args).load(id) di resolvers. DataLoader mengumpulkan semua ID yang diminta dalam satu eksekusi GraphQL dan mengambilnya dalam satu query.

Apa perbedaan antara query dan mutation?

Semantik: queries membaca data dan tidak boleh memiliki efek samping. Mutations memodifikasi data dan mungkin memiliki efek samping. GraphQL menjamin eksekusi paralel untuk field query dan eksekusi sekuensial untuk field mutation, sehingga dua mutation dalam request yang sama dieksekusi berurutan.

Bagaimana subscriptions bekerja di Rails?

Subscriptions menggunakan ActionCable untuk koneksi WebSocket. Klien mengirim query subscription melalui WebSocket. Server menyimpan subscription dan, ketika trigger dipanggil, mendorong hasil ke semua klien yang cocok. ID subscription memungkinkan klien untuk unsubscribe.

Kapan memilih GraphQL daripada REST untuk Rails API?

GraphQL mengurangi over-fetching ketika klien membutuhkan subset data yang berbeda, yang umum di aplikasi mobile dengan berbagai ukuran layar. Ini juga menghilangkan kebutuhan untuk multiple REST endpoints ketika satu view memerlukan data dari beberapa model. REST tetap lebih sederhana untuk API yang berat CRUD dengan bentuk data konsisten dan untuk API publik di mana caching sangat penting.

Bagaimana menangani autentikasi dan otorisasi di graphql-ruby?

Autentikasi terjadi di controller sebelum eksekusi GraphQL, biasanya melalui Devise, Warden, atau verifikasi JWT. User yang terautentikasi diteruskan dalam context. Otorisasi terjadi di resolvers atau melalui hook authorized? graphql-ruby pada types dan mutations, melempar GraphQL::ExecutionError ketika akses ditolak.

Untuk lebih banyak persiapan interview Ruby on Rails, lihat modul Rails API Mode, yang mencakup pola desain REST API yang melengkapi pengetahuan GraphQL.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Membangun GraphQL API Produksi dengan Rails: Poin-Poin Penting

  • Instal graphql-ruby dengan rails generate graphql:install untuk membuat scaffold schema, types, dan routing
  • Gunakan DataLoader sources untuk semua asosiasi untuk mengelompokkan query database dan mencegah masalah performa N+1
  • Definisikan mutations di kelas terpisah dengan field error eksplisit di tipe return
  • Integrasikan subscriptions via ActionCable untuk update real-time, memicu dari model callbacks atau service objects
  • Uji queries dan mutations dengan mengeksekusinya langsung terhadap schema di RSpec
  • Autentikasi berada di controller; pemeriksaan otorisasi terjadi di dalam resolvers menggunakan context[:current_user]
  • Pilih GraphQL ketika klien membutuhkan pengambilan data fleksibel; tetap gunakan REST untuk API CRUD sederhana dengan respons seragam
Tantangan harian

Bisakah kamu menemukan bug di Ruby on Rails?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 19 September 2026

Bagikan

Artikel terkait