Published on

REST vs RPC vs GraphQL: A Frontend Developer's Guide to API Architecture

Authors
  • avatar
    Name
    Mohit Verma
    Twitter

Hey frontend folks! πŸ‘‹ Let's talk about something we deal with daily but often don't think too deeply about - API architectures. I've worked with all three major approaches (REST, RPC, and GraphQL), and today I'm sharing my real-world experiences with each one.

Quick Overview: The Three Musketeers 🀺

First, let's break down each approach with a simple example - fetching user data:

// REST - Resource-focused
GET /api/users/123
GET /api/users/123/posts
GET /api/users/123/followers

// RPC - Action-focused
POST /api/getUserById({ id: 123 })
POST /api/getUserPosts({ userId: 123 })
POST /api/getUserFollowers({ userId: 123 })

// GraphQL - Query-focused
query {
  user(id: 123) {
    name
    posts {
      title
      content
    }
    followers {
      name
      avatar
    }
  }
}

REST: The Familiar Friend 🀝

Pros:

  1. Widely Adopted
// Most developers are familiar with this
const fetchUser = async (id) => {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
};
  1. Caching is Straightforward
// Browser naturally caches GET requests
fetch('/api/users/123', {
  headers: {
    'Cache-Control': 'max-age=3600'
  }
});

Cons:

  1. Overfetching/Underfetching
// Need just the username? Too bad, you get everything
const user = await fetch('/api/users/123').then(r => r.json());
console.log(user.name); // But we received full user object
  1. Multiple Requests for Related Data
// The dreaded waterfall
const user = await fetch('/api/users/123').then(r => r.json());
const posts = await fetch(`/api/users/${user.id}/posts`).then(r => r.json());
const followers = await fetch(`/api/users/${user.id}/followers`).then(r => r.json());

RPC: The Action Hero πŸ¦Έβ€β™‚οΈ

Pros:

  1. Intuitive for Action-Based Operations
// Clear what's happening
const banUser = async (userId) => {
  return await rpc.call('banUser', { userId });
};

const sendFriendRequest = async (fromId, toId) => {
  return await rpc.call('sendFriendRequest', { fromId, toId });
};
  1. Great for Complex Operations
// Multiple steps in one call
const processOrder = async (orderId) => {
  return await rpc.call('processOrder', {
    orderId,
    steps: ['validate', 'payment', 'inventory', 'shipping']
  });
};

Cons:

  1. Less Predictable
// What does this actually do?
await rpc.call('handleUser', { userId: 123 });
// Naming becomes crucial for understanding
  1. Harder to Cache
// Most RPC calls use POST
const getData = async () => {
  return await rpc.call('getData', { /* params */ });
  // No automatic browser caching
};

GraphQL: The New Kid on the Block 🌟

Pros:

  1. Precise Data Fetching
const UserProfile = () => {
  const { data } = useQuery(gql`
    query GetUser($id: ID!) {
      user(id: $id) {
        name
        avatar
        # Only what we need!
      }
    }
  `);
  
  return <div>{data.user.name}</div>;
};
  1. One Request for Multiple Resources
const DashboardData = () => {
  const { data } = useQuery(gql`
    query GetDashboard {
      user {
        name
        posts {
          title
        }
        followers {
          name
        }
      }
      notifications {
        message
      }
      stats {
        views
      }
    }
  `);
  
  // All data in one request!
};

Cons:

  1. Learning Curve
// Need to learn query language
const COMPLEX_QUERY = gql`
  query GetUserData($id: ID!, $first: Int!) {
    user(id: $id) {
      ...UserFields
      posts(first: $first) {
        edges {
          node {
            ...PostFields
          }
        }
      }
    }
  }
  ${USER_FIELDS}
  ${POST_FIELDS}
`;
  1. Can Be Overkill for Simple APIs
// Do we really need GraphQL for this?
query {
  ping
}

// REST would be simpler:
GET /api/ping

Real-World Decision Making πŸ€”

Here's when I choose each:

REST

// Perfect for CRUD operations
const api = {
  getUser: (id) => fetch(`/users/${id}`),
  createUser: (data) => fetch('/users', {
    method: 'POST',
    body: JSON.stringify(data)
  }),
  updateUser: (id, data) => fetch(`/users/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data)
  }),
  deleteUser: (id) => fetch(`/users/${id}`, {
    method: 'DELETE'
  })
};

RPC

// Great for action-based operations
const gameApi = {
  startMatch: (players) => rpc.call('startMatch', { players }),
  makeMove: (gameId, move) => rpc.call('makeMove', { gameId, move }),
  forfeitGame: (gameId, playerId) => rpc.call('forfeitGame', { gameId, playerId })
};

GraphQL

// Perfect for complex, nested data
const SocialFeed = () => {
  const { data } = useQuery(gql`
    query GetFeed {
      feed {
        posts {
          author {
            name
            followers {
              count
            }
          }
          comments {
            text
            likes
          }
          shares {
            count
          }
        }
      }
    }
  `);
};

My Personal Decision Framework 🎯

I choose:

  1. REST when:

    • Building simple CRUD applications
    • Need good caching
    • Working with a traditional backend team
  2. RPC when:

    • Building action-heavy applications (games, trading)
    • Need simple, direct function calls
    • Performance is crucial
  3. GraphQL when:

    • Building data-heavy applications
    • Need flexible data fetching
    • Working with multiple data sources

Practical Tips for Each πŸ’‘

REST Tips

// Use axios for better DX
const api = axios.create({
  baseURL: '/api',
  headers: {
    'Content-Type': 'application/json'
  }
});

// Organize by resources
const userApi = {
  get: (id) => api.get(`/users/${id}`),
  getPosts: (id) => api.get(`/users/${id}/posts`)
};

RPC Tips

// Create type-safe RPC clients
interface RPCMethods {
  startGame(params: StartGameParams): Promise<GameState>;
  makeMove(params: MoveParams): Promise<MoveResult>;
}

const rpc = createRPCClient<RPCMethods>('/api');

GraphQL Tips

// Use fragments for reusable pieces
const USER_FIELDS = gql`
  fragment UserFields on User {
    id
    name
    avatar
    status
  }
`;

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      ...UserFields
    }
  }
  ${USER_FIELDS}
`;

Conclusion

Remember:

  1. There's no "best" approach - only best for your use case
  2. You can mix approaches in the same project
  3. Start simple, add complexity when needed
  4. Consider your team's expertise
  5. Think about long-term maintenance

Choose wisely, and don't be afraid to change if needed! Happy coding! πŸš€

Practice Makes Perfect

Visit PrepareFrontend to start practicing frontend interview questions

Visit