The Good Tech Companies - Designing a Pure Python Web Framework
Episode Date: September 23, 2024This story was originally published on HackerNoon at: https://hackernoon.com/designing-a-pure-python-web-framework. A look at how Reflex, the open-source framework empow...ering Python developers to build web apps faster, works under the hood. Check more stories related to programming at: https://hackernoon.com/c/programming. You can also check exclusive content about #python, #web-app-development, #reactjs, #python-framework, #web-framework, #python-framework-reflex, #hackernoon-top-story, #good-company, and more. This story was written by: @reflexdev. Learn more about this writer by checking @reflexdev's about page, and for more stories, please visit hackernoon.com. Reflex is the open-source framework empowering Python developers to build web apps faster. Build both your frontend and backend in a single language, Python (pip install reflex), with no JavaScript or web development experience required.
Transcript
Discussion (0)
This audio is presented by Hacker Noon, where anyone can learn anything about any technology.
Designing a pure Python web framework by Reflex Dev.
Web development is one of the most popular use cases for programming.
Python is one of the most popular programming languages in the world.
So why can't we build web apps in Python?
Making a UI should be simple, but even you have great engineers on your team,
the overhead of learning a new language and tools was a huge barrier.
Often making a UI could be harder than the actual work one is doing.
We built Reflex, an open-source Python web framework to solve this exact problem.
T L D R
Under the hood, Reflex apps compile down to a React front-end app and a fast API back-end app.
Only the UI is compiled to
JavaScript. All the app logic and state management stays in Python and is run on the server.
Reflex uses WebSockets to send events from the front-end to the back-end,
and to send state updates from the back-end to the front-end.
Existing Python solutions. There were a few ways already to build apps in Python,
but none of them fit our
needs. On the one hand, there are frameworks like Django and Flask that are great for building
production-grade web apps. But they only handle the backend. You still need to use JavaScript
and a frontend framework, as well as writing a lot of boilerplate code to connect the frontend
and backend. On the other hand, pure Python libraries like Dash and Streamlit can be great
for small projects, but they are limited to a specific use case and don't have the features
and performance to build a full web app. As your app grows in features and complexity,
you may find yourself hitting the limits of the framework, at which point you either have to
limit your idea to fit the framework or scrap your project and rebuild it using a real web framework.
We want to bridge this gap
by creating a framework that is easy and intuitive to get started with, while remaining flexible and
powerful to support any app. Goals of Reflex. Pure Python. Use one language for everything.
Easy to get started. Build your ideas easily without needing web development experience.
Full flexibility. Web apps should match the customizability
and performance of traditional web frameworks.
Batteries included.
Handle the full stack from the front end
to the back end to deployment.
Now let's dive into how we built Reflex to meet these goals.
The Reflex architecture.
Full stack web apps are made up of a front end
and a back end.
The front-end is
theser interface and is served as a web page that runs on the user's browser. The back-end handles
the logic and state management, such as databases and APIs, and is run on a server. In traditional
web development, these are usually two separate apps and are often written in different frameworks
or languages. For example, you may combine a Flask backend with a React frontend.
With this approach, you have to maintain two separate apps and end up writing a lot of
boilerplate code to connect the frontend and backend. We want to simplify this process in
Reflex by defining both the frontend and backend in a single codebase, while using Python for
everything. Developers should only worry about their app's
logic and not about the low-level implementation details. Front-end. We want Reflex apps to look
and feel like a traditional web app to the end user, while still being easy to build and maintain
for the developer. To do this, we built on top of mature and popular web technologies.
When you your app, Reflex compiles the frontend down to a single
page next. JS app and serves it on a port, by default, that you can access in your browser.
The frontend's job is to reflect the app's state and send events to the backend when the user
interacts with the UI. No actual logic is run on the frontend. Components Reflex frontends are
built using components that can be composed together to create complex UIs. Instead of using a templating language that mixes HTML and Python,
we just use Python functions to define the UI. Under the hood, components compile down to React
components. Many of our core components are based on Radix, a popular React component library.
We also have a many other components for graphing, data tables, and more.
We chose React because it is a popular library with a huge ecosystem.
Our goal isn't to recreate the web ecosystem, but to make it accessible to Python developers.
This also lets our users bring their own components if we don't have a component they need.
Users can wrap their own React components and then publish them for others to use. Over time we will build out our third-party
component ecosystem so that users can easily find and use components that others have built.
Styling we wanted to make sure Reflex apps look good out of the box, while still giving developers
full control over the appearance of their app. We have a core theming system that lets you set high-level styling options such as dark
mode and accent color throughout your app to give it a unified look and feel.
Beyond this, Reflex components can be styled using the full power of CSS.
We leverage the Emotion library to allow styling, so you can pass any CSS prop
as a keyword argument to a component.
This includes responsive props by passing a list of values. Backend. In Reflex only the frontend
compiles to JavaScript and runs on the user browser, while all the state and logic stays
in Python and is run on the server. When you, we start a FastAPI server, by default on port,
that the frontend connects to through a websocket.
All the state and logic are defined within a class. The state is made up of vars and event
handlers. Vars are any values in your app that can change over time. They are defined as class
attributes on your class, and may be any Python type that can be serialized to JSON. Event handlers
are methods in your class that are called when the user interacts
with the UI. They are the only way that we can modify the vars in Reflex and can be called in
response to user actions, such as clicking a button or typing in a text box. Since event
handlers are run on the backend, you can use any Python library within them. Event processing.
Normally when writing web apps, you have to write a lot of boilerplate code to connect
the frontend and backend. With Reflex, you don't have to worry about that. We handle
the communication between the frontend and backend for you. Developers just have to write
their event handler logic, and when the VARs are updated the UI is automatically updated.
Event triggers the user can interact with the UI in many ways, such as clicking a button, typing in a text box, or hovering over an element.
In Reflex, we call the Cvent triggers.
Event K-U-E-O-N the front-end, we maintain an event queue of all pending events.
An event consists of three major pieces of data.
Client token.
Each client has a unique token to identify it.
This lets the backend know which state to update.
Event handler. The event handler to run on the state.
Arguments. The arguments to pass to the event handler.
When an event is triggered, it is added to the queue.
We have a flag to make sure only one event is processed at a time.
This ensures that the state is always consistent and there
aren't any race conditions with two event handlers modifying the state at the same time.
There are exceptions to this, such as background events which allow you to run events in the
background without blocking the UI. Once the event is ready to be processed, it is sent to the back
end through a web socket connection. State manager once the event is received, it is processed on
the backend. Reflex uses a state manager which maintains a mapping between client tokens and
their state. By default, the state manager is just an in-memory dictionary, but it can be extended
to use a database or cache. In production we use Redis as our state manager. Event handling once
we have the user's state, the next step is to run
the event handler with the arguments. State up dates every time an event handler returns,
or yields. We save the state in the state manager and send the state updates to the frontend to
update the UI. To maintain performance as your state grows, internally reflex keeps track of
vars that were updated during the event handler, Dirty VARs.
When the event handler is done processing, we find all the Dirty VARs and create a state update to send to the frontend. We store the new state in our state manager and then send the state
update to the frontend. The frontend then updates the UI to reflect the new state.
Conclusion. I hope this provides a good overview of how Reflex works under the hood.
We will have more posts coming out to share how we made Reflex scalable and
performant through features such as state sharding and compiler optimizations.
Thank you for listening to this Hackernoon story, read by Artificial Intelligence.
Visit hackernoon.com to read, write, learn and publish.