Basics
Essential patterns for Tornado web framework development. Copy-paste ready code examples for app setup, handlers, and routing.
Installation & Setup
Install Tornado
# Install latest version
pip install tornado
# Install specific version
pip install tornado==6.4
# Install with conda
conda install tornado
# Install development version
pip install git+https://github.com/tornadoweb/tornado.git
Basic Project Structure
myapp/
├── app.py # Main application
├── handlers/ # Request handlers
│ ├── __init__.py
│ ├── main.py
│ └── api.py
├── templates/ # HTML templates
│ ├── base.html
│ └── index.html
├── static/ # Static files
│ ├── css/
│ ├── js/
│ └── img/
├── config.py # Configuration
└── requirements.txt # Dependencies
Basic Application Setup
Minimal Application
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, Tornado!")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
print("Server started on http://localhost:8888")
tornado.ioloop.IOLoop.current().start()
Application with Configuration
import tornado.ioloop
import tornado.web
import tornado.options
from tornado.options import define, options
# Define command line options
define("port", default=8888, help="run on the given port", type=int)
define("debug", default=False, help="run in debug mode")
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, Tornado!")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
], debug=options.debug)
if __name__ == "__main__":
tornado.options.parse_command_line()
app = make_app()
app.listen(options.port)
print(f"Server started on http://localhost:{options.port}")
tornado.ioloop.IOLoop.current().start()
Application with Settings
import tornado.ioloop
import tornado.web
import os
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, Tornado!")
def make_app():
settings = {
"debug": True,
"static_path": os.path.join(os.path.dirname(__file__), "static"),
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
"cookie_secret": "your-secret-key-here",
"xsrf_cookies": True,
"login_url": "/login",
"autoreload": True,
"compiled_template_cache": False,
"static_hash_cache": False,
"serve_traceback": True,
}
return tornado.web.Application([
(r"/", MainHandler),
], **settings)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Request Handlers
Basic Handler Class
import tornado.web
class BaseHandler(tornado.web.RequestHandler):
def get(self):
self.write("GET request")
def post(self):
self.write("POST request")
def put(self):
self.write("PUT request")
def delete(self):
self.write("DELETE request")
def head(self):
# HEAD requests don't return body
pass
def options(self):
self.write("OPTIONS request")
Handler with Arguments
class UserHandler(tornado.web.RequestHandler):
def get(self, user_id):
self.write(f"User ID: {user_id}")
def post(self, user_id):
# Handle POST for specific user
data = self.get_body_argument("data", default="")
self.write(f"Updated user {user_id} with data: {data}")
Handler with Query Parameters
class SearchHandler(tornado.web.RequestHandler):
def get(self):
query = self.get_argument("q", default="")
page = self.get_argument("page", default="1")
limit = self.get_argument("limit", default="10")
self.write({
"query": query,
"page": int(page),
"limit": int(limit)
})
Handler with Form Data
class FormHandler(tornado.web.RequestHandler):
def get(self):
self.render("form.html")
def post(self):
name = self.get_body_argument("name")
email = self.get_body_argument("email")
message = self.get_body_argument("message", default="")
# Process form data
self.write({
"status": "success",
"data": {
"name": name,
"email": email,
"message": message
}
})
Handler with JSON Data
import tornado.escape
import json
class APIHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Content-Type", "application/json")
def get(self):
data = {"message": "Hello, API!"}
self.write(json.dumps(data))
def post(self):
try:
json_data = tornado.escape.json_decode(self.request.body)
# Process JSON data
response = {
"status": "success",
"received": json_data
}
self.write(json.dumps(response))
except ValueError:
self.set_status(400)
self.write(json.dumps({"error": "Invalid JSON"}))
URL Routing
Basic URL Patterns
import tornado.web
app = tornado.web.Application([
# Static URLs
(r"/", MainHandler),
(r"/about", AboutHandler),
(r"/contact", ContactHandler),
# URLs with parameters
(r"/user/([0-9]+)", UserHandler),
(r"/post/([^/]+)", PostHandler),
# Optional parameters
(r"/search/?([^/]*)", SearchHandler),
# Multiple parameters
(r"/category/([^/]+)/post/([0-9]+)", CategoryPostHandler),
])
Named Groups in URLs
import tornado.web
class UserProfileHandler(tornado.web.RequestHandler):
def get(self, user_id):
self.write(f"User Profile: {user_id}")
class PostHandler(tornado.web.RequestHandler):
def get(self, category, post_id):
self.write(f"Category: {category}, Post ID: {post_id}")
app = tornado.web.Application([
(r"/user/(?P<user_id>[0-9]+)", UserProfileHandler),
(r"/(?P<category>[^/]+)/post/(?P<post_id>[0-9]+)", PostHandler),
])
URL Routing with Regex
app = tornado.web.Application([
# Match specific extensions
(r"/files/(.+\.(jpg|jpeg|png|gif))", ImageHandler),
# Match API versions
(r"/api/v([1-9])/users", APIUserHandler),
# Match optional trailing slash
(r"/page/?", PageHandler),
# Match slugs (letters, numbers, hyphens)
(r"/blog/([a-zA-Z0-9-]+)", BlogPostHandler),
])
Static File Serving
import tornado.web
import os
settings = {
"static_path": os.path.join(os.path.dirname(__file__), "static"),
"static_url_prefix": "/static/",
}
app = tornado.web.Application([
(r"/", MainHandler),
# Serve static files manually
(r"/static/(.*)", tornado.web.StaticFileHandler,
{"path": settings["static_path"]}),
# Serve uploaded files
(r"/uploads/(.*)", tornado.web.StaticFileHandler,
{"path": "/path/to/uploads"}),
# Serve with custom headers
(r"/downloads/(.*)", tornado.web.StaticFileHandler,
{"path": "/path/to/downloads", "default_filename": "index.html"}),
], **settings)
Application Configuration
Environment-based Configuration
import os
import tornado.web
class Config:
DEBUG = os.environ.get("DEBUG", "False").lower() == "true"
PORT = int(os.environ.get("PORT", "8888"))
SECRET_KEY = os.environ.get("SECRET_KEY", "your-secret-key")
DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///app.db")
def make_app():
settings = {
"debug": Config.DEBUG,
"cookie_secret": Config.SECRET_KEY,
"xsrf_cookies": True,
"static_path": os.path.join(os.path.dirname(__file__), "static"),
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
}
return tornado.web.Application([
(r"/", MainHandler),
], **settings)
if __name__ == "__main__":
app = make_app()
app.listen(Config.PORT)
print(f"Server running on port {Config.PORT}")
tornado.ioloop.IOLoop.current().start()
Configuration with YAML
import yaml
import tornado.web
def load_config(config_file="config.yaml"):
with open(config_file, 'r') as f:
return yaml.safe_load(f)
def make_app():
config = load_config()
settings = {
"debug": config.get("debug", False),
"cookie_secret": config.get("secret_key"),
"static_path": config.get("static_path", "static"),
"template_path": config.get("template_path", "templates"),
}
return tornado.web.Application([
(r"/", MainHandler),
], **settings)
Configuration Class
import tornado.web
import os
class AppConfig:
def __init__(self):
self.debug = os.environ.get("DEBUG", "False").lower() == "true"
self.port = int(os.environ.get("PORT", "8888"))
self.secret_key = os.environ.get("SECRET_KEY", "dev-secret-key")
self.static_path = os.path.join(os.path.dirname(__file__), "static")
self.template_path = os.path.join(os.path.dirname(__file__), "templates")
self.database_url = os.environ.get("DATABASE_URL", "sqlite:///app.db")
def to_dict(self):
return {
"debug": self.debug,
"cookie_secret": self.secret_key,
"static_path": self.static_path,
"template_path": self.template_path,
"xsrf_cookies": True,
"login_url": "/login",
}
def make_app():
config = AppConfig()
return tornado.web.Application([
(r"/", MainHandler),
], **config.to_dict())
Application Lifecycle
Startup and Shutdown
import tornado.ioloop
import tornado.web
import signal
import logging
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
]
settings = {"debug": True}
super().__init__(handlers, **settings)
# Initialize application
self.setup_logging()
self.connect_database()
def setup_logging(self):
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
def connect_database(self):
# Initialize database connection
pass
def cleanup(self):
# Cleanup resources
logging.info("Shutting down application")
def signal_handler(signum, frame):
logging.info(f"Received signal {signum}")
tornado.ioloop.IOLoop.current().stop()
if __name__ == "__main__":
# Setup signal handlers
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
app = Application()
app.listen(8888)
logging.info("Starting server on port 8888")
try:
tornado.ioloop.IOLoop.current().start()
except KeyboardInterrupt:
pass
finally:
app.cleanup()
Application Factory Pattern
import tornado.web
import tornado.ioloop
from handlers import MainHandler, UserHandler, APIHandler
def create_app(debug=False):
"""Application factory function"""
handlers = [
(r"/", MainHandler),
(r"/user/([0-9]+)", UserHandler),
(r"/api/.*", APIHandler),
]
settings = {
"debug": debug,
"cookie_secret": "your-secret-key",
"xsrf_cookies": True,
"static_path": "static",
"template_path": "templates",
}
app = tornado.web.Application(handlers, **settings)
return app
def main():
app = create_app(debug=True)
app.listen(8888)
print("Server started on http://localhost:8888")
tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
main()
Error Handling
Custom Error Handler
import tornado.web
import logging
class BaseHandler(tornado.web.RequestHandler):
def write_error(self, status_code, **kwargs):
if status_code == 404:
self.render("404.html")
elif status_code == 500:
self.render("500.html")
else:
self.write(f"Error {status_code}")
def log_exception(self, typ, value, tb):
logging.error(f"Exception: {typ.__name__}: {value}", exc_info=(typ, value, tb))
class MainHandler(BaseHandler):
def get(self):
# This will trigger a 404 if not found
self.write("Hello, World!")
Global Error Handler
import tornado.web
import tornado.ioloop
import logging
class ErrorHandler(tornado.web.RequestHandler):
def prepare(self):
self.set_status(404)
self.render("404.html")
def make_app():
settings = {
"debug": False,
"default_handler_class": ErrorHandler,
"template_path": "templates",
}
return tornado.web.Application([
(r"/", MainHandler),
], **settings)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Development Tools
Debug Mode
import tornado.web
import tornado.autoreload
import tornado.ioloop
def make_app():
settings = {
"debug": True, # Enable debug mode
"autoreload": True, # Auto-reload on file changes
"compiled_template_cache": False, # Disable template caching
"static_hash_cache": False, # Disable static file caching
"serve_traceback": True, # Show tracebacks in browser
}
app = tornado.web.Application([
(r"/", MainHandler),
], **settings)
return app
if __name__ == "__main__":
app = make_app()
app.listen(8888)
# Watch additional files for changes
tornado.autoreload.watch("config.py")
tornado.autoreload.watch("handlers/")
tornado.ioloop.IOLoop.current().start()
Logging Configuration
import logging
import tornado.web
import tornado.log
# Configure logging
logging.getLogger().setLevel(logging.INFO)
tornado.log.enable_pretty_logging()
# Custom logger
logger = logging.getLogger(__name__)
class MainHandler(tornado.web.RequestHandler):
def get(self):
logger.info("Handling GET request")
self.write("Hello, World!")
def post(self):
logger.info("Handling POST request")
self.write("POST received")
This covers the fundamental patterns for getting started with Tornado. The framework's power comes from its asynchronous capabilities and WebSocket support, which are covered in the advanced sections.