← Back to list

Lilota, a lightweight solution for long running task

Modern APIs often need to perform work that takes seconds or even minutes to complete. Examples are

Tobias Rössler · 2026-06-23 07:46 · 0 claps · 2.8 min read
#lilota #background-task #celery
Open on Medium ↗
Wiki topics: 🏃 · Running & Endurance

Lilota, a lightweight solution for long running task

Modern APIs often need to perform work that takes seconds or even minutes to complete. Examples are

  • generating reports
  • processing uploaded files
  • importing data
  • sending emails

Blocking the HTTP request until the work finishes creates a poor user experience and can cause timeouts.

This is exactly where lilota shines. It allows you to schedule long-running tasks and immediately return control to the client while the task runs in the background.

In this tutorial, you’ll learn how to integrate lilota into a FastAPI application and expose endpoints for:

  1. Creating a background task
  2. Scheduling a report
  3. Checking task status
  4. Retrieving task results

In this example, we do not actually create a report. We simply simulate the process to show how to schedule such tasks using lilota.

Installation

Install FastAPI and lilota using uv:

uv add "fastapi[standard]" lilota

Project structure

A simple project might look like this:

app/
├── main.py
├── tasks.py
└── models.py

Define input and output models

Let’s create a task that generates a report.

models.py

from dataclasses import dataclass

@dataclass
class ReportInput:
   customer_id: int

@dataclass
class ReportOutput:
  filename: str

The input model contains the information required to generate the report. The output model represents the result (here a filename) that will be stored when the task finishes.

Create a lilota instance

tasks.py

from lilota.worker import LilotaWorker

worker = LilotaWorker(
  db_url="sqlite:///tasks.db"
)

Lilota stores all information in a database. In the db_url a connection string is specified (here to a SQLite database named tasks.db). It is also possible and recommended for larger projects to specify a connection string to connect to a PostgreSQL database or any other database that is supported by SQLAlchemy.

Register a background task

Now register the function that performs the work.

tasks.py

import time
from models import ReportInput, ReportOutput

@worker.task
def generate_report(data: ReportInput) -> ReportOutput:
  # Simulate a long-running operation
  time.sleep(10)

  # Return the output
  return ReportOutput(
    filename = f"report-{data.customer_id}.pdf"
  )

def main():
  worker.start()

if __name__ == "__main__":
  main()

In a real application, this function could

  • send emails
  • query a database
  • generate a PDF
  • upload files to cloud storage
  • run expensive calculations

Create the FastAPI application

main.py

from contextlib import asynccontextmanager
from fastapi import FastAPI
from lilota.core import Lilota
from models import ReportInput
from uuid import UUID

Start lilota during application startup

lilota = Lilota(
    db_url="sqlite:///tasks.db",
    script_path="tasks.py"
)

@asynccontextmanager
async def lifespan(app: FastAPI):
  lilota.start()
  yield
  lilota.stop()

app = FastAPI(lifespan=lifespan)

This ensures the scheduler and worker are started when FastAPI starts and shut down cleanly when the application exits.

Endpoint: Create a report

Add an endpoint that schedules the report generation.

@app.post("/reports")
def create_report(data: ReportInput):
  task_id = lilota.schedule("generate_report", data)
  return {
    "task_id": task_id
  }

After the endpoint has been executed, the report generation continues in the background.

Endpoint: Check Task Status

Clients need a way to determine whether the task is still running.

@app.get("/tasks/{task_id}")
def get_task(task_id: UUID):
  task = lilota.get_task_by_id(task_id)
  return {
    "id": task.id,
    "status": task.status
  }

Endpoint: Retrieve Results

Once the task is finished, the generated output is returned.

@app.get("/tasks/{task_id}/result")
def get_result(task_id: str):
  task = lilota.get_task(task_id)

  return {
    "status": task.status,
    "result": task.output,
  }

Run the example

Start the FastAPI application

uvicorn main:app --reload

Create a report:

curl -X 'POST' \
    'http://127.0.0.1:8000/reports' \
    -H 'accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
    "customer_id": 42
}'

Response:

{
  "task_id": "b26fb0c8-9299-4e36-9ac3-0914c6141fea"
}

Retrieve task information:

curl -X 'GET' \
    'http://127.0.0.1:8000/tasks/b26fb0c8-9299-4e36-9ac3-0914c6141fea' \
    -H 'accept: application/json'

Response:

{
  "id": "b26fb0c8-9299-4e36-9ac3-0914c6141fea",
  "status": "completed"
}

Return the generated output:

curl -X 'GET' \
  'http://127.0.0.1:8000/tasks/b26fb0c8-9299-4e36-9ac3-0914c6141fea/result' \
  -H 'accept: application/json'

Response:

{
  "status": "completed",
  "result": {
    "filename": "report-42.pdf"
  }
}

Why Use lilota Instead of FastAPI BackgroundTasks?

FastAPI includes BackgroundTasks, which is great for very small workloads.

However, BackgroundTasks:

  • Run inside the application process
  • Do not persist state
  • Cannot easily survive restarts
  • Provide no built-in task tracking

Lilota adds:

  • Persistent task storage
  • Task status tracking
  • Progress reporting
  • Result storage
  • Dedicated workers

while remaining much simpler than introducing Celery, RabbitMQ, or Redis.

Conclusion

Lilota integrates naturally with FastAPI:

  • Start lilota during application startup
  • Register task functions
  • Schedule tasks from API endpoints
  • Return task IDs immediately
  • Expose endpoints for status and results

With only a small amount of code, you gain a reliable background-job system that is lightweight, persistent, and easy to operate.

Links

You can find the complete example on GitHub: https://github.com/tobiasroessler/lilota-fastapi

The full lilota documentation is available here: https://tobiasroessler.github.io/lilota/


메타데이터
post_id
40d43e9ed96a
slug
lilota-a-lightweight-solution-for-long-running-task-40d43e9ed96a
url
https://medium.com/@tobiasroessler/lilota-a-lightweight-solution-for-long-running-task-40d43e9ed96a
canonical_url
https://medium.com/@tobiasroessler/lilota-a-lightweight-solution-for-long-running-task-40d43e9ed96a
author_url
https://medium.com/@tobiasroessler
status
ok
fetched_at
2026-07-09 20:10:33