Reflex
Build full-stack web apps in pure Python
Reflex is an open-source framework for building and deploying full-stack web applications entirely in Python without writing JavaScript. The compiled apps can be self-hosted on your own infrastructure.
Key features
- Pure Python web apps
- No JavaScript needed
- Built-in components
- Self-hostable deploys
Pros & cons
Strengths
- Pure Python full stack
- No JavaScript needed
- Compiles to React
Trade-offs
- Node.js build dependency
- API still evolving
Reflex replaces
Last reviewed Aug 26, 2026 · 840 words
Reflex is the tool for the moment a Streamlit dashboard grows users. Streamlit reruns the script top to bottom on every click and shares nothing sensibly between sessions; Reflex compiles your Python into a React frontend and a FastAPI backend with per-session state, so a login, a multi-page admin tool or a form that remembers what you typed are normal work rather than workarounds. The cost is a build step with a JavaScript runtime in it and the discipline to keep state on the server, where Reflex puts it whether you like it or not.
What "pure Python" compiles to
You write components as Python function calls and state as a class:
import reflex as rx
class State(rx.State):
count: int = 0
def increment(self):
self.count += 1
def index():
return rx.vstack(rx.heading(State.count), rx.button("Add", on_click=State.increment))
app = rx.App()
app.add_page(index)
reflex init scaffolds a project, reflex run compiles the pages into a Next.js app under .web/ and starts two processes: the frontend on port 3000 and the backend on port 8000, joined by a WebSocket that carries every event and every state delta. The "Node.js build dependency" in the catalogue is real, though Reflex fetches its own JavaScript runtime on first run, so in practice it is a 200 MB download and a slow first compile rather than something you install by hand. Every element you see is a React component underneath; when the built-in set runs out, wrapping an npm package takes a short Python class rather than a rewrite.
Production is two processes, not one
For self-hosting, reflex export produces a static frontend bundle and a backend bundle, or you run reflex run --env prod and let it serve both. The pattern I use is static files from Caddy and the backend proxied on its event paths, which keeps the Python process off the public port:
app.example.com {
handle /_event* {
reverse_proxy localhost:8000
}
handle /_upload* {
reverse_proxy localhost:8000
}
handle {
root * /srv/reflex/frontend
try_files {path} {path}.html /index.html
file_server
}
}
Set api_url="https://app.example.com" in rxconfig.py before exporting, because the frontend bakes the backend address in at compile time, and a bundle built with the default localhost:8000 will render and then silently fail every click. The WebSocket upgrade must pass through the proxy; Caddy does it without configuration, Nginx needs the usual Upgrade headers. Budget 512 MB for the backend and a few hundred more during a compile.
State lives in RAM until you add Redis
Each browser session gets its own instance of your State class held in the backend's memory. That is what makes multi-user apps easy, and it has two consequences. A restart drops every session; users reload and start over, which is acceptable for an internal tool and not for a checkout. And a second backend worker knows nothing about the first's sessions, so the moment you scale past one process you set redis_url in rxconfig.py and state moves into Redis. Do that before you need it if the app is anything more than a toy; it is one line and it also survives restarts.
Where it fits against the other Python-app tools
Streamlit is the right answer when the only user is you and the app is a notebook with widgets; it is faster to write and needs no build. Gradio is the right answer for wrapping a model with an input box and a share link. Appsmith and Budibase win when the app is CRUD over an existing database and the builder would rather drag than type; the Retool alternatives page compares that group. Reflex is the answer when the person building it is a Python developer, the app has more than one screen and more than one user, and nobody on the team wants to maintain a separate React codebase. It is closer to "a web framework" than to "a dashboard tool", and it should be judged as one.
The API is still moving
Reflex is on 0.x releases and the component API changes between minor versions; the migration notes are good, but an upgrade after 6 months idle usually touches every page. Pin the version in requirements.txt, upgrade deliberately, and read the changelog for the component names that were renamed. This is the honest cost of a 4-year-old framework with 28,000 stars and a fast release cadence.
What I'd do
Prototype in Streamlit if the audience is one person, and reach for Reflex the day the second user asks for a login. Deploy it as static frontend plus one backend process behind Caddy, api_url set to the public host, Redis for state from the start, and a pinned Reflex version with a quarterly upgrade slot. Run like that, it is the shortest path I know from "Python script" to "internal web app my colleagues use without asking me how".
Compare Reflex
8 head-to-head comparisons.
Similar developer tools & git apps
Excalidraw
Developer Tools & GitVirtual hand-drawn style whiteboard
Replaces Miro
lazygit
Developer Tools & GitSimple terminal UI for Git commands
Replaces GitKraken, Sourcetree
Hoppscotch
Developer Tools & GitOpen-source API development ecosystem
Replaces Postman, Insomnia
json-server
Developer Tools & GitFull fake REST API from a JSON file in seconds
Replaces Mockoon, Postman Mock
Strapi
Developer Tools & GitLeading open-source headless CMS
Replaces Contentful
NocoDB
Developer Tools & GitOpen-source Airtable alternative
Replaces Airtable