Of course! Let's build a comprehensive guide to creating a REST API with Python and Sanic.

Sanic is a modern, fast, and simple Python web framework built on top of Python's asyncio. It's an excellent choice for building high-performance APIs because of its asynchronous nature, which allows it to handle many concurrent connections efficiently.
This guide will cover:
- Why Sanic? - The key advantages.
- Installation & Setup - Getting your environment ready.
- "Hello, World!" - Your first Sanic app.
- Building a RESTful API - The core concepts (routes, requests, responses).
- Advanced Features - URL parameters, request bodies, middleware, and static files.
- Project Structure - A scalable way to organize your application.
- Running with an ASGI Server - Production deployment.
Why Sanic?
- Asynchronous First: Sanic uses
async/awaitsyntax, making it non-blocking by default. This is perfect for I/O-bound tasks like database calls, network requests, or file operations, leading to high throughput. - Blazing Fast: It's one of the fastest Python web frameworks, often compared to Go and Node.js in benchmarks.
- Simple & Intuitive: The API is clean and easy to learn, especially if you're familiar with Flask or FastAPI.
- Built-in Features: Includes support for WebSockets, static file serving, and middleware right out of the box.
- Great Ecosystem: A growing number of extensions for databases (SQLAlchemy, TortoiseORM), authentication, and more.
Installation & Setup
First, you need to have Python 3.7 or newer installed.
Create a project directory and a virtual environment to keep your dependencies isolated.

# Create and navigate into your project directory mkdir my_sanic_api cd my_sanic_api # Create a virtual environment python -m venv venv # Activate the virtual environment # On macOS/Linux: source venv/bin/activate # On Windows: .\venv\Scripts\activate # Install Sanic pip install "sanic[ext]"
The [ext] extra is recommended as it includes common extensions you might need.
"Hello, World!" - Your First Sanic App
Create a file named app.py and add the following code:
# app.py
from sanic import Sanic
from sanic.response import json
# 1. Create an instance of the Sanic class
app = Sanic("MyHelloWorldApp")
# 2. Define a route using the @app.route decorator
# - The first argument is the URL path.
# - The second argument is the HTTP method (defaults to 'GET').
@app.get("/")
async def hello_world(request):
"""
This is an asynchronous function that handles the request.
'request' is a Sanic request object containing all info about the incoming request.
"""
# 3. Return a JSON response
return json({"message": "Hello, World!"})
# 4. Run the app
if __name__ == "__main__":
# The `host` and `port` arguments specify where the server will listen.
app.run(host="0.0.0.0", port=8000, debug=True)
To run your app:
python app.py
You will see output like this:
[2025-10-27 10:30:00 -0400] [12345] [INFO] Goin' Fast @ http://0.0.0.0:8000
[2025-10-27 10:30:00 -0400] [12345] [INFO] Stopping worker after 0.5 seconds
Now, open your browser or a tool like curl or Postman and go to http://127.0.0.1:8000. You should see the JSON response: {"message": "Hello, World!"}.
Building a RESTful API: A Todo List Example
Let's build a simple REST API for managing a to-do list. We'll use a simple Python list as our "database" for this example.
Our API will have the following endpoints:
| Method | Path | Description |
|---|---|---|
GET |
/todos |
Get all to-do items. |
POST |
/todos |
Create a new to-do item. |
GET |
/todos/<id> |
Get a single to-do item by ID. |
PUT |
/todos/<id> |
Update an existing to-do item. |
DELETE |
/todos/<id> |
Delete a to-do item. |
Update your app.py:
# app.py
from sanic import Sanic
from sanic.response import json, text
from sanic.exceptions import NotFound
# --- In-Memory "Database" ---
# In a real app, you would use a proper database.
todos = [
{"id": 1, "task": "Build an API with Sanic", "completed": False},
{"id": 2, "task": "Deploy the API", "completed": False},
]
next_todo_id = 3
# --- App Initialization ---
app = Sanic("TodoAPI")
# --- Helper Functions ---
def find_todo(todo_id):
"""Helper to find a todo by its ID."""
for todo in todos:
if todo["id"] == todo_id:
return todo
return None
# --- API Routes ---
# GET /todos - Get all todos
@app.get("/todos")
async def get_all_todos(request):
return json(todos)
# POST /todos - Create a new todo
@app.post("/todos")
async def create_todo(request):
global next_todo_id
try:
# 1. Get the JSON data from the request body
data = request.json
# 2. Basic validation
if not data or "task" not in data:
return json({"error": "Missing 'task' in request body"}, status=400)
# 3. Create the new todo item
new_todo = {
"id": next_todo_id,
"task": data["task"],
"completed": data.get("completed", False) # Default to False
}
# 4. Add to our "database"
todos.append(new_todo)
next_todo_id += 1
# 5. Return the created item with a 201 Created status
return json(new_todo, status=201)
except Exception:
return json({"error": "Invalid JSON"}, status=400)
# GET /todos/<id> - Get a single todo
@app.get("/todos/<todo_id:int>")
async def get_todo(request, todo_id):
todo = find_todo(todo_id)
if todo:
return json(todo)
# If not found, return a 404 error
raise NotFound(f"Todo with id {todo_id} not found")
# PUT /todos/<id> - Update a todo
@app.put("/todos/<todo_id:int>")
async def update_todo(request, todo_id):
todo = find_todo(todo_id)
if not todo:
raise NotFound(f"Todo with id {todo_id} not found")
data = request.json
if not data:
return json({"error": "Request body cannot be empty"}, status=400)
# Update fields if they are provided in the request
if "task" in data:
todo["task"] = data["task"]
if "completed" in data:
todo["completed"] = data["completed"]
return json(todo)
# DELETE /todos/<id> - Delete a todo
@app.delete("/todos/<todo_id:int>")
async def delete_todo(request, todo_id):
todo = find_todo(todo_id)
if not todo:
raise NotFound(f"Todo with id {todo_id} not found")
todos.remove(todo)
# A successful DELETE request often returns a 204 No Content status
return text("", status=204)
# --- Run the App ---
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True)
Key Concepts in this Example:
<todo_id:int>: This is a URL parameter. Sanic automatically converts it to an integer.request.json: Accesses the parsed JSON body of aPOSTorPUTrequest.json(data, status=code): A convenient way to return JSON responses with a specific HTTP status code.text("", status=204): Returns an empty body with a204 No Contentstatus, which is standard for successfulDELETEoperations.raise NotFound(...): Sanic has built-in exceptions. Raising them automatically generates the correct HTTP error response.
Advanced Features
Middleware
Middleware functions are executed on every request, either before it reaches your route handler (request middleware) or after the handler has processed it but before the response is sent (response middleware).
Here's a simple middleware to log all incoming requests.
# Add this to your app.py
@app.middleware("request")
async def log_request(request):
print(f"Processing request: {request.method} {request.path}")
@app.middleware("response")
async def custom_headers(request, response):
response.headers["Server"] = "MySuperCoolAPIServer"
Static Files
To serve static files (like CSS, JavaScript, or images), use the app.static method.
# Add this to your app.py
# Serve files from a 'static' directory in your project
app.static("/static", "./static")
# Now you can access a file at /static/style.css
# if you have a file named ./static/style.css
Project Structure
For any real application, you'll want to organize your code. Here's a common and scalable structure.
my_sanic_api/
├── venv/
├── app/
│ ├── __init__.py # Initializes the Sanic app and registers blueprints
│ ├── routes/
│ │ ├── __init__.py
│ │ └── todos.py # All todo-related routes
│ └── extensions.py # For things like database connections
├── config.py # Configuration settings
├── requirements.txt # Your project dependencies
└── run.py # The entry point to run the app
How it works:
-
app/routes/todos.py: Move your todo routes here.# app/routes/todos.py from sanic import Blueprint from sanic.response import json, text from sanic.exceptions import NotFound # Create a Blueprint for the todo routes # A Blueprint is a way to organize a group of related routes todos_bp = Blueprint("todos", url_prefix="/todos") # ... (paste your todo route handlers here, but change @app.get to @todos_bp.get) # ... (paste find_todo helper here too) -
app/__init__.py: Initialize the app and register the blueprint.# app/__init__.py from sanic import Sanic from .routes import todos def create_app(): app = Sanic("TodoAPI") # Register the blueprint app.blueprint(todos.todos_bp) return app -
run.py: The main script to run the app.# run.py from app import create_app app = create_app() if __name__ == "__main__": app.run(host="0.0.0.0", port=8000, debug=True)
This structure keeps your code modular and much easier to maintain as your API grows.
Running with an ASGI Server
The built-in app.run() is great for development, but it's not suitable for production. For production, you should use a production-grade ASGI (Asynchronous Server Gateway Interface) server like Uvicorn.
-
Install Uvicorn:
pip install uvicorn
-
Run your app with Uvicorn: If you're using the project structure from the previous step, you would run:
uvicorn run:app --host 0.0.0.0 --port 8000
run:apptells Uvicorn to look for an object namedappinside therun.pyfile.--host 0.0.0.0makes the server accessible from any IP address on your machine.--port 8000specifies the port.
You can also run it with multiple workers for better performance:
uvicorn run:app --host 0.0.0.0 --port 8000 --workers 4
Summary
You've now learned the fundamentals of building a REST API with Sanic. You've covered:
- Basic setup and "Hello, World".
- Creating routes (
GET,POST,PUT,DELETE). - Handling URL parameters and request bodies.
- Returning JSON and status codes.
- Using middleware and serving static files.
- Organizing your code with blueprints for scalability.
- Running your application in production with Uvicorn.
Sanic is a powerful and fun framework to work with, and its performance makes it a top contender for modern web development in Python. Happy coding
