node.jssharepointmicrosoft-graph-apionedrivemicrosoft-graph-files

create upload session to sharepoint site folder on Office 365 using microsoft graph


TLDR;

I have several Office 365 SharePoint sites. I would like my NodeJS based daemon that is registered as an Azure App Registration to write to several different folders within these SharePoint sites. I am struggling to get the correct syntax of the request url correct to create a resumable upload session. This is made worse by fairly unclear and non working examples provided by Microsoft. An important detail here is that the application is doing this without user delegation, meaning no user is using the application to cause these files but rather it is and should be the entity who owns the file uploads. Many examples show how to do this with user delegation. Some of the files being uploaded include binary or text content.

I have read quite a bit of the ms docs and referred to many questions. I even watched many videos on YouTube and started digging through source code with the microsoft-graph-client incase there were insights to be had there. I don't think I can review graph server code or if that would be helpful. Below are just a few that I still I have open due to being closely related.

Here is my latest attempt which I feel should be getting pretty close:

const msal = require('@azure/msal-node');
const graph = require( '@microsoft/microsoft-graph-client' );
// all that client initialization and authentication work so I omited it for this example

const fileName = 'fileName.txt';
const filePath = 'path/to/file/' + fileName;
const stats = fs.statSync( filePath );
const totalSize = stats.size;
const readStream = fs.createReadStream( filePath );
const fileObject = new graph.StreamUpload( readStream, fileName, totalSize );

const site_id = '' // refers to the identifier for the SharePoint site, something like domain,guid,guid
const drive_id = '' // refers to the site specific drive that would sync to the Corp OneDrive folder
const item_id = '' // refers to the ID for a target folder
let folder1, folder2, folder3 // refer to possible folders. I assumed I could just specify the ID of the last one to upload into it

requestUrl = `https://graph.microsoft.com/v1.0/sites/${ site_id }/drives/${ drive_id }/items/${ item_id }/${ fileName }:/createuploadsession`
// OR
requestUrl = `https://graph.microsoft.com/v1.0/drives/${ drive_id }/items/${ item_id }/${ fileName }:/createuploadsession`
// OR
requestUrl = `https://graph.microsoft.com/v1.0/drives/${ drive_id }/root:/${ folder1 }/${ fileName }:/createuploadsession`
// OR
requestUrl = `https://graph.microsoft.com/v1.0/drives/${ drive_id }/root:/${ folder1 }:/${ fileName }:/createuploadsession`
// OR
requestUrl = `https://graph.microsoft.com/v1.0/drives/${ drive_id }/root:/${ folder1 }/${ folder2 }/${ fileName }:/createuploadsession`
// OR
requestUrl = `https://graph.microsoft.com/v1.0/drives/${ drive_id }/root:/${ folder1 }/${ folder2 }/${ folder3 }/${ fileName }:/createuploadsession`


const uploadSession = await new graph.LargeFileUploadTask.createUploadSession( client, requestUrl, payload );
// here usually results in (node:0) UnhandledPromiseRejectionWarning: Error: Invalid request
const uploadTask = await graph.LargeFileUploadTask( client, fileObject, uploadSession, options );
const uploadResult = await uploadTask.upload();
console.log( uploadResult?.responseBody || uploadResult )

Application Stack:

Quick site and folder overview:

Project 1:

  1. We have many projects that come and go so we separate the files for these projects and the people who are involved with these projects. I don't get to make changes to how we structure projects as this has been a long standing thing and there are many reasons behind it. Comments to /dev/null for this topic.
  2. Contains a Documents library where we sync files using OneDrive for Business
  3. A Teams site has been created for this where we create channels that create folders that we can access in Project 1 - Documents through OneDrive
  4. Would like to upload files to any level of the folder structure here.

Project 2: etc

Team 1:

  1. Same as the projects but here we do team specific interactions.
  2. We have many teams which equates to departments and sub departments within the company.
  3. Would like to upload files to any level of the folder structure here. Very likely the IDs for the folders are going to be long term but in the event someone deletes them, we should probably find the ID of the folder based upon the path.

Team 2: etc

Here is how we see it from OneDrive:

enter image description here

And in Teams:

enter image description here


Solution

  • I had to add a colon after the folder_id for the 2nd requestUrl format. Also the await graph.LargeFileUploadTask had to become new graph.LargeFileUploadTask

    const msal = require('@azure/msal-node');
    const graph = require( '@microsoft/microsoft-graph-client' );
    // all that client initialization and authentication work so I omited it for this example
    
    const fileName = 'fileName.txt';
    const filePath = 'path/to/file/' + fileName;
    const stats = fs.statSync( filePath );
    const totalSize = stats.size;
    const readStream = fs.createReadStream( filePath );
    const fileObject = new graph.StreamUpload( readStream, fileName, totalSize );
    
    const site_id = '' // refers to the identifier for the SharePoint site, something like domain,guid,guid
    const drive_id = '' // refers to the site specific drive that would sync to the Corp OneDrive folder
    const folder_id = '' // refers to the ID for a target folder
    
    requestUrl = `https://graph.microsoft.com/v1.0/drives/${ drive_id }/items/${ folder_id }:/${ fileName }:/createuploadsession`
    
    
    const uploadSession = await new graph.LargeFileUploadTask.createUploadSession( client, requestUrl, payload );
    // here usually results in (node:0) UnhandledPromiseRejectionWarning: Error: Invalid request
    const uploadTask = new graph.LargeFileUploadTask( client, fileObject, uploadSession, options );
    const uploadResult = await uploadTask.upload();
    console.log( uploadResult?.responseBody || uploadResult )