Running Python Apps with Granian
Granian is an HTTP server for Python written in Rust. It sits between your Python code and the internet—receiving requests, passing them to your application for processing, and sending responses back. The Rust implementation handles the low-level networking parts, giving it speed and efficiency advantages over pure Python servers.
Granian supports three different ways to write your Python web application:
- ASGI — The Asynchronous Server Gateway Interface, used by FastAPI and Django
- RSGI — A Granian-specific async interface optimized for Rust
- WSGI — The traditional Web Server Gateway Interface, used by Flask and older Django versions
This means you can use Granian with whatever web framework you’re already comfortable with.
Installing Granian
Installation is straightforward if you have Python 3.10 or later (3.10+ recommended). Open your terminal and run:
pip install granian
Granian downloads and installs its Rust components automatically.
Optional extras are available for specific features:
# Enable automatic reloading when files change
pip install granian[reload]
# Use faster event loop on Linux
pip install granian[uvloop]
# Enable environment file support
pip install granian[dotenv]
You’ll need a Linux system with io_uring support for the best performance. Most modern Linux distributions have this enabled by default.
Running Your First Server
Create a file called main.py with this code:
# main.py
# This is an ASGI application - a standard way to write async web apps in Python
async def app(scope, receive, send):
"""Every ASGI app is a callable that receives three things."""
# scope contains information about the request (URL, headers, etc.)
assert scope['type'] == 'http'
# Send the response headers (status 200, content-type text/plain)
await send({
'type': 'http.response.start',
'status': 200,
'headers': [[b'content-type', b'text/plain']],
})
# Send the actual response body
await send({
'type': 'http.response.body',
'body': b'Hello from Granian!',
})
Now run it with Granian:
granian --interface asgi main:app
The --interface asgi part tells Granian you’re using ASGI format. This is important because Granian defaults to RSGI, which has a different callable signature. Open your browser to http://127.0.0.1:8000 and you should see “Hello from Granian!”.
The main:app part means “from the file main.py, import the thing called app”. This follows Python’s standard module:object notation.
Running an RSGI Application
RSGI is Granian’s native interface and offers slightly different performance characteristics. Here’s the same “Hello World” app written in RSGI:
# rsgi_app.py
async def app(scope, proto):
"""RSGI uses a different signature than ASGI."""
assert scope['type'] == 'http'
# proto provides methods to send responses
proto.response_str(
status=200,
headers=[('content-type', 'text/plain')],
body="Hello from RSGI!"
)
Run it with:
granian --interface rsgi rsgi_app:app
Notice you don’t need to specify --interface rsgi since that’s the default.
Using Granian with Web Frameworks
You don’t have to write raw ASGI applications. Most people use a framework like FastAPI or Starlette. Here’s how to run a FastAPI app with Granian:
First, install FastAPI if you haven’t:
pip install fastapi
Create a simple FastAPI app:
# app.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello from FastAPI on Granian!"}
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id, "name": f"Item {item_id}"}
Run it with Granian:
granian --interface asgi app:app
FastAPI handles all the ASGI complexity for you, so your code looks just like normal Python functions.
Understanding Granian’s CLI Options
Granian comes with many command-line options. Here are the ones you’ll use most often:
Choosing a Port and Host
By default, Granian listens on 127.0.0.1:8000. You can change this:
granian --interface asgi --host 0.0.0.0 --port 9000 main:app
Using 0.0.0.0 means your server is accessible from other computers on your network, not just localhost.
Running Multiple Workers
A worker is a separate process that handles requests. More workers can handle more traffic:
granian --interface asgi --workers 4 main:app
This runs 4 separate Python processes, each handling requests. If one crashes, the others keep running.
Enabling WebSockets
If your app uses WebSockets for real-time communication:
granian --interface asgi --ws main:app
WebSockets are enabled by default, but you can disable them with --no-ws if you don’t need them.
Access Logs
To see incoming requests in your terminal:
granian --interface asgi --access-log main:app
This prints each request with its status code and response time to stderr.
HTTPS Support
For production, you’ll often need HTTPS. Granian can handle this directly:
granian --interface asgi \
--ssl-certificate certificate.crt \
--ssl-keyfile private.key \
main:app
The key file must be in PKCS#8 format. If you have a different format, you’ll need to convert it.
Serving Static Files
Granian can serve static files directly without needing another server:
# Serve files from /var/www/static at /static URL path
granian --interface asgi \
--static-path-route /static \
--static-path-mount /var/www/static \
--static-path-expires 3600 \
main:app
--static-path-routesets the URL path (default:/static)--static-path-mountsets the directory to serve files from--static-path-expiressets cache expiry time in seconds (3600 = 1 hour)
This is useful for development or simple deployments. For production, consider using a dedicated static file server like nginx.
Unix Domain Sockets
For connecting to reverse proxies:
granian --interface asgi --uds /var/run/granian.sock main:app
This is common when deploying behind nginx or other reverse proxies.
Using Environment Variables
Instead of typing all options on the command line, you can use environment variables. Create a .env file:
export GRANIAN_HOST=0.0.0.0
export GRANIAN_PORT=8000
export GRANIAN_WORKERS=4
export GRANIAN_LOG_LEVEL=debug
Then run Granian with the --env-files option:
granian --interface asgi --env-files .env main:app
Note: Using --env-files requires the dotenv extra (pip install granian[dotenv]).
This is handy for keeping your deployment configuration separate from your code.
Programmatic Usage (Python API)
Sometimes you want to start Granian from within Python code, perhaps as part of a larger application or test setup:
from granian import Granian
def on_startup():
"""This runs when the server starts."""
print("Server is starting!")
def on_shutdown():
"""This runs when the server stops."""
print("Server is stopping!")
server = Granian(
target="main:app",
interface="asgi",
host="127.0.0.1",
port=8000,
workers=2,
)
server.on_startup(on_startup)
server.on_shutdown(on_shutdown)
server.serve()
This gives you more control over the server lifecycle.
Common Gotchas
There are a few things that trip up people new to Granian:
The Default Interface is RSGI
Unlike uvicorn or gunicorn, Granian defaults to RSGI (a Granian-specific async interface). Always specify --interface asgi if you’re using FastAPI, Django, or any ASGI framework:
# Wrong - will confuse your ASGI app
granian main:app
# Right - explicitly say you're using ASGI
granian --interface asgi main:app
WSGI and the GIL
If you use WSGI (for Flask), remember that all requests share the same Python process. Heavy computation in one request can slow down others. Consider using ASGI with an async framework for better performance.
Not Compatible with Every Library
Granian doesn’t work with trio or gevent. If a library specifically requires these, you won’t be able to use it with Granian. Most popular libraries work fine, but check compatibility if you’re using something niche.
ASGI Lifespan Not Fully Supported
The ASGI “lifespan” protocol (for startup and shutdown events) isn’t fully implemented yet. If your app relies heavily on lifespan events, you might encounter issues.
When to Use Granian
Granian is a good choice when:
- You’re already using ASGI frameworks like FastAPI or Starlette
- You want better performance than pure Python servers
- You need HTTP/2 support (Granian handles this natively)
- You’re comfortable on Linux and want to try something faster
It’s less ideal when:
- You need Windows support (Granian works best on Linux)
- You’re using WSGI frameworks and don’t need better performance
- You need extensive debugging tools (Granian is more minimal)
See Also
- dunder-init — Python’s
__init__method, useful for understanding app factory patterns - with-keyword — Context managers for resource handling in web applications
- dunder-new —
__new__method for custom class instantiation