Paging Microsoft Graph data in SPFx

Introduction

When working with Microsoft Graph API, you often need to retrieve large datasets that exceed the default page size limit.

In these cases, pagination (or paging) allows you to handle large responses by breaking them into manageable chunks and iterating over each page until all data is retrieved.

How Pagination works

In client-side paging, a client app specifies the number of results it wants Microsoft Graph to return in a single page by using the $top$skip, or $skipToken query parameters. 

For example, the /users endpoint supports $top but not $skip.

In server-side paging, the Microsoft Graph service returns a default number of results in a single page without the client specifying the number of results to return using $top.

For example, the GET /users endpoint returns a default of 100 results in a single page.

Key Concepts in Microsoft Graph Pagination

  • @odata.nextLink: When a response includes more data than the limit, Microsoft Graph returns a @odata.nextLink property containing the URL to the next page of data.
  • Page Size: You can control the number of items per page using the $top query parameter. The default varies by endpoint, but you can specify a smaller or larger page size within limits.

Prerequisites

  • Azure AD App Registration: Register an app in Azure AD with the User.Read.All permission.
  • Install MSAL.js and @pnp/pnpjs: Use the following commands to install the required libraries: 
npm install @azure/msal-browser

npm install @pnp/sp @pnp/graph

Here’s a guide on implementing paging in your app, especially in the context of a SharePoint Framework (SPFx) web part.

Paging Microsoft Graph data in SPFx

Step 1. Basic Setup for an SPFx Web Part

Create or open an SPFx project where you want to retrieve and page through data (e.g., a list of users, groups, or calendar events). Ensure MSAL.js is installed and configured if you're handling authentication in SPFx.

Step 2. Create a file GraphService.ts n the src/services folder.

// GraphService.ts

import { PublicClientApplication, Configuration, AuthenticationResult } from "@azure/msal-browser";

export class GraphService {
  private msalInstance: PublicClientApplication;

  // Set up MSAL configuration
  private msalConfig: Configuration = {
    auth: {
      clientId: "11111111-1111-1111-1111-111111111111", // Replace with your Azure AD app's client ID
      authority: "https://login.microsoftonline.com/{tenant-id}", // Replace with your Azure AD tenant ID
      redirectUri: "https://your-sp-site-url/_layouts/15/workbench.aspx" // Redirect URI for SPFx testing
    }
  };

  private graphScopes: string[] = ["User.Read.All"]; // Scopes for user data

  constructor() {
    this.msalInstance = new PublicClientApplication(this.msalConfig);
  }

  // Method to acquire an access token
  public async getAccessToken(): Promise<string | null> {
    try {
      const account = this.msalInstance.getAllAccounts()[0];
      const request = { scopes: this.graphScopes, account };
      const response: AuthenticationResult = await this.msalInstance.acquireTokenSilent(request);
      return response.accessToken;
    } catch (error) {
      const response: AuthenticationResult = await this.msalInstance.acquireTokenPopup({ scopes: this.graphScopes });
      return response.accessToken;
    }
  }

  // Method to fetch paginated user data from Microsoft Graph API
  public async fetchAllUsersWithPaging(): Promise<any[]> {
    const accessToken = await this.getAccessToken();
    if (!accessToken) {
      console.error("Failed to acquire an access token.");
      return [];
    }

    let users: any[] = [];
    let nextPageUrl = "https://graph.microsoft.com/v1.0/users?$top=50"; // Set page size with $top

    // Loop to retrieve paginated data
    while (nextPageUrl) {
      const response = await fetch(nextPageUrl, {
        headers: { Authorization: `Bearer ${accessToken}` }
      });

      if (response.ok) {
        const data = await response.json();
        users = users.concat(data.value); // Append users from current page
        nextPageUrl = data["@odata.nextLink"] || null; // Set nextPageUrl for pagination
      } else {
        console.error("Failed to fetch users:", response.statusText);
        break;
      }
    }

    return users;
  }
}

export default new GraphService();

Step 3. MySpfxComponent.tsx ( your webpart .tsx file)  Fetch Data with Paging in Microsoft Graph API. 

import * as React from "react";
import GraphService from "../services/GraphService";

interface MySpfxComponentState {
  users: any[];
  loading: boolean;
}

class MySpfxComponent extends React.Component<{}, MySpfxComponentState> {
  constructor(props: {}) {
    super(props);
    this.state = {
      users: [],
      loading: true
    };
  }

  // Load users on component mount
  public async componentDidMount(): Promise<void> {
    try {
      const users = await GraphService.fetchAllUsersWithPaging();
      this.setState({ users, loading: false });
    } catch (error) {
      console.error("Error fetching users:", error);
      this.setState({ loading: false });
    }
  }

  // Render users or loading indicator
  public render(): React.ReactElement<{}> {
    const { users, loading } = this.state;

    return (
      <div>
        <h3>Microsoft Graph Users</h3>
        {loading ? (
          <p>Loading users...</p>
        ) : users.length > 0 ? (
          <ul>
            {users.map((user, index) => (
              <li key={index}>
                {user.displayName} ({user.mail})
              </li>
            ))}
          </ul>
        ) : (
          <p>No users found.</p>
        )}
      </div>
    );
  }
}

export default MySpfxComponent;

Note

  • A page of results may contain zero or more results.
  • Different APIs might have different default and maximum page sizes.
  • Different APIs might behave differently if you specify a page size (via the $top query parameter) that exceeds the maximum page size for that API. Depending on the API, the requested page size might be ignored, it might default to the maximum page size for that API, or Microsoft Graph might return an error.
  • Not all resources or relationships support paging. e.g. queries against directoryRole don't support paging.
  • When using the $count=true query string when querying against directory resources, the @odata.count property is returned only in the first page of the paged result set.

Conclusion

Using Microsoft Graph API with paging enables you to manage large datasets in a controlled manner, making it ideal for applications in SPFx where you might need to display extensive user data, group lists, or calendar events. This approach keeps memory usage efficient and ensures you retrieve all necessary data without overwhelming your application.