This quickstart demonstrates how you can connect to Dataverse and use the Web API with the following technologies:
Technology | Description |
---|---|
JavaScript | A programming language for web development, enabling interactive content. It runs in browsers for client-side scripting and can be used server-side with Node.js. |
Visual Studio Code | A lightweight, open-source code editor with debugging, syntax highlighting, and plugin support. |
Single Page Applications (SPAs) | Web applications that load a single HTML page and dynamically update content as the user interacts with the app. This approach provides a smoother, faster user experience by reducing page reloads and enhancing performance. |
Microsoft Authentication Library for JavaScript (MSAL.js) | A library that enables authentication and authorization for web applications using Microsoft identity platforms. It simplifies integrating secure sign-in and token acquisition for accessing protected resources. |
Cross-Origin Resource Sharing (CORS) | A SPA application can use client-side JavaScript with the Dataverse Web API because CORS is enabled. CORS is a security feature in web browsers that allows controlled access to resources on a web server from a different origin. It enables web applications to bypass the same-origin policy, facilitating safe and secure data sharing across different domains. |
Goal
This quickstart focuses on connecting to the Dataverse Web API with JavaScript using a SPA client application with a minimum of number of steps. When you complete this quickstart, you're able to:
- Sign in and connect to Dataverse
- Invoke the WhoAmI function and display your
UserID
value.
Completing this quickstart enables you to try the Web API Data operations Samples (Client-side JavaScript) which demonstrate more advanced capabilities.
Note
This quickstart doesn't apply to the following client-side JavaScript scenarios:
Scenario | Learn more |
---|---|
Model-driven application scripts | - Apply business logic using client scripting in model-driven apps using JavaScript - Xrm.WebApi (Client API reference) |
Power Apps component framework | - Code components WebAPI - Implementing Web API component |
Power Pages Portals | Power Pages Portals Web API |
In these scenarios, the respective application type provides a capability for you to send requests rather than use the JavaScript native Fetch API directly as shown in this quickstart. Client-side scripts within model-driven apps run in the context of an authenticated application, so each request doesn't require an access token.
Prerequisites
The following table describes the prerequisites needed to complete this quickstart and Web API Data operations Samples (Client-side JavaScript).
Prerequisite | Description |
---|---|
Privileges to create an Entra App registration | You can't complete this quickstart without the ability create a Microsoft Entra app registration to enable it. If you aren't sure if you can, try the first step to Register a SPA application and find out. |
Visual Studio Code | If Visual Studio Code isn't installed on your computer, you must Download and install Visual Studio Code to run this quickstart. |
Node.js | Node.js is a runtime environment that allows you to run JavaScript on the server side. This quickstart creates a SPA application that runs JavaScript on the client side in a browser rather than the Node.js runtime. But Node Package Manager (npm) is installed with Node.js, and you need npm to install Parcel and the MSAL.js library. |
Parcel | Modern web applications typically have many dependencies on open source libraries distributed using npm and scripts that need to be managed and optimized during the build process. These tools are called 'bundlers'. The most common one is webpack. This quick start uses Parcel because it offers a simplified experience. For quickstarts and samples that show SPA applications using different frameworks and bundlers, see Microsoft Entra Single-page applications samples. You can adapt these samples to use Dataverse Web API with the information shown in this quickstart. |
Web Technologies | Knowledge of HTML, JavaScript, and CSS are required to understand how this quickstart works. Understanding how to make network requests with JavaScript is essential. |
Register a SPA application
This step is first because if you can't register an app, you can't complete this quick start.
Any of the following privileged Microsoft Entra roles include the required permissions:
When you configure the application, you need an application (client) ID, and the ID of your Microsoft Entra tenant. You should also choose a descriptive name for the application so people know what the application was created for.
Register your app
You can register your application using either the:
- Microsoft Entra web application UI
- Azure PowerShell New-AzADApplication cmdlet.
Create the application registration
Sign in to the Microsoft Entra admin center.
If you have access to multiple tenants, use the Settings
icon in the top menu to switch to the tenant in which you want to register the application from the Directories + subscriptions menu.
Browse to Identity > Applications > App registrations and select New registration.
Enter a Name for the application, such as
Dataverse Web API Quickstart SPA
.For Supported account types, under Who can use this application or access this API, select Accounts in this organizational directory only (<Your tenant name> - Single tenant).
For Redirect URI (optional)
- For Select a platform, choose Single-page application (SPA).
- Enter
http://localhost:1234
as the value.
Select Register to save your changes.
In the window for the app registration you created, in the Overview tab, below Essentials, you can find these values:
- Application (client) ID
- Directory (tenant) ID
Copy these values because you need them when you create the .env file to use environment variables.
Add Dataverse user_impersonation
privilege
- In the Manage area, select API permissions.
- Select Add a permission.
- In the Request API permissions flyout, select the APIs my organization uses tab.
- Type 'Dataverse' to find application (client) ID
00000007-0000-0000-c000-000000000000
. - Select the Dataverse application.
- In Select permissions,
user_impersonation
is the only available delegated permission. Select it. - Select Add permissions.
Note
If you don't have the privileges to create an app registration for your company, get a tenant of your own through via the Power Apps Developer Plan.
Install Node.js
Go to Download Node.js.
Choose the appropriate installer for your operating system (Windows, macOS, or Linux) and download it.
Run the installer. Make sure you accept the default option to: Install npm, the recommended package manager for Node.js.
Verify the installation by opening a terminal or command prompt, typing these commands and pressing Enter.
node -v
npm -v
You should see something like this:
PS C:\Users\you> node -v v22.14.0 PS C:\Users\you> npm -v 9.5.0 PS C:\Users\you>
Create a project
Note
You can skip these steps by cloning or downloading the PowerApps-Samples repository. The completed application for these steps is available at /dataverse/webapi/JS/quickspa. Follow the instructions in the README.
The instructions in this section guide you to install dependencies from npm, create the folder structure, open Visual Studio Code.
Open a terminal window to a place where you want to create a project. For these instructions, we use
C:\projects
.Type the following commands and press Enter to achieve the following actions:
Command Action mkdir quickspa
Create a new folder named quickspa
.cd quickspa
Move into the new quickspa
folder.npm install --save-dev parcel
Install Parcel and initialize the project. npm install @azure/msal-browser
Install the MSAL.js library. npm install dotenv
Install dotenv to access environment variables that store potentially sensitive configuration data. mkdir src
Create a src
folder where you add HTML, JS, and CSS files for your app in the following steps.code .
Open Visual Studio Code in the context of the quickspa
folder.
Your project should look like this in Visual Studio Code Explorer:
Create the .env file
Storing configuration data in the environment separate from code is a security best practice.
Create a new file named
.env
in the root of yourquickspa
folder.Paste in the values from Register your app to replace the
CLIENT_ID
andTENANT_ID
values below.# The environment this application will connect to. BASE_URL=https://<yourorg>.api.crm.dynamics.com # The registered Entra application id CLIENT_ID=11112222-bbbb-3333-cccc-4444dddd5555 # The Entra tenant id TENANT_ID=aaaabbbb-0000-cccc-1111-dddd2222eeee # The SPA redirect URI included in the Entra application registration REDIRECT_URI=http://localhost:1234
Set the
BASE_URL
value to the URL of the Web API URL for the environment you want to connect to.
Note
You won't check-in the .env
file. In Create .gitignore
file, you will exclude it. But you might want to create a .env.example
file using the placeholder values so that people know what data it should contain.
Create an HTML page
The instructions in this section describe how to create the HTML file that provides the user interface for the SPA application.
Create a new file in the
src
folder namedindex.html
.Copy and paste this content to the
index.html
page:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Dataverse Web API JavaScript Quick Start</title> <link rel="stylesheet" href="styles/style.css" /> </head> <body> <header> <h1>Dataverse Web API JavaScript Quick Start</h1> <button id="loginButton">Login</button> <button id="logoutButton" class="hidden">Logout</button> </header> <nav id="buttonContainer" class="disabled"> <button id="whoAmIButton">WhoAmI</button> </nav> <main id="container"></main> <script type="module" src="scripts/index.js"></script> </body> </html>
This HTML provides the following elements:
Element ID | Element type | Description |
---|---|---|
loginButton |
button | To open the login dialog. |
logoutButton |
button | To open the logout dialog. Hidden by default. |
buttonContainer |
nav | Contains buttons that require user to be logged in to use. Disabled by default. |
whoAmIButton |
button | Executes the WhoAmI function to display the user's ID. |
container |
main | Area where information can be displayed to the user. |
script | Loads the index.js file after the rests of the elements of the page loads. |
Create a JavaScript script
This file contains all the logic that makes the index.html
page dynamic.
Create a new folder in the
src
folder namedscripts
.Create a new file in
scripts
folder namedindex.js
.Copy and paste this content into the
index.js
page:import { PublicClientApplication } from "@azure/msal-browser"; import 'dotenv/config' // Load the environment variables from the .env file const config = { baseUrl: process.env.BASE_URL, clientId: process.env.CLIENT_ID, tenantId: process.env.TENANT_ID, redirectUri: process.env.REDIRECT_URI, }; // Microsoft Authentication Library (MSAL) configuration const msalConfig = { auth: { clientId: config.clientId, authority: "https://login.microsoftonline.com/" + config.tenantId, redirectUri: config.redirectUri, postLogoutRedirectUri: config.redirectUri, }, cache: { cacheLocation: "sessionStorage", // This configures where your cache will be stored storeAuthStateInCookie: true, }, }; // Create an instance of MSAL const msalInstance = new PublicClientApplication(msalConfig); // body/main element where messages are displayed const container = document.getElementById("container"); // Event handler for login button async function logIn() { await msalInstance.initialize(); if (!msalInstance.getActiveAccount()) { const request = { scopes: ["User.Read", config.baseUrl + "/user_impersonation"], }; try { const response = await msalInstance.loginPopup(request); msalInstance.setActiveAccount(response.account); // Hide the loginButton so it won't get pressed twice document.getElementById("loginButton").style.display = "none"; // Show the logoutButton const logoutButton = document.getElementById("logoutButton"); logoutButton.innerHTML = "Logout " + response.account.name; logoutButton.style.display = "block"; // Enable any buttons in the nav element document.getElementsByTagName("nav")[0].classList.remove("disabled"); } catch (error) { let p = document.createElement("p"); p.textContent = "Error logging in: " + error; p.className = "error"; container.append(p); } } else { // Clear the active account and try again msalInstance.setActiveAccount(null); this.click(); } } // Event handler for logout button async function logOut() { const activeAccount = await msalInstance.getActiveAccount(); const logoutRequest = { account: activeAccount, mainWindowRedirectUri: config.redirectUri, }; try { await msalInstance.logoutPopup(logoutRequest); document.getElementById("loginButton").style.display = "block"; this.innerHTML = "Logout "; this.style.display = "none"; document.getElementsByTagName("nav")[0].classList.remove("disabled"); } catch (error) { console.error("Error logging out: ", error); } } /** * Retrieves an access token using MSAL (Microsoft Authentication Library). * Set as the getToken function for the DataverseWebAPI client in the login function. * * @async * @function getToken * @returns {Promise<string>} The access token. * @throws {Error} If token acquisition fails and is not an interaction required error. */ async function getToken() { const request = { scopes: [config.baseUrl + "/.default"], }; try { const response = await msalInstance.acquireTokenSilent(request); return response.accessToken; } catch (error) { if (error instanceof msal.InteractionRequiredAuthError) { const response = await msalInstance.acquireTokenPopup(request); return response.accessToken; } else { console.error(error); throw error; } } } // Add event listener to the login button document.getElementById("loginButton").onclick = logIn; // Add event listener to the logout button document.getElementById("logoutButton").onclick = logOut; /// Function to get the current user's information /// using the WhoAmI function of the Dataverse Web API. async function whoAmI() { const token = await getToken(); const request = new Request(config.baseUrl + "/api/data/v9.2/WhoAmI", { method: "GET", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", Accept: "application/json", "OData-Version": "4.0", "OData-MaxVersion": "4.0", }, }); // Send the request to the API const response = await fetch(request); // Handle the response if (!response.ok) { throw new Error("Network response was not ok: " + response.statusText); } // Successfully received response return await response.json(); } // Add event listener to the whoAmI button document.getElementById("whoAmIButton").onclick = async function () { // Clear any previous messages container.replaceChildren(); try { const response = await whoAmI(); let p1 = document.createElement("p"); p1.textContent = "Congratulations! You connected to Dataverse using the Web API."; container.append(p1); let p2 = document.createElement("p"); p2.textContent = "User ID: " + response.UserId; container.append(p2); } catch (error) { let p = document.createElement("p"); p.textContent = "Error fetching user info: " + error; p.className = "error"; container.append(p); } };
The index.js
script contains the following constants and functions:
Item | Description |
---|---|
config |
Contains the data used by the Microsoft Authentication Library (MSAL) configuration. |
msalConfig |
Microsoft Authentication Library (MSAL) configuration. |
msalInstance |
The MSAL PublicClientApplication instance. |
container |
The element where messages are displayed. |
getToken |
Retrieves an access token using MSAL. |
logIn |
Event listener function for the login button. Opens a choose account dialog. |
logOut |
Event listener function for the logout button. Opens a choose account dialog. |
whoAmI |
Asynchronous function that calls the WhoAmI function to retrieve data from Dataverse. |
whoAmIButton event listener |
The function that calls the whoAmI function and manages the UI changes to show the message. |
Create a CSS page
The Cascading Style Sheet (CSS) file makes the HTML page more attractive and has a role in controlling when controls are disabled or hidden.
Create a new folder named
styles
in thesrc
folder.Create a new file named
style.css
in thestyles
folder.Copy and paste this text into the
style.css
file:.disabled { pointer-events: none; opacity: 0.5; /* Optional: to visually indicate the element is disabled */ } .hidden { display: none; } .error { color: red; } .expectedError { color: green; } body { font-family: 'Roboto', sans-serif; font-size: 16px; line-height: 1.6; color: #333; background-color: #f9f9f9; } h1, h2, h3 { color: #2c3e50; } button { background-color: #3498db; color: #fff; border: none; padding: 10px 20px; border-radius: 5px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); transition: background-color 0.3s ease; margin: 5px; /* Adjust the value as needed */ } button:hover { background-color: #2980b9; } header { padding-bottom: 10px; /* Adjust the value as needed */ }
Create .gitignore
file
When your app is checked in with source control, adding a .gitignore
file prevents checking in files the specified files and folders.
Create a file named
.gitignore
.Add the following content:
.parcel-cache dist node_modules .env
The .parcel-cache
and dist
folders appear when you run the app for the first time.
Not checking in the .env
file is a security best practice. You might want to check in a placeholder .env.sample
file with placeholder values.
Your project should look like this in Visual Studio Code Explorer:
Configure your package.json file
Your package.json
file should look something like this:
{
"devDependencies": {
"parcel": "^2.14.1",
},
"dependencies": {
"@azure/msal-browser": "^4.7.0",
"dotenv": "^16.4.7"
}
}
Add this scripts
item underneath dependencies
:
"dependencies": {
"@azure/msal-browser": "^4.7.0",
"dotenv": "^16.4.7"
},
"scripts": {
"start": "parcel src/index.html"
}
This configuration allows you to start the application using npm start
in the next step.
Try it
In Visual Studio Code, open a terminal window
Type
npm start
and press Enter.Note
You might see some output written to the terminal while the project initializes for the first time. This is parcel installing some more node modules to mitigate issues when using dotenv. Look at the
package.json
and you should some new items added to thedevDependencies
.You should expect output to the terminal that looks like this:
Server running at http://localhost:1234 Built in 1.08s
Press Ctrl + click the http://localhost:1234 link to open your browser.
In your browser, select the Login button.
The Sign in to your account dialog opens.
In the Sign in to your account dialog, select the account that has access to Dataverse.
The first time you access using a new application (client) ID value, you see this Permissions requested dialog:
Select Accept on the Permissions requested dialog.
Select the WhoAmI button.
The message Congratulations! You connected to Dataverse using the Web API. is displayed with your
UserId
value from the WhoAmIResponse complex type.
Trouble shooting
This section contains errors that you might encounter running this quick start.
Note
If you experience issues completing the steps in this quick start, try cloning or downloading the PowerApps-Samples repository. The completed application for these steps is available at /dataverse/webapi/JS/quickspa. Follow the instructions in the README. If that doesn't work, create an GitHub issue referencing this quickspa
sample application.
Selected user account doesn't exist in tenant
When the account you select doesn't belong to the same Microsoft Entra tenant as the registered application, you get this error in the Pick an account dialog:
Selected user account does not exist in tenant '{Your tenant name}' and cannot access the application '{Your application ID}' in that tenant. The account needs to be added as an external user in the tenant first. Please use a different account.
Resolution: Make sure you choose the correct user.
Next steps
Try other samples that use client-side JavaScript.
Learn more about Dataverse Web API capabilities by understanding the service documents.