Rails GraphQL API ในปี 2026: graphql-ruby, Subscriptions และคำถามสัมภาษณ์
สร้าง GraphQL API พร้อมใช้งานจริงด้วย Rails 8 และ graphql-ruby การออกแบบ schema, mutations, subscriptions กับ ActionCable และการเตรียมตัวสัมภาษณ์

GraphQL กลายเป็นตัวเลือกหลักสำหรับ API ที่ต้องการการดึงข้อมูลแบบยืดหยุ่น และ graphql-ruby นำความสามารถนี้มาสู่ Rails พร้อมการผสานรวม ActiveRecord อย่างแน่นหนา Rails 8 ที่เปิดตัวปลายปี 2024 ทำงานได้ดีกับ graphql-ruby 2.4 ซึ่งแนะนำการจัดการ subscription ที่ดีขึ้นและ lazy execution ที่ปรับปรุงเพื่อป้องกัน N+1
Gem graphql-ruby มอบการ implement GraphQL อย่างสมบูรณ์: การกำหนด schema ด้วย Ruby DSL, การอนุมาน type อัตโนมัติจาก ActiveRecord models, DataLoader ในตัวสำหรับ batching และการผสานรวม ActionCable สำหรับ subscriptions แบบ real-time
การตั้งค่า graphql-ruby ในแอปพลิเคชัน Rails 8
Generator ของ gem สร้างโครงสร้าง schema เริ่มต้นและ mount endpoint GraphQL เริ่มต้นด้วย gem และรัน install generator เพื่อสร้าง scaffold ไฟล์พื้นฐาน
# Gemfile
gem 'graphql', '~> 2.4'# Terminal
bundle install
rails generate graphql:installGenerator สร้างโฟลเดอร์ app/graphql/ พร้อมไฟล์ schema, base types และ directory mutations นอกจากนี้ยังเพิ่ม route ที่ /graphql และสามารถ mount GraphiQL สำหรับการพัฒนาได้
# app/graphql/sharpskill_schema.rb
class SharpskillSchema < GraphQL::Schema
mutation(Types::MutationType)
query(Types::QueryType)
subscription(Types::SubscriptionType)
# ใช้ DataLoader ในตัวเพื่อป้องกัน N+1
use GraphQL::Dataloader
# จำเป็นสำหรับ subscriptions
use GraphQL::Subscriptions::ActionCableSubscriptions
endไฟล์ schema ทำหน้าที่เป็นจุดเข้าหลัก มันประกาศว่า types ใดจัดการ queries, mutations และ subscriptions รวมถึงลงทะเบียน middleware ใดๆ เช่น DataLoader
การกำหนด Types และ Resolvers สำหรับ Rails Models
แต่ละ ActiveRecord model ที่ปรากฏใน API ต้องการ GraphQL type ที่สอดคล้องกัน Type กำหนดว่า fields ใดที่ถูก expose และวิธีที่พวกมันถูก resolve
# 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 พร้อม batching อัตโนมัติผ่าน DataLoader
field :posts, [Types::PostType], null: false
def posts
dataloader.with(Sources::ActiveRecordCollection, Post, :user_id).load(object.id)
end
end
endการเรียก dataloader.with รวมการดึง posts หลายครั้งเป็น SQL query เดียว หากไม่มี DataLoader query ที่ร้องขอ 50 users พร้อม posts จะรัน 51 queries ด้วย DataLoader จะรันเพียง 2 queries
# 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รูปแบบนี้ปรากฏในทุกโปรเจค graphql-ruby ที่จริงจัง เอกสารทางการของ DataLoader ครอบคลุม use cases เพิ่มเติม เช่น caching และ nested sources
การสร้าง Queries ด้วย Arguments และ Filtering
QueryType กำหนด root fields ที่พร้อมใช้งานสำหรับ clients Arguments อนุญาตให้ทำ filtering และ pagination
# 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
endResolver methods รับ keyword arguments ที่ตรงกับ field definition การ return nil จาก nullable field เป็นสิ่งที่ถูกต้อง การ throw error จาก non-null field จะทำให้เกิด GraphQL error response
พร้อมที่จะพิชิตการสัมภาษณ์ Ruby on Rails แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
Mutations: การสร้างและอัปเดต Records
Mutations ปฏิบัติตามข้อตกลงที่เข้มงวดกว่า queries แต่ละ mutation อยู่ใน class แยกและ return payload type ที่รวมทั้งผลลัพธ์และ errors
# 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] มาจาก controller การ authenticate เกิดขึ้นก่อน layer GraphQL โดยทั่วไปใช้ Devise หรือ JWT library
# 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การควบคุมการเข้าถึงภายใน resolvers ตรวจสอบ context[:current_user] และ throw GraphQL::ExecutionError เมื่อไม่ได้รับอนุญาต คู่มือ authorization ของ graphql-ruby อธิบายรูปแบบ authorization ระดับ field และระดับ type
การอัปเดตแบบ Real-Time ด้วย GraphQL Subscriptions และ ActionCable
Subscriptions อนุญาตให้ clients รับการอัปเดตเมื่อเกิด events ฝั่ง server Rails ActionCable จัดการ WebSocket connection และ graphql-ruby ผสานรวมผ่าน 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 # Object ที่ส่งผ่านจาก trigger
end
end
endการ trigger subscription เกิดขึ้นจากที่ใดก็ได้ในแอปพลิเคชัน โดยทั่วไปใน model callback หรือ service object
# 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
endActionCable channel ที่จัดการ GraphQL subscriptions มาพร้อมกับ gem
# 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ฝั่ง client libraries เช่น Apollo Client หรือ urql เชื่อมต่อกับ ActionCable WebSocket และจัดการ subscription state สำหรับการเตรียมสัมภาษณ์เกี่ยวกับกลไก ActionCable ดู module ActionCable & WebSockets
การทดสอบ GraphQL API ด้วย RSpec
การทดสอบ GraphQL API ต้องรัน queries กับ schema และ assert โครงสร้างและข้อมูลของ response
# 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 ตรวจสอบทั้งเส้นทางสำเร็จและ validation errors
# 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สำหรับรูปแบบการทดสอบ Rails เพิ่มเติม ดู module Testing ด้วย RSpec
คำถามสัมภาษณ์ GraphQL ทั่วไปสำหรับ Rails Developers
ผู้สัมภาษณ์ที่ทดสอบความรู้ GraphQL ในบริบท Rails มักเน้นที่ปัญหาการ implement จริงมากกว่าทฤษฎี type ที่เป็นนามธรรม
จะป้องกัน N+1 queries ใน graphql-ruby ได้อย่างไร?
ใช้ DataLoader กับ custom sources กำหนด source class ที่รวม database calls และเรียก dataloader.with(SourceClass, args).load(id) ใน resolvers DataLoader รวบรวม IDs ทั้งหมดที่ร้องขอในการรัน GraphQL ครั้งเดียวและดึงข้อมูลใน query เดียว
อะไรคือความแตกต่างระหว่าง query และ mutation?
ความหมาย: queries อ่านข้อมูลและไม่ควรมี side effects mutations แก้ไขข้อมูลและอาจมี side effects GraphQL รับประกันการรันแบบขนานสำหรับ query fields และการรันแบบลำดับสำหรับ mutation fields ดังนั้นสอง mutations ใน request เดียวกันจะรันตามลำดับ
Subscriptions ทำงานอย่างไรใน Rails?
Subscriptions ใช้ ActionCable สำหรับ WebSocket connections client ส่ง subscription query ผ่าน WebSocket server เก็บ subscription และเมื่อ trigger ถูกเรียก จะ push ผลลัพธ์ไปยัง clients ที่ตรงกันทั้งหมด subscription ID อนุญาตให้ clients unsubscribe ได้
เมื่อใดควรเลือก GraphQL แทน REST สำหรับ Rails API?
GraphQL ลด over-fetching เมื่อ clients ต้องการ subsets ของข้อมูลที่แตกต่างกัน ซึ่งพบบ่อยในแอป mobile ที่มีขนาดหน้าจอต่างกัน มันยังไม่ต้องการ REST endpoints หลายตัวเมื่อ view เดียวต้องการข้อมูลจากหลาย models REST ยังคงง่ายกว่าสำหรับ APIs ที่เน้น CRUD พร้อมรูปแบบข้อมูลที่สม่ำเสมอและสำหรับ public APIs ที่ caching สำคัญ
จะจัดการ authentication และ authorization ใน graphql-ruby อย่างไร?
Authentication เกิดขึ้นใน controller ก่อนการรัน GraphQL โดยทั่วไปผ่าน Devise, Warden หรือการตรวจสอบ JWT user ที่ authenticated ถูกส่งผ่านใน context Authorization เกิดขึ้นใน resolvers หรือผ่าน hook authorized? ของ graphql-ruby บน types และ mutations โดย throw GraphQL::ExecutionError เมื่อถูกปฏิเสธการเข้าถึง
สำหรับ การเตรียมสัมภาษณ์ Ruby on Rails เพิ่มเติม ดู module Rails API Mode ซึ่งครอบคลุมรูปแบบการออกแบบ REST API ที่เสริมความรู้ GraphQL
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
การสร้าง Production GraphQL APIs ด้วย Rails: ประเด็นสำคัญ
- ติดตั้ง graphql-ruby ด้วย
rails generate graphql:installเพื่อสร้าง scaffold schema, types และ routing - ใช้ DataLoader sources สำหรับ associations ทั้งหมดเพื่อรวม database queries และป้องกันปัญหาประสิทธิภาพ N+1
- กำหนด mutations ใน classes แยกพร้อม error fields ที่ชัดเจนใน return type
- ผสานรวม subscriptions ผ่าน ActionCable สำหรับการอัปเดตแบบ real-time โดย trigger จาก model callbacks หรือ service objects
- ทดสอบ queries และ mutations โดยรันโดยตรงกับ schema ใน RSpec
- Authentication อยู่ใน controller; การตรวจสอบ authorization เกิดขึ้นภายใน resolvers โดยใช้
context[:current_user] - เลือก GraphQL เมื่อ clients ต้องการการดึงข้อมูลที่ยืดหยุ่น; ใช้ REST สำหรับ CRUD APIs อย่างง่ายพร้อม responses ที่สม่ำเสมอ
คุณหาบั๊กใน Ruby on Rails เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 19 กันยายน 2569
แชร์
บทความที่เกี่ยวข้อง

Background Jobs ใน Rails 2026: Sidekiq vs Good Job และคำถามสัมภาษณ์
คู่มือฉบับสมบูรณ์สำหรับการเลือก queue backend ใน Rails: เปรียบเทียบ Sidekiq 8.x, Good Job 4.x และ Solid Queue โดยเน้นที่ throughput, ฟีเจอร์ และคำถามสัมภาษณ์เชิงเทคนิค

Rails Active Storage 2026: การอัปโหลดไฟล์ การเชื่อมต่อ S3 และคำถามสัมภาษณ์งาน
คู่มือฉบับสมบูรณ์เกี่ยวกับ Rails Active Storage สำหรับการอัปโหลดไฟล์ การเชื่อมต่อ Amazon S3 และการเตรียมตัวสัมภาษณ์งาน Ruby on Rails

Rails Stimulus และ Importmaps 2026: JavaScript สมัยใหม่ไม่ต้องใช้ Build Tools
Stimulus และ Importmaps ใน Rails 8.1 ช่วยให้เขียน JavaScript สมัยใหม่โดยไม่ต้องใช้ bundler บทความนี้ครอบคลุม controller, action, target และ package npm