Build custom web apps easily and quickly
Meet NiceGUI, a simple Python-based UI framework that works smoothly with your web browser or as a desktop app. Whether you’re making small web apps, dashboards, or playing with robotics projects, NiceGUI makes it easy with its easy interface and many features.
The goal of this post is to convince you to try it out by listing the pros and cons of this library by showing you how you can build and deploy a NiceGUI app. (This is not a sponsored post, I just like the library 🙃)
Streamlit vs. NiceGUI: Why Switch?
While Streamlit is good for making interactive apps, it can be tricky to handle events and states, especially for bigger projects. NiceGUI is different. It lets you control states and interactions directly, without needing extra steps or hacky workarounds.
Simple State Management
NiceGUI makes managing states easy. Unlike Streamlit, which can reset states unexpectedly, NiceGUI keeps things steady, whether it’s the starting state or changes made by users. You can use callbacks to handle user interactions in event-based manner without getting annoyed by a full page refresh and losing state data.
Lots of Features
NiceGUI has many cool features:
- Buttons, switches, sliders, inputs, and more for interaction.
- Easy ways to arrange things on the screen.
- Charts, tables, and even 3D scenes for visuals.
- Integration with data visualization libraries like Matplotlib or Plotly.
- Customize colors and styles easily.
- Tools to help with coding and testing.
- Main devs are always available to answer questions and are very receptive to feedback on their GitHub space.
- Build on top of popular frameworks: FastAPI, Vue3, Tailwind, Quasar.
- Their whole site is made with the NiceGUI library: https://nicegui.io/documentation
Limitations
While NiceGUI is great, it’s worth noting that its smaller community size might be a little limiting. It also has a slightly longer learning curve compared to more popular frameworks like Streamlit. It’s preferable to get familiar with CSS and Tailwind CSS to make the most of the library’s features. Also, knowledge of FastAPI, Vue and Quasar can provide you with greater flexibility and extend what you can implement.
Hands-On
Now, lets explore some features of NiceGUI and then build and deploy a demo app.
Basic app
First install NiceGUI:
pip install nicegui[highcharts]
Lets start from an example from the main documentation:
# https://nicegui.io/documentation/section_data_elements
from nicegui import ui
from random import randomchart = ui.highchart({
'title': False,
'chart': {'type': 'bar'},
'xAxis': {'categories': ['A', 'B']},
'series': [
{'name': 'Alpha', 'data': [0.1, 0.2]},
{'name': 'Beta', 'data': [0.3, 0.4]},
],
}).classes('w-full h-64')
def update():
chart.options['series'][0]['data'][0] = random()
chart.update()
ui.button('Update', on_click=update)
ui.run()
Here, the UI module is what will allow you to create a UI element.
In this example, first we create a highchart element, we assign to it the tailwind classes w-full and h-64. w-full will make it use the whole screen horizontally in a response manner and h-64 specifies the height.
When we click on the button, a callback function is triggered. This callback will update the data used for the chart and then re-renders it in a fluid manner.
You can also change the callback to add new bars:
def update():
chart.options["xAxis"]["categories"].append(random.choice(string.ascii_uppercase))
for series in chart.options['series']:
series["data"].append(random.random())
chart.update()
Also, notice that refreshing the page does not make you lose your data! You can’t do that with some other Python UI libraries. The reason why it works that way here is that data is shared among all users, but there are lots of ways to keep data user-specific like the app.storage.user object or app.storage.browser (when wrapped around a @ui.page
decorator).
But, what if you want to update the UI on a recurrent timer? easy ! Just change the button element to ui.timer
ui.timer(5, callback=lambda: (update(), ui.notify("Data Updated")))
Now, let us build a demo app that lets users pick a category then allows them to generate a random Chuck Norris Fact.
First, here is the main code:
import requests # Importing the requests library to make HTTP requests
from nicegui import ui # Importing UI components from the NiceGUI library
from nicegui_app.header import add_head_html # Importing a function to add HTML head content# List of categories for Chuck Norris facts
CATEGORIES = [
"animal",
"career",
"celebrity",
"dev",
"fashion",
"food",
"money",
"movie",
"music",
"science",
"sport",
"travel",
]
# Class to handle Chuck Norris facts
class Fact:
def __init__(self):
self.fact = None # Initialize the fact attribute to None
# Method to update the fact based on a given category
def update_fact(self, category):
url = f"https://api.chucknorris.io/jokes/random?category={category}" # URL to Chuck Norris API
for i in range(10): # Try up to 10 times to fetch a valid fact
result = requests.get(url) # Make a GET request to the Chuck Norris API
if result.status_code == 200: # If the request is successful
result_json = result.json() # Parse the JSON response
if self.fact != result_json["value"]: # If the fetched fact is different from the current one
self.fact = result_json["value"] # Update the fact attribute
break # Exit the loop
# Function to generate the Chuck Norris fact UI
def chuck():
add_head_html() # Add HTML head content for the NiceGUI app
default_value = CATEGORIES[0] # Default category for Chuck Norris facts
fact = Fact() # Create an instance of the Fact class
fact.update_fact(default_value) # Update the fact using the default category
# Create a grid layout with 12 columns
with ui.grid(columns=12).classes("w-full"):
# Column for category selection
with ui.column().classes("col-span-4 sm:col-span-2 space-x-0"):
ui.label("Pick a fact category:") # Display a label for category selection
# Radio button group for selecting categories
category = ui.radio(
CATEGORIES,
value=default_value,
on_change=lambda _: fact.update_fact(category.value), # Update the fact when the category changes
).classes("w-full")
# Button to regenerate the fact for the selected category
ui.button(
"⟳ Re-Generate", on_click=lambda _: fact.update_fact(category.value)
)
# Column for displaying the Chuck Norris fact
with ui.column().classes(
"flex col-span-8 sm:col-span-10 w-full justify-center mx-auto max-w-screen-md"
):
# Label to display the Chuck Norris fact, bound to the fact attribute of the Fact instance
ui.label().bind_text_from(fact, "fact").classes(
"text-lg sm:text-3xl text-gray-800 bg-gray-100 rounded-lg shadow-lg p-6"
)
Now let us go through it step by step:
First, we make the necessary imports and define the possible categories.
Then, we define the class that will store and update our random fact:
class Fact:
def __init__(self):
self.fact = None # Initialize the fact attribute to None# Method to update the fact based on a given category
def update_fact(self, category):
url = f"https://api.chucknorris.io/jokes/random?category={category}" # URL to Chuck Norris API
for i in range(10): # Try up to 10 times to fetch a valid fact
result = requests.get(url) # Make a GET request to the Chuck Norris API
if result.status_code == 200: # If the request is successful
result_json = result.json() # Parse the JSON response
if self.fact != result_json["value"]: # If the fetched fact is different from the current one
self.fact = result_json["value"] # Update the fact attribute
break # Exit the loop
This class stores the fact in the attribute “fact” and has a method update_fact that calls the Chuck Norris facts api. https://api.chucknorris.io
Next, we define our page in the “chuck” function. NiceGUI adopts a modular approach that lets you define your app over multiple modules and python files.
We define an instance of our data class fact = Fact()
This is a specific instance to each user. Next, we init the fact using the update_fact method.
Now, we start defining our UI elements.
We define a grid with two columns:
- A first column that has our category options and generate button. This one has the following tailwind classes: col-span-4 sm:col-span-2. It means that for very small screens it will use up 4/12 of the screen, otherwise it will use up 2/12. This makes the design work in mobile phones too.
- A second column where we will display the fact.
For the first column:
- A radio menu ui.radio.
- A button to generate a random fact.
Both elements, when clicked or changed will use a callback that calls fact.update_fact
For the second column:
- We have a ui.label that binds its value to
fact.fact
so each time this variable changes, it will update the display automatically.
The label has the following tailwind classes: text-lg sm:text-3xl This makes it so the text is smaller on small screens.
This gives you the following app:
Neat! right?
Deployment
Deploying such app is easy! Using CloudRun for example. You just have to create a Dockerfile and then run the following gcloud instructions:
PROJECT_ID=$(gcloud config get-value project)
REPO="demo"
LOCATION="europe-west1"
IMAGE="nicegui_app"
SERVICE_NAME="nicegui-app"
VERSION="0.0.1"
GAR_TAG=$LOCATION-docker.pkg.dev/$PROJECT_ID/$REPO/$IMAGE:$VERSION# Create repository
gcloud artifacts repositories create $REPO --repository-format=docker
--location=$LOCATION --description="Docker repository"
--project=$PROJECT_ID || true # If fails because already exist then its fine
# Build image
gcloud builds submit --tag $GAR_TAG
# Deploy Cloud run
gcloud run deploy $SERVICE_NAME --image=$GAR_TAG --max-instances=1 --min-instances=0 --port=8080
--allow-unauthenticated --region=europe-west1 --memory=0.5Gi --cpu=1 -q --no-cpu-throttling --session-affinity
This builds the docker image using cloud build and then deploys it to CloudRun.
The only key options here are: “ — no-cpu-throttling — session-affinity” This allows the same user to be routed the same container when possible and keeps the CPU alive between requests. You can try it out here: https://nicegui-app-dfmj3maizq-ew.a.run.app/
In Conclusion
NiceGUI is a great choice if you want to make user interfaces quickly and easily with Python. It will help you build powerful python apps where you retain full control of the internal state and that you can test and deploy easily. Hopefully, it can allow you to express your creativity in your data science projects without being limited by tools.
What was shown here is just a small fraction of what you can do with NiceGUI. You can learn more by following the links below.
Resources:
- Chuck Norris (very exaggerated) Facts API: https://api.chucknorris.io/
- NiceGUI documentation: https://nicegui.io/documentation
- NiceGUI examples: https://github.com/zauberzeug/nicegui/tree/main/examples
- Code for this post: https://github.com/CVxTz/nicegui_tutorial
- Demo app: https://nicegui-app-dfmj3maizq-ew.a.run.app/