swadhinbiswas 📦 v2.0.8: security fixes, documentation, testing improvements · 7/28/2026
84307b8
381 lines · 7.5 KB
Raw Download

Migration Guide: From Robyn/Granian to JustAPI

Migrate your existing Robyn or Granian applications to JustAPI for better performance and features.


Table of Contents

  1. Overview
  2. Migration from Robyn
  3. Migration from Granian
  4. Feature Comparison
  5. Common Patterns
  6. Performance Gains

1. Overview

Why Migrate to JustAPI?

Feature Robyn Granian JustAPI
Performance Good Good Best
Rust core Partial Yes Yes
Native fast path No No Yes
WebSocket Yes No Yes
gRPC No No Yes
GraphQL No No Yes
ML inference No No Yes
Circuit breakers No No Yes
Rate limiting Basic No Yes
OpenAPI Manual No Auto

Migration Effort

  • Robyn → JustAPI: ~30 minutes (similar API)
  • Granian → JustAPI: ~1-2 hours (different approach)

2. Migration from Robyn

Step 1: Update Imports

# Before (Robyn)
from robyn import Robyn, Request

# After (JustAPI)
from justapi import JustAPIApp, Request

Step 2: Update App Creation

# Before (Robyn)
app = Robyn(__name__)

# After (JustAPI)
app = JustAPIApp()

Step 3: Update Route Decorators

# Before (Robyn)
@app.get("/hello")
async def hello():
    return "Hello, World!"

# After (JustAPI) - identical!
@app.get("/hello")
async def hello():
    return {"message": "Hello, World!"}

Step 4: Update Request Handling

# Before (Robyn)
@app.get("/users/{id}")
async def get_user(request: Request):
    user_id = request.path_params["id"]
    return {"id": user_id}

# After (JustAPI)
@app.get("/users/{id}")
async def get_user(id: int):  # Auto-extracted!
    return {"id": id}

Step 5: Update WebSocket Handlers

# Before (Robyn)
@app.websocket("/ws")
async def websocket(request):
    await request.accept()
    while True:
        msg = await request.recv_text()
        await request.send_text(f"Echo: {msg}")

# After (JustAPI)
@app.websocket("/ws")
async def websocket(ws):
    await ws.accept()
    while True:
        msg = await ws.receive_text()
        await ws.send_text(f"Echo: {msg}")

Step 6: Update Startup/Shutdown

# Before (Robyn)
@app.startup
async def startup():
    print("Server starting")

@app.shutdown
async def shutdown():
    print("Server shutting down")

# After (JustAPI)
@app.on_startup
async def startup():
    print("Server starting")

@app.on_shutdown
async def shutdown():
    print("Server shutting down")

Step 7: Run the Server

# Before (Robyn)
app.start(port=8000)

# After (JustAPI)
app.run("0.0.0.0:8000")

3. Migration from Granian

Step 1: Understand the Difference

Granian is an ASGI server (like Uvicorn), not a framework. You likely use it with FastAPI or Starlette.

Option A: Migrate to JustAPI as a framework (recommended)
Option B: Use JustAPI as an ASGI server (compatibility mode)

Option A: Full Migration to JustAPI

# Before (FastAPI + Granian)
from fastapi import FastAPI
app = FastAPI()

@app.get("/hello")
async def hello():
    return {"message": "Hello"}

# After (JustAPI)
from justapi import JustAPIApp
app = JustAPIApp()

@app.get("/hello")
async def hello():
    return {"message": "Hello"}

Option B: ASGI Compatibility Mode

# Keep your FastAPI app, run on JustAPI's Rust runtime
from fastapi import FastAPI
from justapi import JustAPIApp

fastapi_app = FastAPI()

@fastapi_app.get("/hello")
async def hello():
    return {"message": "Hello"}

# Run FastAPI app on JustAPI's Rust server
app = JustAPIApp()
app.run("0.0.0.0:8000", asgi_app=fastapi_app)

Step 2: Update Server Configuration

# Before (Granian)
import granian
granian.run(
    app,
    interface="asgi",
    host="0.0.0.0",
    port=8000,
    workers=4
)

# After (JustAPI)
app.run("0.0.0.0:8000")

Step 3: Update Middleware

# Before (Starlette middleware)
from starlette.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware, allow_origins=["*"])

# After (JustAPI)
from justapi.middleware import CORS
app.add_middleware(CORS(allow_origins=["*"]))

4. Feature Comparison

Request Handling

# Robyn
@app.get("/users/{id}")
async def get_user(request):
    return {"id": request.path_params["id"]}

# Granian (with FastAPI)
@app.get("/users/{id}")
async def get_user(id: int):
    return {"id": id}

# JustAPI (auto-extraction like FastAPI)
@app.get("/users/{id}")
async def get_user(id: int):
    return {"id": id}

Dependency Injection

# Robyn - No built-in DI

# Granian (with FastAPI)
from fastapi import Depends
async def get_db():
    return db_pool
@app.get("/users")
async def get_users(db = Depends(get_db)):
    return await db.fetch_all()

# JustAPI (FastAPI-compatible)
from justapi import Depends
async def get_db():
    return db_pool
@app.get("/users")
async def get_users(db = Depends(get_db)):
    return await db.fetch_all()

Background Tasks

# Robyn - No built-in background tasks

# Granian (with FastAPI)
from fastapi import BackgroundTasks
@app.post("/users")
async def create_user(background_tasks: BackgroundTasks):
    background_tasks.add_task(send_email, user.email)
    return {"status": "created"}

# JustAPI (FastAPI-compatible)
from justapi import BackgroundTasks
@app.post("/users")
async def create_user(background_tasks: BackgroundTasks):
    background_tasks.add_task(send_email, user.email)
    return {"status": "created"}

5. Common Patterns

File Uploads

# Robyn
@app.post("/upload")
async def upload(request):
    file = await request.files("file")
    return {"filename": file.name}

# JustAPI
from justapi import UploadFile
@app.post("/upload")
async def upload(file: UploadFile):
    contents = await file.read()
    return {"filename": file.filename, "size": len(contents)}

Error Handling

# Robyn
@app.get("/error")
async def error():
    raise ValueError("Something went wrong")

# JustAPI
from justapi import HTTPException
@app.get("/error")
async def error():
    raise HTTPException(status_code=400, detail="Something went wrong")

Authentication

# Robyn - Manual implementation

# JustAPI - Built-in JWT
from justapi import JWTAuth
auth = JWTAuth(secret="your-secret-key")

@app.get("/protected")
@auth.require
async def protected_endpoint():
    return {"message": "Authenticated"}

6. Performance Gains

Expected Improvements

Metric Robyn Granian JustAPI Improvement
Hello-world RPS 39,103 314,195 60,297 1.5x vs Robyn
JSON echo RPS 36,899 144,502 47,415 1.3x vs Robyn
p99 latency 11.47ms 0.74ms 1.10ms 10x vs Robyn

Native Fast Path

For schema-validated routes, JustAPI can serve entirely in Rust:

from justapi import JustAPIApp, Schema

app = JustAPIApp()

class UserSchema(Schema):
    name: str
    age: int

# This runs entirely in Rust - no Python GIL!
@app.post("/users", schema=UserSchema, native=True)
async def create_user():
    return {"status": "created"}

Performance:

  • Python handler: ~60,000 RPS
  • Native fast path: ~700,000 RPS (12x faster)

Need Help?

  • Documentation: docs/
  • Examples: examples/
  • GitHub Issues: Report migration problems