CRUD with Database
This example builds a complete CRUD (Create, Read, Update, Delete) interface using Forms and the built-in Database. The user can list, search, add, edit, and delete records — all in a single reactive page.
This pattern uses a reactive page with custom buttons. Side effects (insert, update, delete) run only when the corresponding button is clicked — never on every keystroke.
Table setup
Create a table called employees in the Database with these columns:
| Column | Type |
|---|---|
id | uuid (default) |
name | text |
email | text |
department | text |
Full example
from abstra.forms import (
run,
TextInput,
DropdownInput,
PandasRowSelectionInput,
MarkdownOutput,
Button,
Row,
)
from abstra.tables import select_df, insert, update, delete, is_ilike
DEPARTMENTS = [
{"value": "engineering", "label": "Engineering"},
{"value": "sales", "label": "Sales"},
{"value": "marketing", "label": "Marketing"},
{"value": "hr", "label": "Human Resources"},
]
def employees_page(state):
# --- Handle button actions ---
if state.get("delete"):
selected = state.get("selected")
if selected:
delete("employees", {"id": selected["id"]})
state["selected"] = None
state["delete"] = False
if state.get("save_new"):
insert("employees", {
"name": state["new_name"],
"email": state["new_email"],
"department": state["new_department"],
})
state["adding"] = False
state["save_new"] = False
if state.get("save_edit"):
selected = state.get("selected")
if selected:
update("employees",
where={"id": selected["id"]},
set={
"name": state["edit_name"],
"email": state["edit_email"],
"department": state["edit_department"],
},
)
state["editing"] = False
state["selected"] = None
state["save_edit"] = False
if state.get("cancel"):
state["adding"] = False
state["editing"] = False
state["cancel"] = False
# --- Add form ---
if state.get("add"):
state["adding"] = True
state["add"] = False
if state.get("adding"):
return [
MarkdownOutput("## New employee"),
Row([
TextInput("Name", key="new_name"),
TextInput("Email", key="new_email"),
]),
DropdownInput("Department", options=DEPARTMENTS, key="new_department"),
], [
Button("Save", key="save_new"),
Button("Cancel", key="cancel"),
]
# --- Edit form ---
if state.get("edit"):
selected = state.get("selected")
if selected:
state["editing"] = True
state["edit_name"] = selected["name"]
state["edit_email"] = selected["email"]
state["edit_department"] = selected["department"]
state["edit"] = False
if state.get("editing"):
return [
MarkdownOutput("## Edit employee"),
Row([
TextInput("Name", key="edit_name"),
TextInput("Email", key="edit_email"),
]),
DropdownInput("Department", options=DEPARTMENTS, key="edit_department"),
], [
Button("Save", key="save_edit"),
Button("Cancel", key="cancel"),
]
# --- List view ---
search = state.get("search", "")
if search:
df = select_df("employees", where={
"name": is_ilike(f"%{search}%"),
})
else:
df = select_df("employees")
df = df[["id", "name", "email", "department"]]
has_selection = state.get("selected") is not None
return [
MarkdownOutput("## Employees"),
TextInput("Search by name", key="search", required=False),
PandasRowSelectionInput(df, key="selected", required=False, label=""),
], [
Button("Add", key="add"),
Button("Edit", key="edit", disabled=not has_selection),
Button("Delete", key="delete", disabled=not has_selection),
]
run([employees_page])
How it works
The entire CRUD lives in a single reactive page function. The flow is controlled by button keys stored in state:
-
List — The default view queries the database with
select_dfand displays the results in aPandasRowSelectionInput. A search field filters by name usingis_ilike. -
Add — Clicking "Add" sets
state["adding"] = True, which swaps the UI to a creation form. "Save" callsinsertand returns to the list. -
Edit — Clicking "Edit" pre-fills the form with the selected row's data. "Save" calls
updatewith the row'sid. -
Delete — Clicking "Delete" calls
deleteon the selected row and clears the selection.
Side effects (insert, update, delete) must only run inside button-handling branches — never in the main rendering path. See Step Types for details.