Closed oshihirii closed 9 months ago
Update:
I changed two main things in the Azure Function code above and am now getting a successful response.
The modified code is at the end of this message.
It includes examples of passing data through to the endpoint via either query parameters or the request body.
Change 01) I changed the syntax of the Graph API endpoint from this:
const graphRequestURL = `https://graph.microsoft.com/v1.0/sites/${target_site_id}/lists/${target_library_id}/items?$expand=fields&$filter=ChoiceCol01/Value eq '${choice_col_01_value}' and ChoiceCol02/Value eq '${choice_col_02_value}'`;
to this (just added fields/
before the column name):
const graphRequestURL = `https://graph.microsoft.com/v1.0/sites/${target_site_id}/lists/${target_library_id}/items?$expand=fields&$filter=fields/ChoiceCol01/Value eq '${choice_col_01_value}' and fields/ChoiceCol02/Value eq '${choice_col_02_value}'`;
Change 02) I indexed the SharePoint library columns that I wanted to filter on.
However I am still uncomfortable about the following behavior in Azure Functions:
await
keyword is halting executing like I believe it should context.log()
statements are logged in a seemingly random order which makes it very difficult to 'see' how the function is progressing return { body: JSON.stringify(graphResponse.data) }
, Postman shows that the response Content-Type
is text/plain;charset=UTF-8
, even though in the request headers, Content-Type
is specified as application/json
. $expand
, $filter
, $select
and fields
I would love it if anyone could alleviate, explain or resolve any of the above concerns so that I could confidently use Azure Functions.
Otherwise, it seems it would be a lot less stress to set up a conventional web app with backend Node.js/Express server etc so that I could confidently use my normal approach, conventions and folder structure when developing apps in Node.js and not be 'caught out' by any unexpected behavior that may exist in Azure Functions.
Modified Azure Function Code
// required libraries
const { app } = require('@azure/functions');
// to handle token management
const msal = require('@azure/msal-node');
// to make requests
const axios = require('axios');
app.http('MyHttpTriggerFunction', {
methods: ['GET', 'POST'],
authLevel: 'function',
handler: async (request, context) => {
try {
// sanity check to see if this function is being called when requested
context.log(`The http function processed a request for the req.url "${request.url}"`);
/*
below are two approaches to sending data to the endpoint using a POST request
01. via query parameters (values are displayed in the request URL)
02. in the request body (values are not displayed in the request URL)
*/
// // BEGIN approach 01: using query parameters - this works if values are passed through in query parameters
// const requestQueryParams = request.query;
// context.log(requestQueryParams);
// // get target_site_id from the request query parameters
// const target_site_id = requestQueryParams.get('target_site_id');
// // get target_library_id from the request query parameters
// const target_library_id = requestQueryParams.get('target_library_id');
// // get choice_col_01_value from the request query parameters
// const choice_col_01_value = requestQueryParams.get('choice_col_01_value');
// // get choice_col_02_value from the request query parameters
// const choice_col_02_value = requestQueryParams.get('choice_col_02_value');
// // END approach 01: using query parameters
// BEGIN approach 02: using request body - this works if values are passed through in request body
const requestData = await request.json();
// get target_site_id from the request body
const target_site_id = requestData.target_site_id;
// get target_library_id from the request body
const target_library_id = requestData.target_library_id;
// get choice_col_01_value from the request body
const choice_col_01_value = requestData.choice_col_01_value;
// get choice_col_02_value from the request body
const choice_col_02_value = requestData.choice_col_02_value;
// END approach 02: using request body
// all of the context.log() statements from this point onwards are displayed in a seemingly random order, i.e not synchronously
// this makes it very confusing to ascertain how the function is progressing
context.log("target_site_id:");
context.log(target_site_id);
context.log("target_library_id:");
context.log(target_library_id);
context.log("choice_col_01_value:");
context.log(choice_col_01_value);
context.log("choice_col_02_value:");
context.log(choice_col_02_value);
const msal_config = {
auth: {
clientId: process.env["azure_ad_app_registration_client_id"],
authority: `https://login.microsoftonline.com/${process.env["azure_ad_app_registration_tenant_id"]}`,
clientSecret: process.env["azure_ad_app_registration_client_secret"],
}
};
// create MSAL client instance
const cca = new msal.ConfidentialClientApplication(msal_config);
// acquire token
const clientCredentialRequest = {
scopes: ["https://graph.microsoft.com/.default"],
};
const response = await cca.acquireTokenByClientCredential(clientCredentialRequest);
const token = response.accessToken;
context.log("token:");
context.log(token);
// get all library items - this works
// const graphRequestURL = `https://graph.microsoft.com/v1.0/sites/${target_site_id}/lists/${target_library_id}/items`;
// get matching library items - this works ONLY if the SharePoint library columns you are filtering on have been indexed!
// const graphRequestURL = `https://graph.microsoft.com/v1.0/sites/${target_site_id}/lists/${target_library_id}/items?$expand=fields&$filter=fields/ChoiceCol01/Value eq '${choice_col_01_value}' and fields/ChoiceCol02/Value eq '${choice_col_02_value}'`;
// get matching library items and only return selected fields as well as the default fields returned, which it does not seem possible to suppress
// as above - this works ONLY if the SharePoint library columns you are filtering on have been indexed!
const graphRequestURL = `https://graph.microsoft.com/v1.0/sites/${target_site_id}/lists/${target_library_id}/items?$expand=fields($select=ChoiceCol01,ChoiceCol02,Slot01,Slot02,Slot03)&$filter=fields/ChoiceCol01/Value eq '${choice_col_01_value}' and fields/ChoiceCol02/Value eq '${choice_col_02_value}'`;
// this works when the columns are indexed, but it only returns basic details about the file:
// const graphRequestURL = `https://graph.microsoft.com/v1.0/sites/${target_site_id}/lists/${target_library_id}/items?$filter=fields/ChoiceCol01/Value eq '${choice_col_01_value}' and fields/ChoiceCol02/Value eq '${choice_col_02_value}'`;
context.log("graphRequestURL");
context.log(graphRequestURL);
const graphResponse = await axios.get(graphRequestURL, {
headers: {
Authorization: `Bearer ${token}`
}
});
context.log("This is JSON.stringify(graphResponse.data):");
context.log(JSON.stringify(graphResponse.data));
// return { body: graphResponse.data } <--- this returns [object Object]
return { body: JSON.stringify(graphResponse.data) }
} catch (error) {
context.res = {
status: 500,
body: `Error: ${error.message || error}`
};
}
}
});
I do not trust that the
await
keyword is halting executing like I believe it should
I'm not sure what you mean by "halting execution". Can you expand on your desired behavior? There's nothing unique to Azure Functions in terms of how the await
works - that's just pure Node.js.
the
context.log()
statements are logged in a seemingly random order which makes it very difficult to 'see' how the function is progressing
This is unfortunately a known issue. @brettsam is actively working on this, related to https://github.com/Azure/azure-functions-host/issues/9238. Apparently some improvements to performance made a while ago caused this behavior, and they want to be careful when fixing it to make sure they don't lose the performance gains.
I expected that Azure Functions would work 'just like Node.js', but am concerned that standard features may be missing and/or that there are layers of abstraction code that need to be implemented to get things working
There will be some abstractions necessary, but we are getting closer and closer to "just like Node.js" every day. If you notice any standard features missing, please file a new issue for each feature you would like to use.
I am also concerned that if the programming model changes (as it did from v3 to v4) then I will be forced to majorly refactor code in the future, which I will not have the time to do
v3 to v4 was the biggest change we've done for Node.js in many years. It was inspired by one of the most-upvoted issues for Azure Functions and took at least 2+ years from the first design to the GA release. We have a lot of exciting new features in the works (top of mind is stream support), but nothing nearly as disruptive. We also have no plans to deprecate v3 for a while, so even in this case no one will be "forced" to refactor their code.
I would like to know how to return a JSON object as the response, rather than having to use
return { body: JSON.stringify(graphResponse.data) }
, Postman shows that the responseContent-Type
istext/plain;charset=UTF-8
, even though in the request headers,Content-Type
is specified asapplication/json
.
You could try return { jsonBody: graphResponse.data }
. Regardless, I'm not entirely sure why the content-type is text/plain, though. If you still have issues with this please file a separate issue for us to investigate.
I am not sure if I can/should be using Express.js in the v4 model, as demonstrated in this video , and then I could use body parser
We don't have any direct integrations with Express.js. We have an issue to track something like that, but have no real plans to work on it soon unless we get more feedback asking for it: https://github.com/Azure/azure-functions-nodejs-library/issues/16
I very much appreciate your thoughtful and detailed response.
For reference, I recognize my message was a little 'all over the place', but I was just trying to 'cram understand' Azure Functions over the space of a few days and thought there might be some value in documenting all of the newbie experiences I was having.
Thank you again.
Also, I can confirm that using return { jsonBody: graphResponse.data }
returns a JSON object in Postman, with the Content-Type
in the headers of the response displayed as application/json
as opposed to text/plain;charset=UTF-8
.
Can I please ask how I should reference query parameters in a GET request.
This doesn't seem to work:
const requestQueryParams = request.query;
// get target_site_id from the request query parameters
const target_site_id = requestQueryParams.target_site_id;
// get target_library_id from the request query parameters
const target_library_id = requestQueryParams.target_library_id;
// get choice_col_01_value from the request query parameters
const choice_col_01_value = requestQueryParams.choice_col_01_value;
// get choice_col_02_value from the request query parameters
const choice_col_02_value = requestQueryParams.choice_col_02_value;
Whilst this does work:
const requestQueryParams = request.query;
// get target_site_id from the request query parameters
const target_site_id = requestQueryParams.get('target_site_id');
// get target_library_id from the request query parameters
const target_library_id = requestQueryParams.get('target_library_id');
// get choice_col_01_value from the request query parameters
const choice_col_01_value = requestQueryParams.get('choice_col_01_value');
// get choice_col_02_value from the request query parameters
const choice_col_02_value = requestQueryParams.get('choice_col_02_value');
Edit:
If I am reading it right, it seems that query parameters are in a URLSearchParams
object:
And the docs for that object are here:
https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
And therefore I do need to use request.query.get('some_key_here')
For reference, I recognize my message was a little 'all over the place', but I was just trying to 'cram understand' Azure Functions over the space of a few days and thought there might be some value in documenting all of the newbie experiences I was having.
No problem! We'll have to split any action items into separate issues moving forward, but this type of feedback is always helpful and informative!
And yes your "Edit" about the query parameters is correct
I don't know whether my response is going to help someone or not. Unlike NodeJs's traditional request.body()
the one in Azure comes with a request.text()
method and returns Promise<String>
, now we can use JSON.parse() to convert the returned string into a JSON and access our intended values.
Here is a simple code snippet
const { app } = require('@azure/functions');
app.http('CreatePost', {
methods: ['POST'],
authLevel: 'anonymous',
handler: async (request, context) => {
try {
context.log(`Http function processed request for url "${request.url}"`)
const { name } = JSON.parse(await request.text())
if(!name) {
return {
status: 411,
body: "Bad Request"
}
}
return {
status: 201,
body: `Hello, ${name}!`
};
}
catch(error) {
return {
status: 501,
body: error.message
}
}
}
})
Hi @ihnaqi thanks for the sample. You can also use await request.json()
directly as shorthand for JSON.parse(await request.text())
Fyi, the HTTP request docs specific to Azure Functions are here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#http-request
And here are some docs for the fetch standard, which we are based off of: https://developer.mozilla.org/en-US/docs/Web/API/Request#instance_methods
Closing this issue - it covered several areas, but I think the most impactful ones already have separate issues for tracking. If not, please create a new issue for each one. Thanks again!
I've looked at the official docs:
Azure Functions Node.js developer guide https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4
Azure Functions developer guide
https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference?tabs=blob&pivots=programming-language-javascript
Azure Functions overview https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-javascript
Blog Post - Azure Functions: Version 4 of the Node.js programming model is in preview
https://techcommunity.microsoft.com/t5/apps-on-azure-blog/azure-functions-version-4-of-the-node-js-programming-model-is-in/ba-p/3773541
The are no code samples that demonstrate how to access
request.body
.The docs state:
In order to access a request or response's body, the following methods can be used:
arrayBuffer()
returnsPromise<ArrayBuffer>
blob()
returnsPromise<Blob>
formData()
returnsPromise<FormData>
json()
returnsPromise<unknown>
text()
returnsPromise<string>
I am sending a POST request to the Azure Function endpoint in the local environment.
I have tried in both Postman and using curl in Windows 11 terminal.
The headers defined in both cases are:
The body defined in both cases is:
I have tried numerous approaches to access
request.body
.Below are details of one of the approaches that has not worked, using the
json()
method onrequest
.I am choosing to ask about this approach because it seems like it should be the most straightforward.
Environment
I installed the following to get a local development environment:
-- Node.js (v18.18.1)
-- Azure CLI
-- Azure Functions Core Tools
I created a Project using this command:
I created a Function within this Project using these commands:
I ran the function locally using this command:
package.json
Azure Function Code