Getting Started (End-to-End)
This walkthrough gives you a working backend + frontend using Nestipy Web.
1) Backend (actions + HTTP)
Project layout (backend in src/, web UI in app/, Vite output in web/):
main.py
pyproject.toml
src/
__init__.py
app_module.py
user_actions.py
user_controller.py
app/
page.py
layout.py
web/src/user_actions.py:
py
from nestipy.common import Injectable
from nestipy.web import action
@Injectable()
class UserActions:
@action()
async def hello(self, name: str) -> str:
return f"Hello, {name}!"src/user_controller.py:
py
from nestipy.common import Controller, Get
@Controller("/api")
class UserController:
@Get("/health")
async def health(self) -> dict:
return {"ok": True}src/app_module.py (enable CSRF + allowed origins):
py
from nestipy.common import Module
from nestipy.web import (
ActionsModule,
ActionsOption,
OriginActionGuard,
CsrfActionGuard,
)
from user_actions import UserActions
from user_controller import UserController
@Module(
imports=[
ActionsModule.for_root(
ActionsOption(
path="/_actions",
guards=[
OriginActionGuard(allowed_origins=["http://localhost:5173"]),
CsrfActionGuard(),
],
)
)
],
providers=[UserActions],
controllers=[UserController],
)
class AppModule:
passmain.py:
py
from granian.constants import Interfaces
from nestipy.core import NestipyFactory
from src.app_module import AppModule
app = NestipyFactory.create(AppModule)
if __name__ == "__main__":
# Optional router spec
# export NESTIPY_ROUTER_SPEC=1
# export NESTIPY_ROUTER_SPEC_TOKEN=secret
app.listen(
"main:app",
address="127.0.0.1",
port=8001,
interface=Interfaces.ASGI,
reload=True,
)Run backend:
bash
python main.pyYou now have:
POST /_actions(RPC actions)GET /_actions/schema(actions schema)GET /api/health(HTTP example)GET /_router/spec(optional, when enabled)
2) Frontend (Python → TSX → Vite)
Initialize + run Vite with proxy:
bash
nestipy run web:init
nestipy run web:dev --vite --install --proxy http://127.0.0.1:8001Generate actions client (recommended):
bash
nestipy run web:actions --spec http://127.0.0.1:8001/_actions/schema \
--output web/src/actions.client.tsUse actions in React/TS:
ts
import { createActions } from './actions.client';
const actions = createActions();
const res = await actions.UserActions.hello({ name: 'Nestipy' });
if (res.ok) console.log(res.data);Use actions from Python UI via external_fn():
app/page.py:
py
from nestipy.web import component, h, external_fn, use_effect, use_state
create_actions = external_fn("../actions.client", "createActions", alias="createActions")
@component
def Page():
value, set_value = use_state("")
actions = create_actions()
use_effect(
lambda: actions.UserActions.hello({"name": "Nestipy"}).then(
lambda res: set_value(res.data if res.ok else "Error")
),
deps=[],
)
return h.div(
h.h1("Nestipy Web"),
h.p(value, class_name="text-sm"),
class_name="p-8 space-y-3",
)Optional: Typed HTTP Client
Generate the HTTP client from RouterSpec:
bash
nestipy run web:codegen --spec http://127.0.0.1:8001/_router/spec \
--output web/src/api/client.ts --lang tsUse it in TS:
ts
import { createApiClient } from './api/client';
const api = createApiClient();
const res = await api.UserController.health();