Yeah, I like doing this as well. And all the data goes in the request body. No query parameters.
Especially when the primary intended client is an SPA, where the URL shown is decoupled with the API URL.
Little bit of a memory jolt: I once built a (not for prod) backend in python as follows:
write a list of functions, one for each RPC, in a file `functions.py`
then write this generic function for flask:
import server.functions as functions
@server.post("/<method>")
def api(method: str):
data: Any = request.json if request.is_json else {}
fn = lookup(functions, method)
if fn is None:
return {"error": "Method not found."}
return fn(data)
And `lookup()` looks like:
def lookup(module: ModuleType, method: str):
md = module.__dict__
mn = module.__name__
is_present = method in md
is_not_imported = md[method].__module__ == mn
is_a_function = inspect.isfunction(md[method])
if is_present and is_not_imported and is_a_function:
return md[method]
return None
So writing a new RPC is just writing a new function, and it all gets automatically wired up to `/api/function_name`. Quite nice.
The other nice feature there was automatic "docs" generation, from the python docstring of the function. You see, in python you can dynamically read the docstring of an object. So, I wrote this:
def get_docs(module: ModuleType):
md = module.__dict__
mn = module.__name__
docs = ""
for name in md:
if not inspect.isfunction(md[name]) or md[name].__module__ != mn:
continue
docs += md[name].__doc__ + "\n<br>\n"
return docs[:-6]
Gives a simple text documentation which I served at an endpoint. Of course you could also write the docstring in openapi yaml format and serve it that way too.
Quite cursed overall, but hey, its python.
One of the worst footguns here is that you could accidentally expose helper functions, so you have to be sure to not write those in the functions file :P