Erstellen von Node.js Express-Apps mit Microsoft Graph
In diesem Lernprogramm erfahren Sie, wie Sie eine Node.js Express-Web-App erstellen, die die Microsoft Graph-API zum Abrufen von Kalenderinformationen für einen Benutzer verwendet.
Tipp
Wenn Sie es vorziehen, nur das abgeschlossene Lernprogramm herunterzuladen, können Sie es auf zwei Arten herunterladen.
- Laden Sie die Node.js Schnellstart herunter , um in Minuten Arbeitscode zu erhalten.
- Laden Sie das GitHub Repository herunter, oder klonen Sie es.
Voraussetzungen
Bevor Sie mit dieser Demo beginnen, sollten SieNode.js auf Ihrem Entwicklungscomputer installiert haben. Wenn Sie nicht über Node.js verfügen, finden Sie unter dem vorherigen Link Downloadoptionen.
Hinweis
Windows Benutzer müssen möglicherweise Python und Visual Studio Build Tools installieren, um NPM-Module zu unterstützen, die aus C/C++ kompiliert werden müssen. Das Node.js-Installationsprogramm auf Windows bietet die Möglichkeit, diese Tools automatisch zu installieren. Alternativ können Sie die Anweisungen unter https://github.com/nodejs/node-gyp#on-windowsfolgen.
Sie sollten auch über ein persönliches Microsoft-Konto mit einem Postfach auf Outlook.com oder ein Microsoft-Geschäfts-, Schul- oder Unikonto verfügen. Wenn Sie kein Microsoft-Konto haben, gibt es einige Optionen, um ein kostenloses Konto zu erhalten:
- Sie können sich für ein neues persönliches Microsoft-Konto registrieren.
- Sie können sich für das Microsoft 365-Entwicklerprogramm registrieren, um ein kostenloses Microsoft 365 Abonnement zu erhalten.
Hinweis
Dieses Lernprogramm wurde mit Node Version 14.15.0 geschrieben. Die Schritte in diesem Handbuch funktionieren möglicherweise mit anderen Versionen, die jedoch nicht getestet wurden.
Feedback
Bitte geben Sie Feedback zu diesem Lernprogramm im GitHub Repository.
Erstellen einer Node.js Express-Web-App
In dieser Übung verwenden Sie Express , um eine Web-App zu erstellen.
Öffnen Sie Ihre CLI, navigieren Sie zu einem Verzeichnis, in dem Sie über die Berechtigung zum Erstellen von Dateien verfügen, und führen Sie den folgenden Befehl aus, um eine neue Express-App zu erstellen, die Handlebars als Renderingmodul verwendet.
npx express-generator --hbs graph-tutorialDer Express-Generator erstellt ein neues Verzeichnis mit dem Namen
graph-tutorialund erstellt ein Gerüst für eine Express-App.Navigieren Sie zum
graph-tutorialVerzeichnis, und geben Sie den folgenden Befehl ein, um Abhängigkeiten zu installieren.npm installFühren Sie den folgenden Befehl aus, um Node-Pakete mit gemeldeten Sicherheitsrisiken zu aktualisieren.
npm audit fixFühren Sie den folgenden Befehl aus, um die Version von Express und andere Abhängigkeiten zu aktualisieren.
npm install express@4.17.1 http-errors@1.8.0 morgan@1.10.0 debug@4.3.1 hbs@4.1.2Verwenden Sie den folgenden Befehl, um einen lokalen Webserver zu starten.
npm startÖffnen Sie Ihren Browser und navigieren Sie zu
http://localhost:3000. Wenn alles funktioniert, wird die Meldung "Willkommen beim Express" angezeigt. Wenn diese Meldung nicht angezeigt wird, überprüfen Sie die Schnellanleitung für die ersten Schritte.
Installieren von Node-Paketen
Bevor Sie fortfahren, installieren Sie einige zusätzliche Pakete, die Sie später verwenden werden:
- Dotenv zum Laden von Werten aus einer ENV-Datei.
- date-fns formatting date/time values.
- windows-iana zum Übersetzen Windows Zeitzonennamen in IANA-Zeitzonen-IDs.
- connect-flash to flash error messages in the app.
- Expresssitzung zum Speichern von Werten in einer serverseitigen Sitzung im Arbeitsspeicher.
- express-promise-router to allow route handlers to return a Promise.
- Express-Validator zum Analysieren und Überprüfen von Formulardaten.
- msal-node zum Authentifizieren und Abrufen von Zugriffstoken.
- microsoft-graph-client for making calls to Microsoft Graph.
- isomorphic-fetch to polyfill the fetch for Node. Für die Bibliothek ist ein
microsoft-graph-clientFetch-Polyfill erforderlich. Weitere Informationen finden Sie im Wiki der Microsoft Graph JavaScript-Clientbibliothek. - qs zum Erstellen von URL-Abfragezeichenfolgen.
Führen Sie den folgenden Befehl in Der CLI aus.
npm install dotenv@10.0.0 date-fns@2.23.0 date-fns-tz@1.1.6 connect-flash@0.1.1 express-validator@6.12.1 npm install express-session@1.17.2 express-promise-router@4.1.0 isomorphic-fetch@3.0.0 npm install @azure/msal-node@1.3.0 @microsoft/microsoft-graph-client@3.0.0 windows-iana@5.0.2Tipp
Windows Benutzern wird möglicherweise die folgende Fehlermeldung angezeigt, wenn sie versuchen, diese Pakete auf Windows zu installieren.
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable.Um den Fehler zu beheben, führen Sie den folgenden Befehl aus, um die Windows Buildtools mithilfe eines Terminalfensters mit erhöhten Rechten (Administrator) zu installieren, in dem die VS-Buildtools und Python installiert werden.
npm install --global --production windows-build-toolsAktualisieren Sie die Anwendung, um die
connect-flashMiddleware zuexpress-sessionverwenden. Öffnen Sie ./app.js , und fügen Sie die folgenderequireAnweisung am Anfang der Datei hinzu.const session = require('express-session'); const flash = require('connect-flash'); const msal = require('@azure/msal-node');Fügen Sie den folgenden Code unmittelbar nach der
var app = express();Zeile hinzu.// Session middleware // NOTE: Uses default in-memory session store, which is not // suitable for production app.use(session({ secret: 'your_secret_value_here', resave: false, saveUninitialized: false, unset: 'destroy' })); // Flash middleware app.use(flash()); // Set up local vars for template layout app.use(function(req, res, next) { // Read any flashed errors and save // in the response locals res.locals.error = req.flash('error_msg'); // Check for simple error string and // convert to layout's expected format var errs = req.flash('error'); for (var i in errs){ res.locals.error.push({message: 'An error occurred', debug: errs[i]}); } // Check for an authenticated user and load // into response locals if (req.session.userId) { res.locals.user = app.locals.users[req.session.userId]; } next(); });
Entwerfen der App
In diesem Abschnitt implementieren Sie die Benutzeroberfläche für die App.
Öffnen Sie "./views/layout.hbs" , und ersetzen Sie den gesamten Inhalt durch den folgenden Code.
<!DOCTYPE html> <html> <head> <title>Node.js Graph Tutorial</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.1/css/all.css" crossorigin="anonymous"> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <div class="container"> <a href="/" class="navbar-brand">Node.js Graph Tutorial</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarCollapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a href="/" class="nav-link{{#if active.home}} active{{/if}}">Home</a> </li> {{#if user}} <li class="nav-item" data-turbolinks="false"> <a href="/calendar" class="nav-link{{#if active.calendar}} active{{/if}}">Calendar</a> </li> {{/if}} </ul> <ul class="navbar-nav justify-content-end"> <li class="nav-item"> <a class="nav-link" href="https://developer.microsoft.com/graph/docs/concepts/overview" target="_blank"> <i class="fas fa-external-link-alt mr-1"></i>Docs </a> </li> {{#if user}} <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"> {{#if user.avatar}} <img src="{{ user.avatar }}" class="rounded-circle align-self-center mr-2" style="width: 32px;"> {{else}} <i class="far fa-user-circle fa-lg rounded-circle align-self-center mr-2" style="width: 32px;"></i> {{/if}} </a> <div class="dropdown-menu dropdown-menu-right"> <h5 class="dropdown-item-text mb-0">{{ user.displayName }}</h5> <p class="dropdown-item-text text-muted mb-0">{{ user.email }}</p> <div class="dropdown-divider"></div> <a href="/auth/signout" class="dropdown-item">Sign Out</a> </div> </li> {{else}} <li class="nav-item"> <a href="/auth/signin" class="nav-link">Sign In</a> </li> {{/if}} </ul> </div> </div> </nav> <main role="main" class="container"> {{#each error}} <div class="alert alert-danger" role="alert"> <p class="mb-3">{{ this.message }}</p> {{#if this.debug }} <pre class="alert-pre border bg-light p-2"><code>{{ this.debug }}</code></pre> {{/if}} </div> {{/each}} {{{body}}} </main> <!-- Bootstrap/jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script> </body> </html>Dieser Code fügt Bootstrap für einfache Formatierungen hinzu. Außerdem wird ein globales Layout mit einer Navigationsleiste definiert.
Öffnen Sie ./public/stylesheets/style.css , und ersetzen Sie den gesamten Inhalt durch Folgendes.
body { padding-top: 4.5rem; } .alert-pre { word-wrap: break-word; word-break: break-all; white-space: pre-wrap; }Open ./views/index.hbs and replace its contents with the following.
<div class="jumbotron"> <h1>Node.js Graph Tutorial</h1> <p class="lead">This sample app shows how to use the Microsoft Graph API to access a user's data from Node.js</p> {{#if user}} <h4>Welcome {{ user.displayName }}!</h4> <p>Use the navigation bar at the top of the page to get started.</p> {{else}} <a href="/auth/signin" class="btn btn-primary btn-large">Click here to sign in</a> {{/if}} </div>Öffnen Sie ./routes/index.js , und ersetzen Sie den vorhandenen Code durch Folgendes.
var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { let params = { active: { home: true } }; res.render('index', params); }); module.exports = router;Fügen Sie eine Bilddatei Ihrer Wahl namens no-profile-photo.png im Verzeichnis ./public/images hinzu. Dieses Bild wird als Foto des Benutzers verwendet, wenn der Benutzer kein Foto in Microsoft Graph hat.
Speichern Sie alle Änderungen, und starten Sie den Server neu. Jetzt sollte die App ganz anders aussehen.

Registrieren der App im Portal
In dieser Übung erstellen Sie eine neue Azure AD-Webanwendungsregistrierung über das Azure Active Directory Admin Center.
Öffnen Sie einen Browser, und navigieren Sie zum Azure Active Directory Admin Center. Melden Sie sich mit einem persönlichen Konto (auch: Microsoft-Konto) oder einem Geschäfts- oder Schulkonto an.
Wählen Sie in der linken Navigationsleiste Azure Active Directory aus, und wählen Sie dann App-Registrierungen unter Verwalten aus.

Wählen Sie Neue Registrierung aus. Legen Sie auf der Seite Anwendung registrieren die Werte wie folgt fest.
- Legen Sie Name auf
Node.js Graph Tutorialfest. - Legen Sie Unterstützte Kontotypen auf Konten in allen Organisationsverzeichnissen und persönliche Microsoft-Konten fest.
- Legen Sie unter Umleitungs-URI die erste Dropdownoption auf
Webfest, und legen Sie den Wert aufhttp://localhost:3000/auth/callbackfest.

- Legen Sie Name auf
Wählen Sie Registrieren aus. Kopieren Sie auf der SeiteNode.js Graph Lernprogramm den Wert der Anwendungs-ID (Client-ID), und speichern Sie sie, sie benötigen Sie im nächsten Schritt.

Wählen Sie unter Verwalten die Option Zertifikate und Geheime Clientschlüssel aus. Wählen Sie die Schaltfläche Neuen geheimen Clientschlüssel aus. Geben Sie einen Wert in Beschreibung ein, wählen Sie eine der Optionen für Gilt bis aus, und wählen Sie dann Hinzufügen aus.

Kopieren Sie den Wert des geheimen Clientschlüssels, bevor Sie diese Seite verlassen. Sie benötigen ihn im nächsten Schritt.
Wichtig
Dieser geheime Clientschlüssel wird nicht noch einmal angezeigt, stellen Sie daher sicher, dass Sie ihn jetzt kopieren.

Hinzufügen der Azure AD-Authentifizierung
In dieser Übung erweitern Sie die Anwendung aus der vorherigen Übung, um die Authentifizierung mit Azure AD zu unterstützen. Dies ist erforderlich, um das erforderliche OAuth-Zugriffstoken zum Aufrufen der Microsoft Graph abzurufen. In diesem Schritt integrieren Sie die Msal-Node-Bibliothek in die Anwendung.
Erstellen Sie eine neue Datei mit dem Namen ".env " im Stammverzeichnis der Anwendung, und fügen Sie den folgenden Code hinzu.
OAUTH_APP_ID=YOUR_APP_ID_HERE OAUTH_APP_SECRET=YOUR_CLIENT_SECRET_HERE OAUTH_REDIRECT_URI=http://localhost:3000/auth/callback OAUTH_SCOPES='user.read,calendars.readwrite,mailboxsettings.read' OAUTH_AUTHORITY=https://login.microsoftonline.com/common/Ersetzen Sie
YOUR_CLIENT_ID_HEREdies durch die Anwendungs-ID aus dem Anwendungsregistrierungsportal, und ersetzen Sie sieYOUR_CLIENT_SECRET_HEREdurch den von Ihnen generierten geheimen Clientschlüssel.Wichtig
Wenn Sie die Quellcodeverwaltung wie Git verwenden, wäre jetzt ein guter Zeitpunkt, um die .env-Datei aus der Quellcodeverwaltung auszuschließen, um zu vermeiden, dass versehentlich Ihre App-ID und Ihr Kennwort offengelegt werden.
Öffnen Sie ./app.js , und fügen Sie die folgende Zeile am Anfang der Datei hinzu, um die ENV-Datei zu laden.
require('dotenv').config();
Implementieren der Anmeldung
Suchen Sie die Zeile
var app = express();in ./app.js. Fügen Sie nach dieser Zeile den folgenden Code ein.// In-memory storage of logged-in users // For demo purposes only, production apps should store // this in a reliable storage app.locals.users = {}; // MSAL config const msalConfig = { auth: { clientId: process.env.OAUTH_APP_ID, authority: process.env.OAUTH_AUTHORITY, clientSecret: process.env.OAUTH_APP_SECRET }, system: { loggerOptions: { loggerCallback(loglevel, message, containsPii) { console.log(message); }, piiLoggingEnabled: false, logLevel: msal.LogLevel.Verbose, } } }; // Create msal application object app.locals.msalClient = new msal.ConfidentialClientApplication(msalConfig);Dieser Code initialisiert die Msal-Node-Bibliothek mit der App-ID und dem Kennwort für die App.
Erstellen Sie eine neue Datei im Verzeichnis "./routes " mit dem Namen auth.js , und fügen Sie den folgenden Code hinzu.
const router = require('express-promise-router')(); /* GET auth callback. */ router.get('/signin', async function (req, res) { const urlParameters = { scopes: process.env.OAUTH_SCOPES.split(','), redirectUri: process.env.OAUTH_REDIRECT_URI }; try { const authUrl = await req.app.locals .msalClient.getAuthCodeUrl(urlParameters); res.redirect(authUrl); } catch (error) { console.log(`Error: ${error}`); req.flash('error_msg', { message: 'Error getting auth URL', debug: JSON.stringify(error, Object.getOwnPropertyNames(error)) }); res.redirect('/'); } } ); router.get('/callback', async function(req, res) { const tokenRequest = { code: req.query.code, scopes: process.env.OAUTH_SCOPES.split(','), redirectUri: process.env.OAUTH_REDIRECT_URI }; try { const response = await req.app.locals .msalClient.acquireTokenByCode(tokenRequest); // TEMPORARY! // Flash the access token for testing purposes req.flash('error_msg', { message: 'Access token', debug: response.accessToken }); } catch (error) { req.flash('error_msg', { message: 'Error completing authentication', debug: JSON.stringify(error, Object.getOwnPropertyNames(error)) }); } res.redirect('/'); } ); router.get('/signout', async function(req, res) { // Sign out if (req.session.userId) { // Look up the user's account in the cache const accounts = await req.app.locals.msalClient .getTokenCache() .getAllAccounts(); const userAccount = accounts.find(a => a.homeAccountId === req.session.userId); // Remove the account if (userAccount) { req.app.locals.msalClient .getTokenCache() .removeAccount(userAccount); } } // Destroy the user's session req.session.destroy(function (err) { res.redirect('/'); }); } ); module.exports = router;Dadurch wird ein Router mit drei Routen definiert:
signin,callbackundsignout.Die
signinRoute ruft diegetAuthCodeUrlFunktion auf, um die Anmelde-URL zu generieren, und leitet dann den Browser zu dieser URL weiter.Die
callbackRoute ist der Ort, an dem Azure nach Abschluss der Anmeldung umleitet. Der Code ruft dieacquireTokenByCodeFunktion auf, um den Autorisierungscode gegen ein Zugriffstoken auszutauschen. Sobald das Token abgerufen wurde, wird es zurück zur Startseite mit dem Zugriffstoken im temporären Fehlerwert umgeleitet. Wir verwenden dies, um zu überprüfen, ob unsere Anmeldung funktioniert, bevor wir fortfahren. Bevor wir testen, müssen wir die Express-App so konfigurieren, dass sie den neuen Router von ./routes/auth.js verwendet.Die
signoutMethode protokolliert den Benutzer und zerstört die Sitzung.Öffnen Sie ./app.js , und fügen Sie den folgenden Code vor der
var app = express();Zeile ein.const authRouter = require('./routes/auth');Fügen Sie den folgenden Code nach der
app.use('/', indexRouter);Zeile ein.app.use('/auth', authRouter);
Starten Sie den Server, und navigieren Sie zu https://localhost:3000. Klicken Sie auf die Schaltfläche zum Anmelden, um zu https://login.microsoftonline.comweitergeleitet zu werden. Melden Sie sich mit Ihrem Microsoft-Konto an, und stimmen Sie den angeforderten Berechtigungen zu. Der Browser leitet zur App um, in der Sie das Token sehen.
Benutzerdetails abrufen
Erstellen Sie eine neue Datei im Stammverzeichnis des Projekts mit dem Namen graph.js , und fügen Sie den folgenden Code hinzu.
var graph = require('@microsoft/microsoft-graph-client'); require('isomorphic-fetch'); module.exports = { getUserDetails: async function(msalClient, userId) { const client = getAuthenticatedClient(msalClient, userId); const user = await client .api('/me') .select('displayName,mail,mailboxSettings,userPrincipalName') .get(); return user; }, }; function getAuthenticatedClient(msalClient, userId) { if (!msalClient || !userId) { throw new Error( `Invalid MSAL state. Client: ${msalClient ? 'present' : 'missing'}, User ID: ${userId ? 'present' : 'missing'}`); } // Initialize Graph client const client = graph.Client.init({ // Implement an auth provider that gets a token // from the app's MSAL instance authProvider: async (done) => { try { // Get the user's account const account = await msalClient .getTokenCache() .getAccountByHomeId(userId); if (account) { // Attempt to get the token silently // This method uses the token cache and // refreshes expired tokens as needed const response = await msalClient.acquireTokenSilent({ scopes: process.env.OAUTH_SCOPES.split(','), redirectUri: process.env.OAUTH_REDIRECT_URI, account: account }); // First param to callback is the error, // Set to null in success case done(null, response.accessToken); } } catch (err) { console.log(JSON.stringify(err, Object.getOwnPropertyNames(err))); done(err, null); } } }); return client; }Dadurch wird die
getUserDetailsFunktion exportiert, die das Microsoft Graph SDK verwendet, um den/meEndpunkt aufzurufen und das Ergebnis zurückzugeben.Öffnen Sie ./routes/auth.js , und fügen Sie die folgenden
requireAnweisungen am Anfang der Datei hinzu.const graph = require('../graph');Ersetzen Sie die vorhandene Rückrufroute durch den folgenden Code.
router.get('/callback', async function(req, res) { const tokenRequest = { code: req.query.code, scopes: process.env.OAUTH_SCOPES.split(','), redirectUri: process.env.OAUTH_REDIRECT_URI }; try { const response = await req.app.locals .msalClient.acquireTokenByCode(tokenRequest); // Save the user's homeAccountId in their session req.session.userId = response.account.homeAccountId; const user = await graph.getUserDetails(response.accessToken); // Add the user to user storage req.app.locals.users[req.session.userId] = { displayName: user.displayName, email: user.mail || user.userPrincipalName, timeZone: user.mailboxSettings.timeZone }; } catch(error) { req.flash('error_msg', { message: 'Error completing authentication', debug: JSON.stringify(error, Object.getOwnPropertyNames(error)) }); } res.redirect('/'); } );Der neue Code speichert die Konto-ID des Benutzers in der Sitzung, ruft die Details des Benutzers von Microsoft Graph ab und speichert sie im Benutzerspeicher der App.
Starten Sie den Server neu, und durchlaufen Sie den Anmeldevorgang. Sie sollten wieder auf der Startseite angezeigt werden, aber die Benutzeroberfläche sollte geändert werden, um anzugeben, dass Sie angemeldet sind.

Klicken Sie auf den Benutzer-Avatar in der oberen rechten Ecke, um auf den Abmeldelink zuzugreifen. Wenn Sie auf Abmelden klicken, wird die Sitzung zurückgesetzt und Sie kehren zur Startseite zurück.

Speichern und Aktualisieren von Token
An diesem Punkt verfügt Ihre Anwendung über ein Zugriffstoken, das in der Authorization Kopfzeile von API-Aufrufen gesendet wird. Dies ist das Token, mit dem die App im Namen des Benutzers auf die Microsoft-Graph zugreifen kann.
Dieses Token ist jedoch nur kurzzeitig verfügbar. Das Token läuft eine Stunde nach der Ausstellung ab. An dieser Stelle kommt das Aktualisierungstoken ins Spiel. Die OAuth-Spezifikation führt ein Aktualisierungstoken ein, mit dem die App ein neues Zugriffstoken anfordern kann, ohne dass sich der Benutzer erneut anmelden muss.
Da die App das Msal-Node-Paket verwendet, müssen Sie keine Tokenspeicher- oder Aktualisierungslogik implementieren. Die App verwendet den standardmäßigen Msal-Node-Tokencache im Arbeitsspeicher, der für eine Beispielanwendung ausreichend ist. Produktionsanwendungen sollten ein eigenes Caching-Plug-In bereitstellen, um den Tokencache auf einem sicheren, zuverlässigen Speichermedium zu serialisieren.
Abrufen einer Kalenderansicht
In dieser Übung integrieren Sie Microsoft Graph in die Anwendung. Für diese Anwendung verwenden Sie die Microsoft-Graph-Clientbibliothek, um Aufrufe an Microsoft Graph zu tätigen.
Abrufen von Kalenderereignissen von Outlook
Öffnen Sie ./graph.js, und fügen Sie die folgende Funktion in .
module.exportsgetCalendarView: async function(accessToken, start, end, timeZone) { const client = getAuthenticatedClient(accessToken); const events = await client .api('/me/calendarview') // Add Prefer header to get back times in user's timezone .header("Prefer", `outlook.timezone="${timeZone}"`) // Add the begin and end of the calendar window .query({ startDateTime: start, endDateTime: end }) // Get just the properties used by the app .select('subject,organizer,start,end') // Order by start time .orderby('start/dateTime') // Get at most 50 results .top(50) .get(); return events; },Überlegen Sie sich, was dieser Code macht.
- Die URL, die aufgerufen wird, lautet
/me/calendarview. - Die
headerMethode fügt der Anforderung denPrefer: outlook.timezoneHeader hinzu, wodurch die Start- und Endzeiten in der Zeitzone des Benutzers zurückgegeben werden. - Die
queryMethode legt diestartDateTimeParameter für die Kalenderansicht festendDateTime. - Die
selectMethode beschränkt die für jedes Ereignis zurückgegebenen Felder auf die Felder, die tatsächlich von der Ansicht verwendet werden. - Die
orderbyMethode sortiert die Ergebnisse nach der Startzeit. - Die
topMethode beschränkt die Ergebnisse auf 50 Ereignisse.
- Die URL, die aufgerufen wird, lautet
Erstellen Sie eine neue Datei im Verzeichnis ./routes mit dem Namen calendar.js, und fügen Sie den folgenden Code hinzu.
const router = require('express-promise-router')(); const graph = require('../graph.js'); const addDays = require('date-fns/addDays'); const formatISO = require('date-fns/formatISO'); const startOfWeek = require('date-fns/startOfWeek'); const zonedTimeToUtc = require('date-fns-tz/zonedTimeToUtc'); const iana = require('windows-iana'); const { body, validationResult } = require('express-validator'); const validator = require('validator'); /* GET /calendar */ router.get('/', async function(req, res) { if (!req.session.userId) { // Redirect unauthenticated requests to home page res.redirect('/') } else { const params = { active: { calendar: true } }; // Get the user const user = req.app.locals.users[req.session.userId]; // Convert user's Windows time zone ("Pacific Standard Time") // to IANA format ("America/Los_Angeles") const timeZoneId = iana.findIana(user.timeZone)[0]; console.log(`Time zone: ${timeZoneId.valueOf()}`); // Calculate the start and end of the current week // Get midnight on the start of the current week in the user's timezone, // but in UTC. For example, for Pacific Standard Time, the time value would be // 07:00:00Z var weekStart = zonedTimeToUtc(startOfWeek(new Date()), timeZoneId.valueOf()); var weekEnd = addDays(weekStart, 7); console.log(`Start: ${formatISO(weekStart)}`); try { // Get the events const events = await graph.getCalendarView( req.app.locals.msalClient, req.session.userId, formatISO(weekStart), formatISO(weekEnd), user.timeZone); res.json(events.value); } catch (err) { res.send(JSON.stringify(err, Object.getOwnPropertyNames(err))); } } } ); module.exports = router;Aktualisieren Sie ./app.js , um diese neue Route zu verwenden. Fügen Sie die folgende Zeile vor der
var app = express();Zeile hinzu.const calendarRouter = require('./routes/calendar');Fügen Sie die folgende Zeile nach der
app.use('/auth', authRouter);Zeile hinzu.app.use('/calendar', calendarRouter);Starten Sie den Server neu. Melden Sie sich an, und klicken Sie auf den Kalenderlink in der Navigationsleiste. Wenn alles funktioniert, sollte ein JSON-Abbild von Ereignissen im Kalender des Benutzers angezeigt werden.
Anzeigen der Ergebnisse
Jetzt können Sie eine Ansicht hinzufügen, um die Ergebnisse benutzerfreundlicher anzuzeigen.
Fügen Sie den folgenden Code in ./app.js nach der
app.set('view engine', 'hbs');Zeile hinzu.var hbs = require('hbs'); var parseISO = require('date-fns/parseISO'); var formatDate = require('date-fns/format'); // Helper to format date/time sent by Graph hbs.registerHelper('eventDateTime', function(dateTime) { const date = parseISO(dateTime); return formatDate(date, 'M/d/yy h:mm a'); });Dadurch wird ein Handlebars-Hilfsprogramm implementiert, um das von Microsoft Graph zurückgegebene ISO 8601-Datum in etwas Benutzerfreundlicheres zu formatieren.
Create a new file in the ./views directory named calendar.hbs and add the following code.
<h1 class="mb-3">Calendar</h1> <a href="/calendar/new" class="btn btn-light btn-sm mb-3">New event</a> <table class="table"> <thead> <tr> <th scope="col">Organizer</th> <th scope="col">Subject</th> <th scope="col">Start</th> <th scope="col">End</th> </tr> </thead> <tbody> {{#each events}} <tr> <td>{{this.organizer.emailAddress.name}}</td> <td>{{this.subject}}</td> <td>{{eventDateTime this.start.dateTime}}</td> <td>{{eventDateTime this.end.dateTime}}</td> </tr> {{/each}} </tbody> </table>Dadurch wird eine Ereignissammlung durchlaufen und jedem Ereignis wird jeweils eine Tabellenzeile hinzugefügt.
Aktualisieren Sie nun die Route in ./routes/calendar.js , um diese Ansicht zu verwenden. Ersetzen Sie die vorhandene Route durch den folgenden Code.
/* GET /calendar */ router.get('/', async function(req, res) { if (!req.session.userId) { // Redirect unauthenticated requests to home page res.redirect('/') } else { const params = { active: { calendar: true } }; // Get the user const user = req.app.locals.users[req.session.userId]; // Convert user's Windows time zone ("Pacific Standard Time") // to IANA format ("America/Los_Angeles") const timeZoneId = iana.findIana(user.timeZone)[0]; console.log(`Time zone: ${timeZoneId.valueOf()}`); // Calculate the start and end of the current week // Get midnight on the start of the current week in the user's timezone, // but in UTC. For example, for Pacific Standard Time, the time value would be // 07:00:00Z var weekStart = zonedTimeToUtc(startOfWeek(new Date()), timeZoneId.valueOf()); var weekEnd = addDays(weekStart, 7); console.log(`Start: ${formatISO(weekStart)}`); // Get the access token var accessToken; try { accessToken = await getAccessToken(req.session.userId, req.app.locals.msalClient); } catch (err) { req.flash('error_msg', { message: 'Could not get access token. Try signing out and signing in again.', debug: JSON.stringify(err, Object.getOwnPropertyNames(err)) }); return; } if (accessToken && accessToken.length > 0) { try { // Get the events const events = await graph.getCalendarView( accessToken, formatISO(weekStart), formatISO(weekEnd), user.timeZone); params.events = events.value; } catch (err) { req.flash('error_msg', { message: 'Could not fetch events', debug: JSON.stringify(err, Object.getOwnPropertyNames(err)) }); } } else { req.flash('error_msg', 'Could not get an access token'); } res.render('calendar', params); } } );Speichern Sie Ihre Änderungen, starten Sie den Server neu, und melden Sie sich bei der App an. Klicken Sie auf den Kalenderlink , und die App sollte nun eine Tabelle mit Ereignissen rendern.

Erstellen eines neuen Ereignisses
In diesem Abschnitt fügen Sie die Möglichkeit hinzu, Ereignisse im Kalender des Benutzers zu erstellen.
Erstellen eines neuen Ereignisformulars
Erstellen Sie eine neue Datei im Verzeichnis "./views" mit dem Namen "newevent.hbs", und fügen Sie den folgenden Code hinzu.
<form method="POST"> <div class="form-group"> <label>Subject</label> <input class="form-control" name="ev-subject" type="text" value="{{ newEvent.subject }}"> </div> <div class="form-group"> <label>Attendees</label> <input class="form-control" name="ev-attendees" type="text" value="{{ newEvent.attendees }}"> </div> <div class="form-row"> <div class="col"> <div class="form-group"> <label>Start</label> <input class="form-control" name="ev-start" type="datetime-local" value="{{ newEvent.start }}"> </div> </div> <div class="col"> <div class="form-group"> <label>End</label> <input class="form-control" name="ev-end" type="datetime-local" value="{{ newEvent.end }}"> </div> </div> </div> <div class="form-group mb-3"> <label>Body</label> <textarea class="form-control" name="ev-body" rows="3">{{ newEvent.body }}</textarea> </div> <input class="btn btn-primary mr-2" type="submit" value="Create" /> <a class="btn btn-secondary" href="/calendar">Cancel</a> </form>Fügen Sie der ./routes/calendar.js-Datei vor der Zeile den folgenden Code
module.exports = router;hinzu./* GET /calendar/new */ router.get('/new', function(req, res) { if (!req.session.userId) { // Redirect unauthenticated requests to home page res.redirect('/') } else { res.locals.newEvent = {}; res.render('newevent'); } } );
Dadurch wird ein Formular für die Benutzereingabe implementiert und gerendert.
Erstellen des Ereignisses
Öffnen Sie ./graph.js, und fügen Sie die folgende Funktion in
module.exports.createEvent: async function(accessToken, formData, timeZone) { const client = getAuthenticatedClient(accessToken); // Build a Graph event const newEvent = { subject: formData.subject, start: { dateTime: formData.start, timeZone: timeZone }, end: { dateTime: formData.end, timeZone: timeZone }, body: { contentType: 'text', content: formData.body } }; // Add attendees if present if (formData.attendees) { newEvent.attendees = []; formData.attendees.forEach(attendee => { newEvent.attendees.push({ type: 'required', emailAddress: { address: attendee } }); }); } // POST /me/events await client .api('/me/events') .post(newEvent); },Dieser Code verwendet die Formularfelder, um ein Graph Ereignisobjekt zu erstellen, und sendet dann eine POST-Anforderung an den
/me/eventsEndpunkt, um das Ereignis im Standardkalender des Benutzers zu erstellen.Fügen Sie der ./routes/calendar.js-Datei vor der Zeile den folgenden Code
module.exports = router;hinzu./* POST /calendar/new */ router.post('/new', [ body('ev-subject').escape(), // Custom sanitizer converts ;-delimited string // to an array of strings body('ev-attendees').customSanitizer(value => { return value.split(';'); // Custom validator to make sure each // entry is an email address }).custom(value => { value.forEach(element => { if (!validator.isEmail(element)) { throw new Error('Invalid email address'); } }); return true; }), // Ensure start and end are ISO 8601 date-time values body('ev-start').isISO8601(), body('ev-end').isISO8601(), body('ev-body').escape() ], async function(req, res) { if (!req.session.userId) { // Redirect unauthenticated requests to home page res.redirect('/') } else { // Build an object from the form values const formData = { subject: req.body['ev-subject'], attendees: req.body['ev-attendees'], start: req.body['ev-start'], end: req.body['ev-end'], body: req.body['ev-body'] }; // Check if there are any errors with the form values const formErrors = validationResult(req); if (!formErrors.isEmpty()) { let invalidFields = ''; formErrors.errors.forEach(error => { invalidFields += `${error.param.slice(3, error.param.length)},` }); // Preserve the user's input when re-rendering the form // Convert the attendees array back to a string formData.attendees = formData.attendees.join(';'); return res.render('newevent', { newEvent: formData, error: [{ message: `Invalid input in the following fields: ${invalidFields}` }] }); } // Get the access token var accessToken; try { accessToken = await getAccessToken(req.session.userId, req.app.locals.msalClient); } catch (err) { req.flash('error_msg', { message: 'Could not get access token. Try signing out and signing in again.', debug: JSON.stringify(err, Object.getOwnPropertyNames(err)) }); return; } // Get the user const user = req.app.locals.users[req.session.userId]; // Create the event try { await graph.createEvent(accessToken, formData, user.timeZone); } catch (error) { req.flash('error_msg', { message: 'Could not create event', debug: JSON.stringify(error, Object.getOwnPropertyNames(error)) }); } // Redirect back to the calendar view return res.redirect('/calendar'); } } );Dieser Code überprüft und bereinigt die Formulareingabe und ruft dann
graph.createEventzum Erstellen des Ereignisses auf. Es wird zurück zur Kalenderansicht umgeleitet, nachdem der Anruf abgeschlossen wurde.Speichern Sie die Änderungen, und starten Sie die App neu. Klicken Sie auf das Kalendernavigationsleistenelement und dann auf die Schaltfläche "Ereignis erstellen". Geben Sie die Werte ein, und klicken Sie auf "Erstellen". Die App kehrt zur Kalenderansicht zurück, nachdem das neue Ereignis erstellt wurde.

Herzlichen Glückwunsch!
Sie haben das Lernprogramm Node.js Microsoft Graph abgeschlossen. Da Sie nun über eine funktionierende App verfügen, die Microsoft Graph aufruft, können Sie experimentieren und neue Features hinzufügen. Besuchen Sie die Übersicht über Microsoft Graph, um alle Daten anzuzeigen, auf die Sie mit Microsoft Graph zugreifen können.
Feedback
Bitte geben Sie Feedback zu diesem Lernprogramm im GitHub Repository.
Liegt ein Problem mit diesem Abschnitt vor? Wenn ja, senden Sie uns Feedback, damit wir den Abschnitt verbessern können.