Concurrency and Async / Await
JustAPI supports both regular functions (def) and asynchronous functions (async def) as request handlers. This page explains the difference and when to use each.
Synchronous vs Asynchronous
Section titled “Synchronous vs Asynchronous”from justapi import JustAPIApp
app = JustAPIApp()
# Synchronous handler (def)@app.get("/sync")def sync_handler(request): return {"message": "Synchronous"}
# Asynchronous handler (async def)@app.get("/async")async def async_handler(request): return {"message": "Asynchronous"}Both work identically from the user’s perspective. The difference is internal.
def — Synchronous
Section titled “def — Synchronous”JustAPI runs synchronous handlers in a dedicated thread pool so they don’t block the Rust event loop. This is handled automatically by the framework.
Use def when your handler does CPU-bound work or calls blocking libraries:
import time
@app.get("/cpu-work")def cpu_work(request): # Simulate CPU-bound work result = sum(i * i for i in range(10_000)) return {"result": result}async def — Asynchronous
Section titled “async def — Asynchronous”Async handlers run directly on the Rust async runtime. They can await other async operations without blocking.
Use async def when your handler does I/O-bound work (network calls, database queries, file I/O):
import httpx
@app.get("/fetch")async def fetch_data(request): async with httpx.AsyncClient() as client: response = await client.get("https://api.example.com/data") return response.json()When to Use Which
Section titled “When to Use Which”| Scenario | Use | Why |
|---|---|---|
| Calling a database ORM (sqlx, SQLAlchemy async) | async def |
Avoids blocking the runtime |
| HTTP calls to external APIs | async def |
Non-blocking I/O |
| Simple data transformation | def |
Cleaner code, no async overhead |
| CPU-bound computation | def |
Thread pool handles it |
| Mixed I/O + computation | async def |
Better concurrency |
| Using blocking libraries | def |
Thread pool prevents blocking |
JustAPI’s Concurrency Model
Section titled “JustAPI’s Concurrency Model”JustAPI uses a Rust async runtime (tokio) for all request handling. The Python handler boundary is designed to avoid GIL contention:
async defhandlers run on the tokio async runtime directly.defhandlers run in a dedicated Python worker thread pool (GIL pool).- The native fast path (
native=True) bypasses Python entirely — no GIL, no threads.
This means you get true concurrency for async handlers even on standard CPython, because the GIL is released during Python handler execution.
Async Database Access
Section titled “Async Database Access”import sqlite3from justapi import JustAPIApp
app = JustAPIApp()
@app.get("/users")async def list_users(request): # Use an async database client import aiosqlite async with aiosqlite.connect("users.db") as db: async with db.execute("SELECT * FROM users") as cursor: users = await cursor.fetchall() return {"users": users}Parallel Async Operations
Section titled “Parallel Async Operations”Async handlers can run multiple I/O operations concurrently:
import httpx
@app.get("/aggregate")async def aggregate(request): async with httpx.AsyncClient() as client: # Run both requests concurrently users_task = client.get("https://api.example.com/users") items_task = client.get("https://api.example.com/items")
users_response = await users_task items_response = await items_task
return { "users": users_response.json(), "items": items_response.json(), }Summary
Section titled “Summary”- Use
deffor synchronous, blocking, or CPU-bound work. - Use
async deffor I/O-bound work that benefits from concurrency. - JustAPI handles both correctly — you don’t need to worry about blocking the runtime.
- For maximum throughput, consider the native fast path (
native=True) which bypasses Python entirely.
See Also
Section titled “See Also”- First Steps — your first JustAPI app
- Python Types Intro — type hints in JustAPI
- Background Tasks — post-response async work