Building Pure Python Web Apps with Reflex

0
7


Image by Author

 

When we talk about Python, we often think about using it to perform data analysis or build a machine learning model. It’s less common to discuss creating full web applications with Python outside of simple prototypes using libraries such as Streamlit or Taipy.

However, a library called Reflex offers web application development features that compete with those of other programming languages. Entirely in Python, this open-source library helps users build anything from small data science apps to large, multi-page websites. With strong flexibility yet intuitive Python code, we can easily scale web development to suit our needs with Reflex.

In this article, we will learn the basics of building a pure Python web application with Reflex.

 

Building Web Apps with Reflex

 
In this tutorial, we will review the standards for building a web application with Reflex. For best practices, it is advisable to use a virtual environment to avoid disrupting the overall environment.

With this in mind, we will begin developing our Reflex web application by installing the Reflex library using the code below:

 

We will then test Reflex by creating a new project and initiating a new application. Use the following code, but change the test_app folder name to your own.

mkdir test_app
cd test_app
reflex init

 

The code above prompts you with questions about whether you want to create the project with a pre-made template or not.

 
Building Pure Python Web Apps with Reflex
 

For this tutorial, select the blank Reflex app, and you will see the new project structure created, like the one below.

 
Building Pure Python Web Apps with Reflex
 

Run the following command to see if your Reflex application runs properly:

 

Visit the local URL serving the application. If it works well, you will see something like the image below:

 
Building Pure Python Web Apps with Reflex
 

This is the basic web application scaffold generated by Reflex. We will build something more sophisticated later, but we’ll start with the fundamentals.

Let’s start by understanding the components used to build the web application in the Reflex library. First, open

test_app.py and replace its contents with the following code:

import reflex as rx

class State(rx.State):
    count: int = 0

    def increment(self):
        self.count += 1

    def decrement(self):
        self.count -= 1

def index():
    return rx.hstack(
        rx.button(
            "Decrement",
            color_scheme="ruby",
            on_click=State.decrement,
        ),
        rx.heading(State.count, font_size="2em"),
        rx.button(
            "Increment",
            color_scheme="grass",
            on_click=State.increment,
        ),
        spacing="4",
    )

app = rx.App()
app.add_page(index)

 

This will show a website like the one below.

 
Building Pure Python Web Apps with Reflex
 

Let’s break down what’s happening in the code above.

First, we define the state, which contains variables (called vars) and functions (called event handlers) that can change the state of the application.

For example, we define a single variable called count that holds an integer with an initial value of 0.

class State(rx.State):
    count: int = 0

 

Then we have event handlers—functions within the state that modify variables in response to user actions. In the code above, we define the event handlers as follows:

def increment(self):
    self.count += 1

def decrement(self):
    self.count -= 1

 
Next, we define the web application UI as follows:

def index():
    return rx.hstack(
        rx.button(
            "Decrement",
            color_scheme="ruby",
            on_click=State.decrement,
        ),
        rx.heading(State.count, font_size="2em"),
        rx.button(
            "Increment",
            color_scheme="grass",
            on_click=State.increment,
        ),
        spacing="4",
    )

 

The functions above define the web application interface and use the following components to build the UI:

  • rx.hstack: used to stack elements horizontally
  • rx.button: used to show a button that triggers an event when clicked
  • rx.heading: used to show text in various sizes

As you can see in the code above, the heading component references the count variable in the state, and each button triggers a function in the state when clicked.

There are many more components you can use to build the web application; see the Reflex components documentation.

Lastly, we define the application and add the components to the base route with the following code:

app = rx.App()
app.add_page(index)

 

That is a simple explanation of the important components that Reflex uses to build a web application.

With the explanation above done, let’s build a slightly more advanced web application with Reflex. In the example below, we will develop a to-do list application that we can fill and remove items from.

import uuid
import reflex as rx
from typing import Any, Dict, List

class TodoState(rx.State):
    todos: List[Dict[str, Any]] = []
    new_text: str = ""
    current_filter: str = "all"   # Select between "all", "active", "done"

    # Derived values (computed from state)
    @rx.var
    def items_left(self) -> int:
        return sum(1 for t in self.todos if not t["done"])

    @rx.var
    def items_left_label(self) -> str:
        return "1 item left" if self.items_left == 1 else f"{self.items_left} items left"

    @rx.var
    def filtered_todos(self) -> List[Dict[str, Any]]:
        if self.current_filter == "active":
            return [t for t in self.todos if not t["done"]]
        if self.current_filter == "done":
            return [t for t in self.todos if t["done"]]
        return self.todos

    # Events (mutate state)
    @rx.event
    def set_new_text(self, value: str):
        self.new_text = (value or "").strip()

    @rx.event
    def add_todo(self):
        text = (self.new_text or "").strip()
        if not text:
            return
        self.todos.append({"id": str(uuid.uuid4()), "text": text, "done": False})
        self.new_text = ""

    @rx.event
    def toggle(self, todo_id: str):
        for t in self.todos:
            if t["id"] == todo_id:
                t["done"] = not t["done"]
                break

    @rx.event
    def remove(self, todo_id: str):
        self.todos = [t for t in self.todos if t["id"] != todo_id]

    @rx.event
    def clear_completed(self):
        self.todos = [t for t in self.todos if not t["done"]]

    @rx.event
    def set_filter(self, name: str):
        if name in {"all", "active", "done"}:
            self.current_filter = name

def filter_button(name: str, label: str) -> rx.Component:
    return rx.button(
        label,
        size="2",
        variant=rx.cond(TodoState.current_filter == name, "solid", "soft"),
        background_color=rx.cond(
            TodoState.current_filter == name, "blue.600", "gray.700"
        ),
        color="white",
        _hover={"background_color": "blue.500"},
        on_click=lambda: TodoState.set_filter(name),
    )

def render_todo_item(todo: rx.Var[dict]) -> rx.Component:
    return rx.hstack(
        rx.checkbox(
            is_checked=todo["done"],
            on_change=lambda _: TodoState.toggle(todo["id"]),
            size="2",
            color_scheme="blue",
        ),
        rx.text(
            todo["text"],
            flex="1",
            color=rx.cond(todo["done"], "gray.500", "white"),
            text_decoration=rx.cond(todo["done"], "line-through", "none"),
        ),
        rx.icon_button(
            "trash",
            color_scheme="red",
            variant="soft",
            on_click=lambda: TodoState.remove(todo["id"]),
        ),
        align="center",
        spacing="3",
        width="100%",
    )

def todo_input_bar() -> rx.Component:
    return rx.hstack(
        rx.input(
            placeholder="What needs to be done?",
            value=TodoState.new_text,
            on_change=TodoState.set_new_text,
            flex="1",
            size="3",
            background_color="gray.800",
            color="white",
            border_color="gray.600",
            _placeholder={"color": "gray.400"},
        ),
        rx.button(
            "Add",
            size="3",
            background_color="blue.600",
            color="white",
            _hover={"background_color": "blue.500"},
            on_click=TodoState.add_todo,
        ),
        spacing="3",
        width="100%",
    )

def todo_list_panel() -> rx.Component:
    return rx.vstack(
        rx.foreach(TodoState.filtered_todos, render_todo_item),
        spacing="2",
        width="100%",
    )

def footer_bar() -> rx.Component:
    return rx.hstack(
        rx.text(TodoState.items_left_label, size="2", color="gray.300"),
        rx.hstack(
            filter_button("all", "All"),
            filter_button("active", "Active"),
            filter_button("done", "Done"),
            spacing="2",
        ),
        rx.button(
            "Clear Completed",
            variant="soft",
            background_color="gray.700",
            color="white",
            _hover={"background_color": "gray.600"},
            on_click=TodoState.clear_completed,
        ),
        justify="between",
        align="center",
        width="100%",
    )

def index() -> rx.Component:
    return rx.center(
        rx.card(
            rx.vstack(
                rx.heading("Reflex To-Do", size="6", color="white"),
                todo_input_bar(),
                rx.separator(border_color="gray.700"),
                todo_list_panel(),
                rx.separator(margin_y="2", border_color="gray.700"),
                footer_bar(),
                width="min(720px, 92vw)",
                spacing="4",
            ),
            size="4",
            width="min(760px, 96vw)",
            shadow="lg",
            background_color="gray.900",
        ),
        min_h="100vh",
        padding_y="8",
        background_color="black",
    )

app = rx.App()
app.add_page(index, route="https://www.kdnuggets.com/", title="Reflex To-Do")

 

The result of the application will look like the image below.

 
Building Pure Python Web Apps with Reflex
 

In the code above, the flow essentially works as follows:

  1. The app keeps a small memory: your tasks, what you’re typing, and which filter is selected.
  2. You type in the box and that text is stored as you type.
  3. You press “Add” and the task is saved (with an id) and the box clears.
  4. The list instantly refreshes to show what’s in memory.
  5. Each task row has a checkbox and a trash icon. Checking toggles completion; the trash removes the task.
  6. The three filter buttons (All / Active / Done) change which tasks are visible.
  7. The footer shows how many tasks are not done and lets you “Clear Completed”.

A few important distinctions—beyond the basic components covered earlier—include:

  1. Decorate with @rx.event to declare events within the state.
  2. Decorate with @rx.var to create derived variables in the state.
  3. Use rx.Component signatures when building reusable UI helpers for your Reflex application.

That is the basic explanation and example of how Reflex works. Try it yourself and build the web application you need with pure Python.

 

Conclusion

 
Reflex is an open-source library that allows us to build web applications in pure Python with a simple yet intuitive code pattern. Its straightforward setup and easy-to-understand code allow users to keep the logic and UI in one place. It’s a useful library for newcomers and professional developers alike who want to build an application with Python.

I hope this has helped!
 
 

Cornellius Yudha Wijaya is a data science assistant manager and data writer. While working full-time at Allianz Indonesia, he loves to share Python and data tips via social media and writing media. Cornellius writes on a variety of AI and machine learning topics.