Skip to content

sunilp303/python-api-styles

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

API Architecture Styles in Python

A single runnable repository that implements five API communication styles against the same domain (a books catalog), so you can compare them side-by-side.

┌──────────────┬──────────────────────────────────────────┐
│ Style        │ One-line summary                         │
├──────────────┼──────────────────────────────────────────┤
│ REST         │ Resource URLs + HTTP verbs               │
│ GraphQL      │ One endpoint, client picks fields        │
│ gRPC         │ Binary (Protobuf) + HTTP/2               │
│ SOAP         │ XML envelopes + strict contracts         │
│ WebSocket    │ Persistent bi-directional connection     │
└──────────────┴──────────────────────────────────────────┘

Every style exposes the same four operations: list_books · get_book(id) · create_book · search_books(q)


Quick Start

# 1. Clone / enter the repo
cd api-styles-python

# 2. Install all dependencies (one command)
pip install -r requirements.txt

# 3. Run any style — two terminals needed
python rest/server.py          # terminal 1 — starts server
python rest/client.py          # terminal 2 — runs demo + timing

# Replace "rest" with graphql_api / grpc_api / soap_api / websocket_api

Run all five + benchmark in one go:

python benchmark/run_all.py

Repository Layout

api-styles-python/
├── requirements.txt
├── common/
│   └── data.py                  20 seed books shared by every server
├── rest/
│   ├── server.py                FastAPI on port 8001
│   └── client.py                httpx + timing
├── graphql_api/
│   ├── server.py                Strawberry + FastAPI on port 8002
│   └── client.py                raw HTTP POST + timing + field-selection demo
├── grpc_api/
│   ├── proto/books.proto        Protobuf schema
│   ├── books_pb2.py             generated stubs (committed)
│   ├── books_pb2_grpc.py        generated stubs (committed)
│   ├── server.py                grpcio server on port 50051
│   └── client.py                grpc channel + streaming + timing
├── soap_api/
│   ├── server.py                Spyne WSGI on port 8003
│   └── client.py                zeep + raw XML display + timing
├── websocket_api/
│   ├── server.py                FastAPI WebSocket on port 8004
│   └── client.py                websockets lib + push subscription demo
└── benchmark/
    ├── run_all.py               starts all servers, benchmarks, prints table
    └── sample_results.md        pre-recorded results with explanations

1. RESTful

What it is

REST (Representational State Transfer) maps every noun in your system to a URL and uses HTTP verbs to express operations:

Verb Meaning Example
GET Read GET /books
POST Create POST /books
PUT Replace PUT /books/5
DELETE Delete DELETE /books/5

Responses are plain JSON over HTTP/1.1. Stateless — every request carries all needed context.

When to use REST

  • Public-facing web or mobile APIs consumed by browsers/apps you don't control
  • CRUD services where resources map naturally to URLs
  • Teams that need maximum tooling support (Swagger, Postman, curl)
  • Any situation where simplicity matters more than raw performance

When NOT to use REST

  • You need real-time push (use WebSocket)
  • Internal microservices where latency and payload size matter (use gRPC)
  • You need strict typed contracts for enterprise integration (consider SOAP)
  • Mobile clients on metered connections fetching large objects (consider GraphQL)

How to run

# Terminal 1
python rest/server.py
# → http://127.0.0.1:8001

# Terminal 2
python rest/client.py

Example — curl

# List all books
curl http://127.0.0.1:8001/books

# Get book by ID
curl http://127.0.0.1:8001/books/3

# Search
curl "http://127.0.0.1:8001/books?q=python"

# Create
curl -X POST http://127.0.0.1:8001/books \
     -H "Content-Type: application/json" \
     -d '{"title":"New Book","author":"Me","year":2024,"pages":300,"genre":"Tech"}'

Sample response

GET /books/1
{
  "id": 1,
  "title": "Clean Code",
  "author": "Robert Martin",
  "year": 2008,
  "pages": 431,
  "genre": "Engineering"
}

2. GraphQL

What it is

GraphQL exposes a single endpoint (POST /graphql). The client sends a query document describing exactly which fields it needs. The server returns only those fields — no more, no less.

Client sends:              Server returns:
{                          {
  books {          →         "data": {
    id                         "books": [
    title                        {"id":1, "title":"Clean Code"},
  }                              {"id":2, "title":"The Pragmatic Programmer"},
}                              ...
                           }
                         }

When to use GraphQL

  • Mobile apps or IoT devices on limited bandwidth — request only what you render
  • Frontend teams that iterate fast and don't want a new REST endpoint for every screen
  • APIs that aggregate data from multiple backend sources (avoids N+1 HTTP round-trips)
  • When different clients (web/mobile/TV) need different field subsets of the same data

When NOT to use GraphQL

  • Simple CRUD with a fixed client — REST is simpler
  • File uploads (awkward in GraphQL)
  • Clients you don't control (public API) — query complexity becomes a security concern
  • Teams unfamiliar with it — there is real operational overhead (query depth limiting, persisted queries, etc.)

How to run

# Terminal 1
python graphql_api/server.py
# → http://127.0.0.1:8002/graphql
# Interactive playground: http://127.0.0.1:8002/graphql

# Terminal 2
python graphql_api/client.py

Example — curl

# List all books (all fields)
curl -X POST http://127.0.0.1:8002/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ books { id title author year pages genre } }"}'

# List books — id and title ONLY (smaller payload)
curl -X POST http://127.0.0.1:8002/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ books { id title } }"}'

# Search
curl -X POST http://127.0.0.1:8002/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ searchBooks(q: \"python\") { id title author } }"}'

# Create (mutation)
curl -X POST http://127.0.0.1:8002/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"mutation { createBook(book:{title:\"Test\",author:\"A\",year:2024,pages:100,genre:\"Tech\"}) { id title } }"}'

Field selection demo

Full payload  (6 fields × 20 books): ~1,842 bytes
Slim payload  (id + title only)     :   ~418 bytes  ← 4.4× smaller

The client decides — no new endpoint needed.


3. gRPC

What it is

gRPC uses HTTP/2 for transport and Protocol Buffers (Protobuf) for serialization. Instead of text JSON, messages are binary-encoded using a schema (.proto file). Field names are replaced by small integers on the wire — payloads are 5–10× smaller than JSON.

HTTP/2 multiplexes multiple calls over one TCP connection and supports four call patterns:

  • Unary (one request → one response) — like REST
  • Server streaming — server sends a stream of messages
  • Client streaming — client sends a stream of messages
  • Bidirectional streaming — both sides stream simultaneously

When to use gRPC

  • Internal microservice communication (service mesh, Kubernetes)
  • When payload size and latency are critical constraints
  • Polyglot environments — Protobuf generates client code for 10+ languages
  • Streaming large datasets or live feeds between services
  • Strong schema enforcement is required between teams

When NOT to use gRPC

  • Browser clients — gRPC requires gRPC-Web proxy in browsers (HTTP/2 framing not accessible to browser JS)
  • Public APIs — Protobuf is not human-readable; debugging is harder
  • Teams without DevOps tooling for Protobuf schema versioning
  • Simple low-traffic APIs where REST simplicity wins

How to run

# Terminal 1
python grpc_api/server.py
# → 127.0.0.1:50051

# Terminal 2
python grpc_api/client.py

Proto schema (grpc_api/proto/books.proto)

service BookService {
  rpc ListBooks   (Empty)       returns (BookList);
  rpc GetBook     (BookId)      returns (Book);
  rpc CreateBook  (Book)        returns (Book);
  rpc SearchBooks (SearchQuery) returns (BookList);
  rpc StreamBooks (Empty)       returns (stream Book);  // server-streaming
}

Example — Python

import grpc
import grpc_api.books_pb2 as pb2
import grpc_api.books_pb2_grpc as pb2_grpc

with grpc.insecure_channel("127.0.0.1:50051") as channel:
    stub = pb2_grpc.BookServiceStub(channel)

    # Unary call
    books = stub.ListBooks(pb2.Empty())
    print(books.books[0].title)

    # Server streaming
    for book in stub.StreamBooks(pb2.Empty()):
        print(book.title)

Payload comparison

JSON (REST)    : 1,842 bytes  for 20 books
Protobuf (gRPC):   312 bytes  for 20 books  ← 5.9× smaller

4. SOAP

What it is

SOAP (Simple Object Access Protocol) wraps every call in an XML envelope. The server publishes a WSDL (Web Services Description Language) file — a machine-readable contract that describes every method, parameter, and return type. Clients generate typed proxies from the WSDL.

<!-- Every SOAP request looks like this -->
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <tns:get_book>
      <tns:book_id>5</tns:book_id>
    </tns:get_book>
  </soap:Body>
</soap:Envelope>

When to use SOAP

  • Integrating with banks, insurance companies, government agencies, or any legacy enterprise system that exposes SOAP endpoints
  • You need WS-Security (XML signatures, encryption)
  • You need ACID-compliant distributed transactions (WS-AtomicTransaction)
  • Strict formal contracts are required between organizations

When NOT to use SOAP

  • Building a new API from scratch — SOAP's verbosity and complexity are rarely worth it for greenfield projects
  • Mobile or browser clients — XML parsing is expensive on constrained devices
  • Any performance-sensitive path

How to run

# Terminal 1
python soap_api/server.py
# → http://127.0.0.1:8003
# WSDL at: http://127.0.0.1:8003/books?wsdl

# Terminal 2
python soap_api/client.py

Example — Python (zeep)

from zeep import Client

client = Client("http://127.0.0.1:8003/books?wsdl")
books  = client.service.list_books()
book   = client.service.get_book(book_id=3)
result = client.service.search_books(q="python")
new    = client.service.create_book(
    title="SOAP Book", author="A", year=2000, pages=100, genre="Legacy"
)

Payload size note

REST JSON  : 1,842 bytes
SOAP XML   : 5,893 bytes  ← 3.2× larger (XML tags, namespaces, envelope overhead)

5. WebSocket

What it is

WebSocket upgrades a normal HTTP connection into a persistent, full-duplex TCP tunnel. Once the handshake completes, either side can send frames at any time with minimal overhead (2–14 byte frame header vs hundreds of bytes of HTTP headers).

Client                     Server
  |--- HTTP Upgrade -------->|
  |<-- 101 Switching --------|
  |<========================>|  (persistent connection)
  |--- {"action":"list"} --->|
  |<--- {"data":[...]} ------|
  |<--- {"event":"push"} ----|  (server-initiated)
  |<--- {"event":"push"} ----|

When to use WebSocket

  • Real-time applications: chat, collaborative editing, live dashboards
  • Push notifications — server needs to push data without the client polling
  • Multiplayer games or trading platforms where every millisecond counts
  • Any scenario where you'd otherwise poll a REST API repeatedly

When NOT to use WebSocket

  • Standard CRUD APIs — HTTP is simpler and better cached
  • One-shot requests — the connection overhead isn't worth it for a single request
  • Clients behind proxies/firewalls that strip non-HTTP traffic

How to run

# Terminal 1
python websocket_api/server.py
# → ws://127.0.0.1:8004/ws/books

# Terminal 2
python websocket_api/client.py

Example — Python

import asyncio, json, websockets

async def main():
    async with websockets.connect("ws://127.0.0.1:8004/ws/books") as ws:
        # Request-response
        await ws.send(json.dumps({"action": "list_books"}))
        books = json.loads(await ws.recv())["data"]
        print(f"{len(books)} books received")

        # Subscribe to server push (500 ms interval)
        await ws.send(json.dumps({"action": "subscribe", "interval_ms": 500}))
        for _ in range(6):                     # receive 6 pushed events
            event = json.loads(await ws.recv())
            print(event)

asyncio.run(main())

Decision Matrix

Criterion REST GraphQL gRPC SOAP WebSocket
Browser support ⚠️
Human-readable wire ⚠️
Server push ✅*
Field selection
Small payload ⚠️ ⚠️
Low latency ⚠️ ⚠️
Strict contract (WSDL) ⚠️
Tooling / ecosystem ⚠️ ⚠️
Learning curve Low Medium Medium High Medium

✅ strong · ⚠️ partial · ❌ no · * server-streaming RPC


Benchmarks

See benchmark/sample_results.md for pre-recorded numbers with explanations.

Run them yourself:

python benchmark/run_all.py

Summary (localhost, 100 × list_books, 20 books)

Style       Mean (ms)   P95 (ms)   P99 (ms)  Payload (B)
---------------------------------------------------------
REST             2.14       3.41       5.12        1,842
GraphQL          2.87       4.23       6.30        1,842
gRPC             0.91       1.38       1.97          312   ← binary Protobuf
SOAP             4.73       7.09      10.18        5,893   ← XML overhead
WebSocket        0.38       0.57       0.84        1,842   ← persistent conn

Key insights:

  • gRPC payload is 5.9× smaller than REST/GraphQL/WebSocket
  • WebSocket per-message latency is lowest because TCP handshake is paid once
  • SOAP is slowest due to XML parsing on both sides
  • GraphQL payload drops to ~418 bytes when you request only id + title

Running Individual Servers

Style Command Port Health check
REST python rest/server.py 8001 curl http://127.0.0.1:8001/health
GraphQL python graphql_api/server.py 8002 curl http://127.0.0.1:8002/health
gRPC python grpc_api/server.py 50051 (use grpc_api/client.py)
SOAP python soap_api/server.py 8003 curl http://127.0.0.1:8003/books?wsdl
WebSocket python websocket_api/server.py 8004 curl http://127.0.0.1:8004/health

Dependencies

fastapi              REST + GraphQL + WebSocket server framework
uvicorn              ASGI server
strawberry-graphql   GraphQL schema builder
grpcio               gRPC runtime
grpcio-tools         protoc compiler (generates books_pb2*.py from .proto)
spyne                SOAP server framework
lxml                 XML parser (required by Spyne)
zeep                 SOAP client
websockets           WebSocket client library
httpx                HTTP client (REST + GraphQL clients)

Install everything with:

pip install -r requirements.txt

To regenerate gRPC stubs from the proto (only needed if you edit books.proto):

python -m grpc_tools.protoc \
  -I grpc_api/proto \
  --python_out=grpc_api \
  --grpc_python_out=grpc_api \
  grpc_api/proto/books.proto

About

Five API styles (REST, GraphQL, gRPC, SOAP, WebSocket) implemented in Python against the same domain — with benchmarks, examples, and a decision guide.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages