I recently interviewed with Anvil, and despite not getting the role, I found what they do to be really cool. As a Python developer, I’ve hit the same wall countless times: I can wrangle data, build ML models, automate workflows, but the moment someone says “put a web UI on that,” I’m stuck learning React, wrestling with JavaScript tooling, and coordinating between frontend and backend.
Anvil takes a contrarian approach: what if you could write your entire web application including the client-side code in Python? No JavaScript required. But here’s the technical puzzle: browsers only understand JavaScript. So how does this actually work?
Architecture Overview
Anvil’s stack consists of four main layers that work together to enable full-stack Python development:
Client Layer: Python code that runs in the browser, compiled to JavaScript via Skulpt
Server Layer: Standard Python with full ecosystem access, running on Anvil’s servers
Communication: RPC over WebSockets connecting client and server seamlessly
Database: Built-in PostgreSQL (Data Tables) or connect to external databases
There’s also an optional fifth component called the Uplink, which lets you connect local Python scripts or Jupyter notebooks to your web app (more on this later).
The key innovation here is a unified language across the entire stack. No context switching between Python and JavaScript, no REST API boilerplate, no separate frontend and backend codebases.
The Technical Magic: Client-Side Python
Here’s the fundamental problem: web browsers only execute JavaScript. For decades, the solution has been clear: learn JavaScript frameworks, coordinate between frontend and backend teams, manage two entirely different codebases.
Anvil’s solution relies on Skulpt, a JavaScript implementation of Python that runs in the browser. When you write Python code for the client-side, it gets compiled to JavaScript at runtime. This means your Python code is actually executing in the browser, handling events, manipulating the DOM, and updating the UI.
Here’s what this looks like in practice:
# This Python code runs IN THE BROWSER
from anvil import *
class MyForm(MyFormTemplate):
def button_click(self, **event_args):
# Client-side event handling in Python
alert("Hello from the browser!")
# Call server seamlessly (looks like a regular function call)
result = anvil.server.call('process_data', self.text_box.text)
self.label.text = result
This is genuinely running as Python in your browser. The button\_click
method handles a UI event, displays an alert, calls a server function, and updates a label, all without writing a single line of JavaScript.
Benefits:
Familiar Python syntax and patterns with no mental context switching
Seamless client-server communication where RPC calls look like regular Python functions
Unified codebase with one language and one set of conventions
Rapid development without frontend/backend coordination overhead
The crucial insight is understanding where code actually runs. UI event handlers execute in the browser (Python via Skulpt), but heavy data processing, machine learning, and pandas operations run on the server in native Python. Database queries happen server-side. The architecture naturally guides you toward the right separation of concerns.
Real Code Example
Let’s build a mini data dashboard that demonstrates how everything fits together:
# === SERVER MODULE (server.py) ===
import pandas as pd
import plotly.express as px
import anvil.server
@anvil.server.callable
def analyze_sales(uploaded_file):
# Full Python ecosystem available here
df = pd.read_csv(uploaded_file)
df = df.groupby('category')['revenue'].sum().reset_index()
fig = px.bar(df, x='category', y='revenue',
title='Sales by Category')
return fig
@anvil.server.callable
def save_to_database(data):
# Built-in database access
app_tables.sales.add_row(
category=data['category'],
amount=data['amount'],
date=datetime.now()
)
return "Saved successfully"
# === CLIENT MODULE (Form1.py) ===
from anvil import *
import anvil.server
class Form1(Form1Template):
def upload_change(self, file, **event_args):
# This runs in the browser
self.progress.visible = True
# Call server function - seamless RPC
chart = anvil.server.call('analyze_sales', file)
self.plot.figure = chart
self.progress.visible = False
def save_button_click(self, **event_args):
data = {
'category': self.dropdown.selected_value,
'amount': self.text_box.text
}
result = anvil.server.call('save_to_database', data)
alert(result)
What’s happening here:
File upload is handled client-side (Python in browser via Skulpt)
Heavy processing with pandas happens server-side with full Python capabilities
RPC calls look like regular Python function calls (no HTTP requests to construct)
No JSON serialization code (Python objects are serialized automatically)
Security is built-in: server functions decorated with
@anvil.server.callable
form your API boundary
Notice there’s no REST API definition, no endpoint configuration, no fetch calls with headers and error handling. The anvil.server.call()
function handles all of that transparently.
Integration & Ecosystem
What Works Where
Server-side, you have access to the full Python ecosystem. Install any pip package: pandas, scikit-learn, requests, TensorFlow. This is native Python execution.
Client-side is more limited. Plotly works for visualizations, and you have access to a subset of the standard library, but compute-heavy libraries like NumPy won’t run in the browser. This limitation actually makes sense architecturally, heavy computation belongs on the server anyway.
JavaScript libraries can be wrapped and called from Python using Anvil’s JavaScript integration features. You’re not completely cut off from the JavaScript ecosystem when you need it.
Anvil includes built-in integrations for common services: Stripe for payments, Google and Microsoft for authentication and APIs, and email sending. These work out of the box with minimal configuration.
The Uplink: The Secret Weapon
The Uplink is perhaps Anvil’s most unique feature. It lets you connect existing Python code running anywhere; your laptop, a Jupyter notebook, a GPU server to your web application.
# Your local Jupyter notebook or script
import anvil.server
anvil.server.connect("YOUR_UPLINK_KEY")
@anvil.server.callable
def run_ml_model(data):
# Use your local GPU, existing code, trained models
return model.predict(data)
Your web app calls this like any server function, but it executes on your machine. This is brilliant for connecting data science workflows to web interfaces without rewriting everything. Your Jupyter notebook becomes a backend service.
When to Use This
Choose Anvil when:
Your team is Python-heavy but frontend-light
Building internal tools, dashboards, or admin panels
Rapidly prototyping data applications
Connecting Jupyter notebooks or data science code to web UIs
You value development speed over pixel-perfect custom UI
Creating CRUD applications where Python’s full ecosystem matters more than complex client-side interactions
The Bottom Line
Anvil makes a specific technical bet: for certain types of applications, the productivity gains from using a unified language outweigh the performance overhead and ecosystem constraints of client-side Python.
This isn’t about replacing React or Vue. It’s about offering an alternative path for Python developers who view web UIs as a necessary interface layer rather than the core of what they’re building. When your value is in data processing, business logic, or machine learning models, Anvil lets you wrap those capabilities in a web interface without learning an entirely new stack.
The platform is part of a broader trend of Python expanding beyond its traditional backend role.
Is this the future of web development? Probably not. But for specific use cases (particularly in data science, internal tooling, and rapid prototyping) it’s a genuinely productive alternative that more Python developers should know about.
This article may not be perfect and I would appreciate any feedback!
Buy me a coffee: https://buymeacoffee.com/daisyauma
Top comments (0)