اگر چند سالی است که با توسعهی نرمافزار، مخصوصاً بکاند یا سیستمهای توزیعشده سر و کار دارید، احتمالاً اسم REST، GraphQL و gRPC بارها به گوشتان خورده است. بعضی وقتها حتی این سؤال پیش میآید که «خب، همهشون API هستن، دقیقاً فرقشون چیه؟» یا «آیا GraphQL قراره جای REST رو بگیره؟» یا «gRPC اصلاً به درد وب میخوره یا فقط برای سیستمهای خاصه؟»
در این مقاله سعی میکنم بدون اغراق، اما عمیق و فنی، این سه رویکرد را بررسی کنم؛ از فلسفهی طراحی گرفته تا جزئیات فنی، مزایا، محدودیتها و سناریوهای واقعی استفاده.

REST یا Representational State Transfer در اصل یک سبک معماری است، نه یک تکنولوژی مشخص. یعنی REST به شما نمیگوید حتماً از چه کتابخانه یا پروتکلی استفاده کنید، بلکه یک سری اصول و قرارداد تعریف میکند برای اینکه کلاینت و سرور بتوانند به شکل ساده و قابل پیشبینی با هم حرف بزنند.
در REST همهچیز حول مفهوم «Resource» میچرخد. شما یک منبع دارید، مثلاً user یا order، و این منبع از طریق URL قابل دسترسی است. عملیاتها هم معمولاً با HTTP Methodها مشخص میشوند؛ GET برای خواندن، POST برای ساختن، PUT/PATCH برای بهروزرسانی و DELETE برای حذف. این سادگی باعث شده REST به انتخاب پیشفرض دنیای وب تبدیل شود.
از نظر فنی، REST معمولاً روی HTTP/1.1 یا HTTP/2 پیادهسازی میشود و رایجترین فرمت تبادل داده هم JSON است. همین انتخابها باعث شده REST بسیار قابل دیباگ باشد؛ شما بهراحتی میتوانید با curl یا Postman درخواست بزنید، پاسخ را ببینید و حتی در مرورگر تستش کنید.
اما همین سادگی، محدودیتهایی هم ایجاد میکند. یکی از مشکلات معروف REST مسئلهی Over-fetching و Under-fetching است. یعنی یا دیتای بیشتری از نیازتان میگیرید یا مجبور میشوید برای گرفتن یک دادهی کامل، چندین درخواست مختلف بفرستید. مخصوصاً در اپلیکیشنهای موبایل یا فرانتاندهای پیچیده، این موضوع میتواند آزاردهنده باشد.
از نظر نسخهبندی هم REST معمولاً به ورژنبندی در URL یا Header متکی است (مثلاً /v1/users). این کار جواب میدهد، اما وقتی API بزرگ میشود، مدیریت نسخهها میتواند پیچیده شود.
به طور خلاصه REST هنوز هم انتخاب بسیار خوبی است وقتی:
API ساده و قابل فهم میخواهیم درست کنیم
با وب و HTTP سر و کار داریم
تیم بزرگ است و خوانایی و استاندارد مهم است
Performance خیلی بحرانی نیست
فرض کنیم یک API ساده با Node.js و Express داریم.
GET /users/:id
import express from "express"; const app = express(); app.get("/users/:id", (req, res) => { const userId = req.params.id; // این رو فرض کنیم دیتابیسه برگردونده برامون const user = { id: userId, name: "Mojtaba", email: "mojtaba@example.com", age: 35, posts: 15 }; res.json(user); }); app.listen(3000, () => { console.log("REST API running on port 3000"); });
GET /users/1 HTTP/1.1 Host: api.example.com
{ "id": "1", "name": "Mojtaba", "email": "Mojtaba@example.com", "age": 35, "posts": 15 }
در REST، شکل پاسخ توسط سرور تعیین میشه. حتی اگر کلاینت فقط name و email را بخواهد، کل آبجکت برمیگرده.
GraphQL توسط فیسبوک معرفی شد تا یکی از مشکلات اساسی REST را حل کند:
اینکه کلاینت دقیقاً همان دیتایی را بگیرد که نیاز دارد، نه بیشتر و نه کمتر.
در GraphQL برخلاف REST، ما چند endpoint مختلف نداریم؛ معمولاً فقط یک endpoint وجود دارد. کلاینت داخل request خودش مشخص میکند چه فیلدهایی را از چه آبجکتهایی میخواهد. سرور هم دقیقاً همان ساختار را برمیگرداند.
این رویکرد از نظر تجربهی توسعه، مخصوصاً در فرانتاند، فوقالعاده هستش. فرانتاند دیگر وابسته به تصمیمهای بکاند دربارهی شکل response نیست. اگر فردا نیاز به یک فیلد جدید داشته باشیم، کافی است query را تغییر دهیم، بدون اینکه API جدیدی ساخته شود. (این از نظر تجربی خیلی موضوع مهمی است)
GraphQL یک Schema strongly typed دارد. یعنی از قبل مشخص است چه typeهایی داریم، چه فیلدهایی، چه ورودیهایی و چه خروجیهایی. همین موضوع باعث میشود tooling خیلی قویای شکل بگیرد؛ از auto-complete در IDE گرفته تا validation قبل از اجرا و هر آن چیزی که جریان ما رو تحت تاثیر بتونه قرار بده.
اما این قدرت، هزینه هم داره. پیادهسازی GraphQL در بکاند معمولاً پیچیدهتر از REST است. باید حواسمون به performance باشه، چون یک query بد میتونه منجر به تعداد زیادی call به دیتابیس بشه (مشکل معروف N+1). این مشکل رو در گروه فنی تلگرامی Dev Experience (@devexperiences) شرح دادم. همچنین caching در GraphQL برخلاف REST که روی HTTP Cache سوار میشود، نیاز به راهکارهای اختصاصی دارد. (در مقاله بعدی بیشتر بهش خواهم پرداخت موضوع کش رو)
از نظر امنیت هم باید دقت بیشتری کرد؛ چون کلاینت میتواند queryهای پیچیده بنویسد، معمولاً محدودیت عمق (query depth) یا complexity تعریف میشود.
GraphQL انتخاب مناسبی است وقتی:
فرانتاند پیچیده و پویا داریم
چند کلاینت مختلف (وب، موبایل و ...) داریم
تغییرات مداوم در نیازهای داده وجود دارد
تیم توان مدیریت پیچیدگی بکاند را دارد
در GraphQL همان سناریو قبلی که نوشیم واسه REST را داریم، اما کنترل داده دست کلاینت هستش.
import { buildSchema } from "graphql"; export const schema = buildSchema(` type User { id: ID! name: String! email: String! age: Int posts: Int } type Query { user(id: ID!): User } `);
export const root = { user: ({ id }) => { return { id, name: "Mojtaba", email: "mojtaba@example.com", age: 35, posts: 15 }; } };
query { user(id: "1") { name email } }
{ "data": { "user": { "name": "Mojtaba", "email": "mojtaba@example.com" } } }
اینجا دقیقاً همان چیزی که درخواست شده برگشته؛
نه age داریم، نه posts.
این همان جایی هست که GraphQL مشکل over-fetching را حل میکند.
gRPC بیشتر از اینکه برای وب عمومی طراحی شده باشد، برای ارتباط بین سرویسها (Microservices) ساخته شده است. این تکنولوژی توسط گوگل توسعه داده شده و تمرکز اصلیاش روی performance، type safety و ارتباطات پایدار است.
gRPC بهجای JSON از Protocol Buffers (Protobuf) استفاده میکند؛ یک فرمت باینری فشرده و بسیار سریع. قرارداد ارتباطی (Service و Messageها) در فایلهای .proto تعریف میشود و بعد از روی آنها کد کلاینت و سرور به زبانهای مختلف بهصورت خودکار تولید میشود.
از نظر شبکه، gRPC روی HTTP/2 سوار است و قابلیتهایی مثل multiplexing، streaming (یکطرفه یا دوطرفه) و connection پایدار را بهصورت built-in دارد. این ویژگیها باعث میشود gRPC برای سیستمهایی با latency بالا یا حجم بالای درخواست فوقالعاده باشد.
اما gRPC برای استفادهی مستقیم در مرورگر محدودیت دارد. مرورگرها بهصورت native از gRPC پشتیبانی نمیکنند (مگر با gRPC-Web که خودش داستان جداگانهای داره و باید در یک پست دیگه حتما راجبش بحث و گفتگو کنیم). همچنین دیباگ کردن gRPC نسبت به REST سختتر است؛ چون دادهها باینری هستند و با چشم دیده نمیشوند.
gRPC معمولاً انتخاب اول نیست برای public API، اما برای ارتباط داخلی بین سرویسها بسیار محبوب است.
gRPC بهترین گزینه است وقتی:
Performance و latency حیاتی هست برامون
معماری Microservice داریم و یا حداقل شبه میکروسرویس
ارتباط server-to-server داریم در پروژه
type safety و قرارداد مشخص مهم است برامون
در gRPC اول قرارداد ارتباطی را تعریف میکنیم و بعد از روی آن کد تولید میشود.
syntax = "proto3"; package user; service UserService { rpc GetUser (UserRequest) returns (UserResponse); } message UserRequest { string id = 1; } message UserResponse { string id = 1; string name = 2; string email = 3; int32 age = 4; int32 posts = 5; }
import grpc from "@grpc/grpc-js"; import protoLoader from "@grpc/proto-loader"; const packageDefinition = protoLoader.loadSync("user.proto"); const userProto = grpc.loadPackageDefinition(packageDefinition).user; function getUser(call, callback) { callback(null, { id: call.request.id, name: "Mojtaba", email: "mojtaba@example.com", age: 35, posts: 15 }); } const server = new grpc.Server(); server.addService(userProto.UserService.service, { getUser }); server.bindAsync( "0.0.0.0:50051", grpc.ServerCredentials.createInsecure(), () => server.start() );
const client = new userProto.UserService( "localhost:50051", grpc.credentials.createInsecure() ); client.getUser({ id: "1" }, (err, response) => { console.log(response); });
{ id: '1', name: 'Mojtaba', email: 'mojtaba@example.com', age: 35, posts: 15 }
در gRPC:
قرارداد کاملاً type-safe است
دیتا باینری و بسیار سریع منتقل میشود
مناسب ارتباط service-to-service است، نه مصرف مستقیم در مرورگر
واقعیت این است که هیچکدام «بهتر مطلق» نیستند. REST، GraphQL و gRPC هر کدام برای مسئلهای خاص طراحی شده. خیلی از سیستمهای حرفهای امروزی حتی از ترکیب اینها استفاده میکنند؛ مثلاً GraphQL برای فرانتاند، gRPC برای ارتباط داخلی سرویسها و REST برای APIهای عمومی.
سؤال اصلی همیشه این است:
مسئلهی من چیست و کدام ابزار بهترین پاسخ را میدهد؟
اگر این سؤال را درست بپرسیم، انتخاب معماری API هم معمولاً خودش را نشان میدهد.
REST: ساده، خوانا، مناسب APIهای عمومی
GraphQL: انعطافپذیر، کلاینتمحور، مناسب فرانتاندهای پیچیده
gRPC: سریع، تایپسیف، عالی برای میکروسرویسها