AppSync resolvers come in two varieties: unit and pipeline. Unit resolvers are "normal" resolvers with a data source and the glue code attached to a field. When AppSync resolves the field, it calls the resolver and interacts with the data source.
The main drawback of unit resolvers is that they allow only a single call to a data source. How to implement a resolver when it needs to call the database multiple times? Or when you need to interact with a different system and write some data? For example, if users are managed by Cognito but all of them also needs to be inserted into a database, then you'd need two data sources: an HTTP that interacts with the Cognito API, and a second one to put an item to DynamoDB.
With only unit resolvers, this is only possible with a Lambda function that the resolver calls and that does both operations.
Pipeline resolvers provide a way to solve this without opting out of the AppSync resolver architecture. In practice, they allow extending a unit resolver into a pipeline for simple cases, so you only need to resort to Lambda functions when the resolver becomes too complicated. Note though that there is a hard limit of 10 functions per pipeline.
A pipeline is made up of individual functions, each behaving like a unit resolver with a data source, a request, and a response mapping template. Moreover, each of them gets the result of the previous function, allowing them to generate a response in steps. Finally, the whole pipeline also has a request and response mapping template acting as a transformer for the first and the last functions.
As each function is a full-blown resolver, they can interact with different data sources. The first might make a HTTP request, while the second executes a DynamoDB transaction. In practice, pipeline resolvers are powerful: especially as the HTTP resolver can call AWS APIs, AppSync can handle a surprising amount of processes.