Skip to content

Getting Started with Tornado

Tornado is a Python web framework and asynchronous networking library originally developed by FriendFeed and later open-sourced by Facebook. It does not rely on WSGI; instead, it processes requests directly using non-blocking I/O and an event loop. A single Tornado process can handle thousands of concurrent connections, making it especially suited for scenarios that require persistent connections such as long polling and WebSocket.

This article introduces Tornado’s installation and core concepts, and walks through routing configuration, debug mode, and multi-process deployment via minimal runnable examples.

Installation

pip install tornado

Tornado 6.x requires Python 3.8+. The official recommendation is to use native async/await coroutines in a Python 3.10+ environment.

Comparison with Django and Flask

DimensionDjangoFlaskTornado
PurposeFull-featured synchronous frameworkLightweight synchronous frameworkAsync framework + networking library
Concurrency modelMulti-process / multi-threadMulti-process / multi-threadAsync event loop
ORMBuilt-inNoneNone
Use casesTraditional web / admin backendsSmall services / prototypesHigh concurrency / persistent connections
Learning curveMediumLowMedium (requires understanding async)

Your First Tornado Application

import tornado.ioloop
import tornado.web

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, Tornado!")

def make_app():
    return tornado.web.Application([
        (r"/", IndexHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

Three core objects:

  • RequestHandler: Each route maps to a Handler class; each HTTP method corresponds to a same-named method (get, post, put, delete, etc.).
  • Application: The application itself, accepting a list of routes and global settings.
  • IOLoop: The event loop. IOLoop.current().start() blocks and runs until the process exits.

Route Configuration

Each entry in the route list is a tuple (pattern, handler) or constructed via tornado.web.url():

from tornado.web import Application, url

app = Application([
    url(r"/",          IndexHandler,  name="index"),
    url(r"/user/(\d+)", UserHandler,  name="user_detail"),
    url(r"/admin",     AdminHandler,  {"title": "Admin Panel"}, name="admin"),
])

The four parameters of url():

ParameterMeaning
patternRegular expression URL pattern
handlerThe Handler class for this route
kwargs (optional)Keyword argument dictionary passed to initialize()
name (optional)Route name, used for reverse URL resolution via reverse_url()

Global Settings

The second argument to Application accepts a settings dictionary:

settings = {
    "debug": True,                        # Debug mode
    "template_path": "templates",         # Template directory
    "static_path": "static",              # Static files directory
    "static_url_prefix": "/static/",      # Static files URL prefix
    "cookie_secret": "your-secret-key",   # Signed cookie secret key
    "login_url": "/login",                # Auth redirect URL
    "xsrf_cookies": True,                 # Enable XSRF protection
}

app = Application(handlers, **settings)

Debug Mode

When debug: True is enabled:

  • The server automatically restarts on code changes
  • Caught exceptions display full tracebacks in the browser
  • Never enable this in production

Command-Line Options

The tornado.options module provides command-line argument parsing without depending on argparse:

from tornado.options import define, options, parse_command_line

define("port", default=8888, type=int, help="Listen port")
define("debug", default=False, type=bool, help="Debug mode")

if __name__ == "__main__":
    parse_command_line()   # Parse sys.argv
    app = make_app()
    app.listen(options.port)
    tornado.ioloop.IOLoop.current().start()

Pass arguments at startup:

python app.py --port=9000 --debug=true

Multi-Process Mode

By default, app.listen() is single-process. To take full advantage of multi-core CPUs, use HTTPServer with server.start(n):

import tornado.httpserver

if __name__ == "__main__":
    app = make_app()
    server = tornado.httpserver.HTTPServer(app)
    server.bind(8888)
    server.start(0)   # 0 means start one subprocess per CPU core
    tornado.ioloop.IOLoop.current().start()

server.start(n) parameter meaning:

  • 0: Automatically equals the number of CPU cores on the current machine
  • 1: Single process (equivalent to app.listen())
  • n > 1: Manually specify the number of processes
In multi-process mode, server.start() must be called before IOLoop.current().start(), and it cannot be used on Windows (fork is not supported). For production environments, it is recommended to manage multiple Tornado processes using tools like Supervisor or Gunicorn instead.
Last updated on