Running Panel apps in FastAPI#

Panel generally runs on the Bokeh server, which itself runs on Tornado. However, it is also often useful to embed a Panel app in an existing web application, such as a FastAPI web server.

Since Panel 1.5.0 it is possible to run Panel application(s) natively on a FastAPI based server. Therefore this how-to guide will explain how to add Panel application(s) directly to an existing FastAPI application. This functionality is new and experimental so we also provide the original how-to guide to embed a Tornado based Panel server application inside a FastAPI application.

By the end of this guide, youโ€™ll be able to run a FastAPI application that serves a simple interactive Panel app. The Panel app will consist of a slider widget that dynamically updates a string of stars (โญ) based on the sliderโ€™s value.

Setup#

Following FastAPIโ€™s Tutorial - User Guide make sure you first have FastAPI and [bokeh-fastapi] installed using:

pip install panel[fastapi]
conda install -c conda-forge bokeh-fastapi

Create a FastAPI application#

Start by creating a FastAPI application. In this application, we will define a root endpoint that returns a simple JSON response. Open your text editor or IDE and create a file named main.py:

from fastapi import FastAPI

# Initialize FastAPI application
app = FastAPI()

@app.get("/")
async def read_root():
    return {"Hello": "World"}

Create a Panel Application#

Next we will define a simple Panel application that allows you to control the number of displayed stars with an integer slider and decorate it with the add_panel_app decorator:

import panel as pn

from panel.io.fastapi import add_panel_app

@add_panel_app('/panel', app=app, title='My Panel App')
def create_panel_app():
    slider = pn.widgets.IntSlider(name='Slider', start=0, end=10, value=3)
    return slider.rx() * 'โญ'

Thatโ€™s it! This decorator will map a specific URL path to the Panel app, allowing it to be served as part of the FastAPI application.

The complete file should now look something like this:

import panel as pn

from fastapi import FastAPI
from panel.io.fastapi import add_application

app = FastAPI()

@app.get("/")
async def read_root():
    return {"Hello": "World"}

@add_application('/panel', app=app, title='My Panel App')
def create_panel_app():
    slider = pn.widgets.IntSlider(name='Slider', start=0, end=10, value=3)
    return slider.rx() * 'โญ'

Now run it with:

fastapi dev main.py

You should see the following output:

INFO     Using path main.py
INFO     Resolved absolute path /home/user/code/awesomeapp/main.py
INFO     Searching for package file structure from directories with __init__.py files
INFO     Importing from /home/user/code/awesomeapp/fast_api

 โ•ญโ”€ Python module file โ”€โ•ฎ
 โ”‚                      โ”‚
 โ”‚  ๐Ÿ main.py          โ”‚
 โ”‚                      โ”‚
 โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

INFO     Importing module main
/panel
INFO     Found importable FastAPI app

 โ•ญโ”€ Importable FastAPI app โ”€โ•ฎ
 โ”‚                          โ”‚
 โ”‚  from main import app    โ”‚
 โ”‚                          โ”‚
 โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

INFO     Using import string main:app

 โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ FastAPI CLI - Development mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
 โ”‚                                                     โ”‚
 โ”‚  Serving at: http://127.0.0.1:8000                  โ”‚
 โ”‚                                                     โ”‚
 โ”‚  API docs: http://127.0.0.1:8000/docs               โ”‚
 โ”‚                                                     โ”‚
 โ”‚  Running in development mode, for production use:   โ”‚
 โ”‚                                                     โ”‚
 โ”‚  fastapi run                                        โ”‚
 โ”‚                                                     โ”‚
 โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

INFO:     Will watch for changes in these directories: ['/home/user/code/awesomeapp/']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [39089] using WatchFiles
INFO:     Started server process [39128]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

If you visit http://127.0.0.1:8000 you will see the Panel application.

Adding multiple applications#

The add_application decorator is useful when server an application defined in a function, if you want to serve multiple applications, whether they are existing Panel objects, functions, or paths to Panel application scripts you can use the add_applications function instead, e.g.:

import panel as pn

from fastapi import FastAPI
from panel.io.fastapi import add_applications

app = FastAPI()

@app.get("/")
async def read_root():
    return {"Hello": "World"}

def create_panel_app():
    slider = pn.widgets.IntSlider(name='Slider', start=0, end=10, value=3)
    return slider.rx() * 'โญ'

add_applications({
    "/panel_app1": create_panel_app,
    "/panel_app2": pn.Column('I am a Panel object!'),
    "/panel_app3": "my_panel_app.py"
}, app=app)

Tips & Tricks#

Running Behind a Proxy#

In some cases, you might be running your FastAPI app behind a reverse proxy, which adds an extra path prefix that your application doesnโ€™t directly handle. This is common when working in environments like JupyterHub or deploying to Kubernetes.

For instance, if your FastAPI / endpoint is accessed at https://some.domain/some/path/, you will need to specify the path prefix when starting your FastAPI server. To do this, use the flag --root-path /some/path/. This ensures you can access the OpenAPI docs at https://some.domain/some/path/docs.

For more details, refer to the Behind a Proxy guide.

Conclusion#

Thatโ€™s it! You now have embedded panel in FastAPI! You can now build off of this to create your own web app tailored to your needs.