When your project grows past ten procedures, a single flat router becomes hard to navigate. pyRPC's Router.merge() lets you split procedures into separate modules and combine them at the top level.
The basic pattern
# users.py from pyrpc_core import Router users = Router() @users.rpc def get_user(user_id: int) -> dict: ... @users.rpc def update_user(user_id: int, name: str) -> dict: ... # orders.py from pyrpc_core import Router orders = Router() @orders.rpc def get_order(order_id: int) -> dict: ... # app.py from pyrpc_core import Router from users import users from orders import orders router = Router() router.merge(users) router.merge(orders)
Each module gets its own Router. The main app merges them. The framework adapter sees one flat namespace of procedures.
With prefixes
merge() accepts an optional prefix parameter. Procedures become prefix.procedure_name:
router.merge(users, prefix="users") router.merge(orders, prefix="orders")
On the TypeScript side, you get api.users.get_user.useQuery() and api.orders.get_order.useQuery(). Without prefixes, it is just api.get_user.useQuery().
Why this matters
Larger projects need separation. One team owns users, another owns orders. Each team writes their own Router. The main app merges them. No circular imports, no shared state, no framework-specific routing.
This is the same pattern tRPC uses with nested routers, but pyRPC keeps it simpler: flat merge, optional prefix, one level. If you need deeper nesting, just merge a merged router.

pyRPC