Skip to content

Building servers with Sanic

Image_of_Sanic Got to go fast!

Install Sanic

To install Sanic, run the following command:

pip install sanic

What is Sanic?

Sanic is a micro web framework that is designed to be fast and easy to use. It is both a framework and a web server. Sanic comes with everything that you need to write, deploy, and scale a production grade web application.

Getting Started with Sanic

To teach you the basics of working with Sanic, we are going to build a simple web application that greets the world. Copy the following into a file called server.py.

Simple web app

from sanic import Sanic, response

app = Sanic("hello-world-app")

@app.get("/")
async def hello(request):
    return response.text("Hello, world!")

It is important to note that the request object is always the first argument of your handler. Also, you are always required to state the response type, this is done at the expense of ease for the benefit of a faster response.

Now to run the server, we simply run the following command:

sanic server.app

The output that you get will look something like this:

[2022-05-27 09:39:13 -0400] [15506] [INFO]
  ┌─────────────────────────────────────────────────────────────────┐
  │                          Sanic v22.3.2                          │
  │                 Goin' Fast @ http://0.0.0.0:8000                │
  ├───────────────────────┬─────────────────────────────────────────┤
  │                       │     mode: debug, single worker          │
  │     ▄███ █████ ██     │   server: sanic                         │
  │    ██                 │   python: 3.9.12                        │
  │     ▀███████ ███▄     │ platform: macOS-10.16-x86_64-i386-64bit │
  │                 ██    │ packages: sanic-routing==22.3.0         │
  │    ████ ████████▀     │                                         │
  │                       │                                         │
  │ Build Fast. Run Fast. │                                         │
  └───────────────────────┴─────────────────────────────────────────┘

[2022-05-27 09:39:13 -0400] [15506] [DEBUG] Dispatching signal: server.init.before
[2022-05-27 09:39:13 -0400] [15506] [DEBUG] Dispatching signal: server.init.after
[2022-05-27 09:39:13 -0400] [15506] [INFO] Starting worker [15506]

Our server is now open at http://localhost:8000/, to view it we can either use curl or open the browser and navigate there to see the output.

Using curl to view the output

curl http://localhost:8000/

sanic-helloworld

Adding POST functionality

If we wanted to add a second handler to enable users to post data to our simple server. You might implement something similar to this:

Simple web app with post

from sanic import Sanic
from sanic.response import text

app = Sanic("hello-world-app")

name = "Dave"


@app.get("/")
async def hello(request):
    return text(f"Hello, {name}!\n")


@app.route("/", methods=["POST"])
async def hello_post(request):
    name = request.json['name']
    return text("Success\n")

Let's then check what happens.

% curl http://localhost:8000
Hello, Dave!

% curl http://localhost:8000/ -d '{"name": "Tim"}'
Success

% curl http://localhost:8000
Hello, Dave!

You might be confused to see that the name that is greeted hasn't changed! This is because all of the handlers are initialised with variables when the server is first launched and so our GET at http://localhost:8000/ never looks outside its handler to see if anything has changed.

The solution to this problem is to store variables that could change in our app's context: app.ctx.name = "...". If you make the changes below and then re-run the curl statements, you should see that the app now changes the name it greets.

Simple web app - POST fixed

from sanic import Sanic
from sanic.response import text, HTTPResponse
from sanic.request import Request

app = Sanic("hello-world-app")

app.ctx.name = "Dave"


@app.get("/")
async def hello(request: Request) -> HTTPResponse:
    return text(f"Hello, {app.ctx.name}!\n")


@app.route("/", methods=["POST"])
async def hello_post(request: Request) -> HTTPResponse:
    app.ctx.name = request.json['name']
    return text("Success\n")

Annotating your code

Because it is important to type annotated your code. Here is a copy of the simple web app with type hinting:

Simple web app with type hinting

from sanic import Sanic
from sanic.response import text, HTTPResponse
from sanic.request import Request

app = Sanic("hello-world-app")

@app.get("/")
async def hello(request: Request) -> HTTPResponse:
    return text(f"Hello, world!")

Testing Sanic applications

Sanic Testing is a testing client for Sanic, it primarily powers the tests of the Sanic project itself. It can be installed running:

Install Sanic Testing

pip install sanic-testing

Once sanic-testing is in the environment there is nothing you need to do to use it.

Sync test

Simply access the test_client property on your application instance:

import pytest
from sanic import Sanic, response


@pytest.fixture
def app():
    sanic_app = Sanic("TestSanic")

    @sanic_app.get("/")
    def basic(request):
        return response.text("foo")

    return sanic_app

def test_basic_test_client(app):
    request, response = app.test_client.get("/")

    assert request.method.lower() == "get"
    assert response.body == b"foo"
    assert response.status == 200

Async test

The async test client in pytest needs to be installed:

Install async test client

pip install pytest-asyncio

Async tests require accessing the asgi_client property on your application instance.

import pytest
from sanic import Sanic, response

@pytest.fixture
def app():
    sanic_app = Sanic(__name__)

    @sanic_app.get("/")
    def basic(request):
        return response.text("foo")

    return sanic_app

@pytest.mark.asyncio
async def test_basic_asgi_client(app):
    request, response = await app.asgi_client.get("/")

    assert request.method.lower() == "get"
    assert response.body == b"foo"
    assert response.status == 200

Further reading

For further information on Sanic, please refer to the documentation.