Biggest misconception of GraphQL's resolvers is that you have to put them on fields. You don't.
Put 1 on a query/mutation. This matters because people think
"oh, DynamoDB is single table design, GraphQL needs individual fields".
No. Your query can just do a DynamoDB query.
The worry with individual GraphQL field resolvers is you get into the waterfall pattern, making a bunch of I/O calls in sequence, and either slow things down, or worse, have to utilize multiple DynamoDB tables making it more relational, and thus not using single table design.
If you're building an AppSync/GraphQL API for that, yeah, that's a problem, better to use relational like Postegres.
If you want single table design for Dynamo AND want to use GraphQL, it's a match made in heaven if you just add a second step to your modelling.
Step 1 is the whole "flatten your joins into a single row". That's the normal DynamoDB pattern of optimizing your data to be obtained in a single query.
Step 2 is "just make a GraphQL query for that dynamodb query". That's it.
Example, GraphQL Query:
getStuff(id:ID!):Stuff!
The Lambda in turn calls Dynamo:
dynamodbDoc.query({ 'id=getStuffID' })
I am NOT trivializing the data access patterns in Step 1. That is super hard. This is also why it's sometimes easier to use relational or NoSQL for flexibility "because Product Owners/Users" change crap all the time and you're not punished when updating queries.
While I enjoy writing ETL pipelines, business/product shouldn't be punished for changing their mind.
Anyway, the tl;dr; ensuring a UI can pass [1, 2, 3] safely to the back-end is easier in GraphQL than REST. Building BFF's for a UI, I know the access patterns. GraphQL + DynamoDB is awesome.