Async Server Actions
Call Async Python functions directly from your browser. Caspian eliminates manual API endpoints, leveraging FastAPI's high-performance ASGI engine.
How it Works
RPCs (Remote Procedure Calls) bridge the gap between your backend logic
and frontend UI. By decorating an
async def
function with
@rpc, it becomes callable from JavaScript using
pp.rpc().
Secure by Default
Automatic CSRF protection, Origin validation, and integrated Authentication checks via Starlette middleware.
Non-Blocking I/O
Native
async/await support
allows your server to handle thousands of concurrent requests
efficiently.
Type Safe
Python handles the logic with Pydantic validation; PulsePoint handles the reactivity.
Defining Actions (Backend)
In your route's index.py, import the
decorator and define your logic using
async def.
from casp.rpc import rpc from casp.validate import Validate from casp.prisma.db import prisma # 1. Simple Async RPC @rpc() async def create_todo(title): # Validations are easy if Validate.with_rules(title, "required|min:3") is not True: raise ValueError("Title must be at least 3 chars") # Non-blocking database call new_todo = await prisma.todo.create(data={ 'title': title, 'completed': False }) return new_todo.to_dict() # 2. Secured RPC (Auto 401/403) @rpc(require_auth=True) async def delete_todo(id, _current_user_id=None): await prisma.todo.delete(where={'id': id}) return {"success": True}
Calling Actions (Frontend)
Use
pp.rpc(name, data)
inside your scripts. It returns a Promise that resolves to the Python
return value.
<form onsubmit="add(event)"> <input name="title" required /> <button>Add</button> </form>
const [todos, setTodos] = pp.state([]); async function add(e) { e.preventDefault(); // 1. Collect Data const formData = new FormData(e.target); const data = Object.fromEntries(formData); // 2. Call Async Python Function const newTodo = await pp.rpc("create_todo", data); // 3. Update State setTodos([newTodo, ...todos]); e.target.reset(); }
Full Example: Todo List
Here is the complete implementation of a CRUD Todo list. Notice how the
logic uses
async/await for
database operations.
from casp.rpc import rpc from casp.prisma.db import prisma from casp.page import load_page import json @rpc() async def toggle_todo(id, completed): updated = await prisma.todo.update( where={'id': id}, data={'completed': completed} ) return updated.to_dict() @rpc() async def create_todo(title): return (await prisma.todo.create(data={ 'title': title, 'completed': False })).to_dict() @rpc() async def delete_todo(id): await prisma.todo.delete(where={'id': id}) return True async def page(): # Initial Server Side Rendering (Async) todos = await prisma.todo.find_many(order={'createdAt': 'desc'}) html = load_page(__file__) return html.replace( '[[todos]]', json.dumps([t.to_dict() for t in todos]) )
<!-- Loop over state --> <ul class="space-y-4"> <template pp-for="todo in todos"> <li class="flex items-center gap-4 bg-white p-4 rounded shadow"> <!-- Toggle Action --> <button onclick="toggle(todo.id, !todo.completed)"> <!-- Conditional rendering --> <div hidden="{!todo.completed}" class="text-green-500">✓</div> <div hidden="{todo.completed}" class="text-gray-300">○</div> </button> <span>{todo.title}</span> <!-- Delete Action --> <button onclick="remove(todo.id)" class="ml-auto text-red-500"> Delete </button> </li> </template> </ul> <script> // Initialize state with server data const [todos, setTodos] = pp.state([[todos]]); async function toggle(id, completed) { const updated = await pp.rpc("toggle_todo", { id, completed }); setTodos(todos.map(t => t.id === id ? updated : t)); } async function remove(id) { await pp.rpc("delete_todo", { id }); setTodos(todos.filter(t => t.id !== id)); } </script>