Server-to-server Node.js app

[AEM Headless as a Cloud Service]{class="badge informative"}

Example applications are a great way to explore the headless capabilities of Adobe Experience Manager (AEM). This server-to-server application demonstrates how to query content using AEM’s GraphQL APIs using persisted queries and print it on terminal.

Server-to-server Node.js app with AEM Headless

View the source code on GitHub

Prerequisites prerequisites

The following tools should be installed locally:

AEM requirements

The Node.js application works with the following AEM deployment options. All deployments requires the WKND Site v3.0.0+ to be installed.

This Node.js application can connect to AEM Author or AEM Publish based on the command-line parameters.

How to use

  1. Clone the adobe/aem-guides-wknd-graphql repository:

    code language-shell
    $ git clone
  2. Open a terminal and run the commands:

    code language-shell
    $ cd aem-guides-wknd-graphql/server-to-server-app
    $ npm install
  3. The app can be run using the command:

    code language-none

    For example, to run the app against AEM Publish without authorization:

    code language-shell
    $ node index.js

    To run the app against AEM Author with authorization:

    code language-shell
    $ node index.js ./service-config.json
  4. A JSON list of adventures from the WKND reference site should print in the terminal.

The code

Below is a summary of how the server-to-server Node.js application is built, how it connects to AEM Headless to retrieve content using GraphQL persisted queries, and how that data is presented. The full code can be found on GitHub.

The common use case for server-to-server AEM Headless apps is to sync Content Fragment data from AEM into other systems, however this application is intentionally simple, and prints the JSON results from the persisted query.

Persisted queries

Following AEM Headless best practices, the application uses AEM GraphQL persisted queries to query adventure data. The application uses two persisted queries:

  • wknd/adventures-all persisted query, which returns all adventures in AEM with an abridged set of properties. This persisted query drives the initial view’s adventure list.
# Retrieves a list of all Adventures
# Optional query variables:
# - { "offset": 10 }
# - { "limit": 5 }
# - {
#    "imageFormat": "JPG",
#    "imageWidth": 1600,
#    "imageQuality": 90
#   }
query ($offset: Int, $limit: Int, $sort: String, $imageFormat: AssetTransformFormat=JPG, $imageWidth: Int=1200, $imageQuality: Int=80) {
    offset: $offset
    limit: $limit
    sort: $sort
    _assetTransform: {
      format: $imageFormat
      width: $imageWidth
      quality: $imageQuality
      preferWebp: true
  }) {
    items {
      primaryImage {
        ... on ImageRef {

Create AEM Headless client

const { AEMHeadless, getToken } = require('@adobe/aem-headless-client-nodejs');

async function run() {

    // Parse the AEM host, and optional service credentials from the command line arguments
    const args = process.argv.slice(2);
    const aemHost = args.length > 0 ? args[0] : null;                // Example:
    const serviceCredentialsFile = args.length > 1 ? args[1] : null; // Example: ./service-config.json

    // If service credentials are provided via command line argument,
    // use `getToken(..)` to exchange them with Adobe IMS for an AEM access token
    let accessToken;
    if (serviceCredentialsFile) {
        accessToken = (await getToken(serviceCredentialsFile)).accessToken;

    // Instantiate withe AEM Headless client to query AEM GraphQL APIs
    // The endpoint is left blank since only persisted queries should be used to query AEM's GraphQL APIs
    const aemHeadlessClient = new AEMHeadless({
        serviceURL: aemHost,
        endpoint: '',           // Avoid non-persisted queries
        auth: accessToken       // accessToken only set if the 2nd command line parameter is set

Execute GraphQL persisted query

AEM’s persisted queries are executed over HTTP GET and thus, the AEM Headless client for Node.js is used to execute the persisted GraphQL queries against AEM and retrieves the adventure content.

The persisted query is invoked by calling aemHeadlessClient.runPersistedQuery(...), and passing the persisted GraphQL query name. Once the GraphQL returns the data, pass it to the simplified doSomethingWithDataFromAEM(..) function, which prints the results - but typically would send the data to another system, or generate some output based on the retrieved data.

// index.js

async function run() {
    try {
        // Retrieve the data from AEM GraphQL APIs
        data = await aemHeadlessClient.runPersistedQuery('wknd-shared/adventures-all')

        // Do something with the data from AEM.
        // A common use case is sending the data to another system.
        await doSomethingWithDataFromAEM(data);
    } catch (e) {