- graphql() function takes "validators" argument for a reason ;) There are multiple strategies for limiting complexity, but common ones are boxing (limiting depth and width of query) or query cost limitation.
For auth, you authenticate user using custom middleware before entering the query executor. Inside query executor you can do one of many things, depending on what makes sense for you: error field resolution, return "null", or return custom type like "ObjectNotFoundError".
For caching, you cache inside resolvers that do something costful, or in data loaders those resolvers call.