Skip to content

REST API Introduction for GraphQL Comparison

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you'll learn about REST API fundamentals. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

REST (Representational State Transfer) is an architectural style where APIs expose resources as URLs and use HTTP methods for CRUD operations.

What You'll Learn

By the end of this lesson, you will understand REST's resource-oriented design, standard HTTP method usage, and how REST differs from Graphql in data fetching.

Why It Matters

REST remains the most widely adopted API style. Understanding REST deeply is essential before comparing it with GraphQL, as most GraphQL critiques reference REST patterns.

Real-World Use

Twitter API uses REST endpoints like GET /statuses/user_timeline and POST /statuses/update. Each endpoint returns a fixed response structure.

REST Request Flow

sequenceDiagram
    Client->>Server: GET /users
    Server->>Client: [{"id":1,"name":"A"},{"id":2,"name":"B"}]
    Client->>Server: GET /users/1/posts
    Server->>Client: [{"id":10,"title":"Post","user_id":1}]
    Client->>Server: GET /users/2/posts
    Server->>Client: [{"id":11,"title":"Another"}]

REST Endpoint Design

# rest_endpoints.py
from typing import Any, Dict, List

class RESTAPI:
    def __init__(self):
        self.routes: Dict[str, Dict[str, callable]] = {}

    def get(self, path: str, handler: callable):
        self.routes.setdefault(path, {})["GET"] = handler

    def post(self, path: str, handler: callable):
        self.routes.setdefault(path, {})["POST"] = handler

    def handle(self, method: str, path: str, **kwargs) -> Dict:
        path_handlers = self.routes.get(path, {})
        handler = path_handlers.get(method)
        if not handler:
            return {"error": f"No {method} {path}"}
        return handler(**kwargs)

api = RESTApi()
api.get("/users", lambda: {"users": [{"id": 1, "name": "Alice"}]})
api.post("/users", lambda name=None, email=None: {
    "user": {"id": 2, "name": name, "email": email},
    "status": "created"
})

print(api.handle("GET", "/users"))
print(api.handle("POST", "/users", name="Bob", email="bob@x.com"))
print(api.handle("GET", "/nonexistent"))

Expected output:

{'users': [{'id': 1, 'name': 'Alice'}]}
{'user': {'id': 2, 'name': 'Bob', 'email': 'bob@x.com'}, 'status': 'created'}
{'error': 'No GET /nonexistent'}

REST Response Structure

# rest_responses.py
from typing import Any, Dict, List

class RESTResponse:
    @staticmethod
    def success(data: Any, status: int = 200) -> Dict:
        return {"status": status, "data": data, "_format": "rest"}

    @staticmethod
    def error(message: str, status: int = 400) -> Dict:
        return {"status": status, "error": message, "_format": "rest"}

    @staticmethod
    def paginated(items: List, page: int, per_page: int, total: int) -> Dict:
        return {
            "status": 200,
            "data": items,
            "pagination": {
                "page": page,
                "per_page": per_page,
                "total": total,
                "pages": (total + per_page - 1) // per_page,
            },
            "_format": "rest",
        }

resp = RESTResponse()
print(resp.success({"id": 1, "name": "Alice"}))
print(resp.error("User not found", 404))
print(resp.paginated([{"id": 1}], page=1, per_page=10, total=25))

Expected output:

{'status': 200, 'data': {'id': 1, 'name': 'Alice'}, '_format': 'rest'}
{'status': 404, 'error': 'User not found', '_format': 'rest'}
{'status': 200, 'data': [{'id': 1}], 'pagination': {'page': 1, 'per_page': 10, 'total': 25, 'pages': 3}, '_format': 'rest'}

Common Mistakes

1. Not Using HTTP Methods Correctly

Using GET for creating resources or POST for reading. GET must be safe and idempotent. POST is not idempotent.

2. Too Many Endpoints

Creating a new endpoint for every data combination leads to endpoint explosion and client complexity.

3. Inconsistent Response Structure

Some endpoints return arrays, others wrap in objects. Consistent structures prevent client-side errors.

4. Ignoring HTTP Status Codes

Returning 200 for errors or 500 for validation failures. Use correct codes: 201 for create, 400 for bad request.

5. No Pagination for Lists

Returning all results in one response. Always paginate list endpoints with page, limit, and total count.

Practice Questions

1. What does REST stand for?

Representational State Transfer.

2. What HTTP method creates a resource?

POST.

3. Is GET idempotent?

Yes, multiple identical GET requests have the same effect as one.

4. How does REST handle related data?

Through nested endpoints like /users/1/posts or linking via IDs.

Challenge

Design REST endpoints for a blog with users, posts, comments, and tags. Include pagination and proper HTTP method usage.

FAQ

Is REST a protocol?

No, it is an architectural style. It uses HTTP as the protocol but defines resource-oriented design principles.

Does REST require JSON?

No, REST supports any format (JSON, XML, HTML, plain text). JSON is the most common.

What is HATEOAS?

Hypermedia as the Engine of Application State — responses include links to related resources. Rarely implemented in practice.

Can REST use WebSockets?

REST is stateless request-response. For real-time, use WebSockets or SSE alongside REST endpoints.

Is REST good for mobile apps?

REST works for mobile, but over-fetching and multiple round trips can drain battery and data. GraphQL is often better for mobile.

Mini Project: REST API Builder

# rest_builder.py
from typing import Any, Dict, List

class RESTBuilder:
    def __init__(self):
        self.resources = {}

    def resource(self, name: str):
        self.resources[name] = []

    def create(self, resource: str, **data) -> Dict:
        entry = {"id": len(self.resources[resource]) + 1, **data}
        self.resources[resource].append(entry)
        return {"status": 201, "data": entry}

    def list_all(self, resource: str) -> Dict:
        return {"status": 200, "data": self.resources.get(resource, []),
                "count": len(self.resources.get(resource, []))}

    def get(self, resource: str, id: int) -> Dict:
        items = self.resources.get(resource, [])
        for item in items:
            if item["id"] == id:
                return {"status": 200, "data": item}
        return {"status": 404, "error": "Not found"}

api = RESTBuilder()
api.resource("users")
api.create("users", name="Alice", role="admin")
api.create("users", name="Bob")
print(api.list_all("users"))
print(api.get("users", 1))
print(api.get("users", 99))

Expected output:

{'status': 200, 'data': [{'id': 1, 'name': 'Alice', 'role': 'admin'}, {'id': 2, 'name': 'Bob'}], 'count': 2}
{'status': 200, 'data': {'id': 1, 'name': 'Alice', 'role': 'admin'}}
{'status': 404, 'error': 'Not found'}

What's Next

You understand REST basics. Next, learn about GraphQL, then compare data fetching in both.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro