Example: you have http://ui.localhost and you have http://api.localhost
UI speaking to API = CORS
But, instead of doing fetch('http://api.localhost/resource'), you do fetch('http://ui.localhost/api/resource')
In the nginx config for ui.localhost domain, you create a rule that says "everything that starts with /api, intercept it, remove /api at the start of the path and send the rest to http://api.localhost, ending up with http://api.localhost/resource"
I do frontend and backend development and I have this setup with docker-compose, the config for nginx is really trivial and widely available in many tutorials.
That's why we have various controls with proxies, such as including the original requester's IP etc.
It's irrelevant who actually asks for data if you pass the HTTP request info unaltered (except path parameter), the WAF can do its job. That's the beauty of HTTP and its stateless nature. You can scale infinitely and do various actions such as this one and get the expected result.