-
Notifications
You must be signed in to change notification settings - Fork 18
Azure Functions Node.js Programming Model v4
v4 of the Node.js programming model for Azure Functions is an exciting new release with many long-awaited features! 🎉🚀 We plan to make a public preview announcement very soon, and are currently rolling out the final changes in March of 2023 (a couple bug fixes, tooling support, official docs, etc.). If you want to try it now, you're welcome to follow the steps below which we used during our internal testing phase. Otherwise, stay tuned for the announcement!
- Node.js v18+
- TypeScript v4+
- Azure Functions Host v4.15+
- Azure Functions Core Tools v4.0.4915+ (if running locally)
- Extension bundle v3.15+ (if using non-http triggers)
For a ready-to-run app, clone the following repository and follow the instructions in that README: https://github.com/ejizba/func-nodejs-prototype
If you are creating your own app, there are a few important things to keep in mind:
- Make sure to reference the following preview npm package in your package.json dependencies:
"@azure/functions": "^4.0.0-alpha.8"
- The
AzureWebJobsStorage
setting is currently required in yourlocal.settings.json
for all trigger types (even http) until #8614 is fixed. You may set it toUseDevelopmentStorage=true
to use the local storage emulator or you may set it to a connection string for a storage account in Azure. - You must set
AzureWebJobsFeatureFlags
toEnableWorkerIndexing
in yourlocal.settings.json
when running locally or in your app settings when running in Azure
- Durable Functions: See here for more information
- VS Code: Select "Model V4 (Preview)" when creating a project to get started, and you will also be able to create functions in that project.
Rather than accepting (context, request)
as the inputs to your function, the order has been switched to (request, context)
. See #34 for more information.
For the first time, the "@azure/functions" npm package will contain the primary source code that backs the Node.js programming model for Azure Functions. Previously, that code shipped directly in Azure and the package only had the TypeScript types. Moving forward both JavaScript and TypeScript users will need to include this package in their app.
Action Item: Make sure the @azure/functions
package is listed in the dependencies
section (not devDependencies
) of your package.json
file.
The new model lets you structure your code however you want. However, this means you need to define a primary entry point so that we know what file to use. You may also specify multiple entry points by using a glob pattern. Common entry points for TypeScript may be dist/src/index.js
or dist/src/functions/*.js
and for JavaScript may be src/index.js
or src/functions/*.js
.
Action Item: Make sure you have defined a main
field in your package.json
file
Say goodbye 👋 to "function.json" files! All of the config that you were previously specifying in a "function.json" file will be defined directly in your TypeScript or JavaScript files. In addition, many properties no longer need to be explicitly specified.
Old | New |
---|---|
module.exports = async function (context, req) {
context.log('HTTP function processed a request');
const name = req.query.name
|| req.body
|| 'world';
context.res = {
body: `Hello, ${name}!`
};
}; |
const { app } = require("@azure/functions");
app.http('helloWorld1', {
methods: ['GET', 'POST'],
handler: async (request, context) => {
context.log('Http function processed request');
const name = request.query.get('name')
|| await request.text()
|| 'world';
return { body: `Hello, ${name}!` };
}
}); |
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
} |
✨ Nothing ✨ |
The context object has been simplified to reduce duplication and make it easier to write unit tests. We have also streamlined the primary input and output so that they will always be the argument and return value of your function. If you are interested in how to work with secondary inputs/outputs in the new programming model, see here.
Old | New |
---|---|
async function helloWorld1(context, request) {
const option1 = request;
const option2 = context.req;
const option3 = context.bindings.req; |
async function helloWorld1(request, context) {
const onlyOption = request; |
Old | New |
---|---|
// Option 1
context.res = {
body: `Hello, ${name}!`
};
// Option 2, but you can't use this with any async code
context.done(null, {
body: `Hello, ${name}!`
});
// Option 3
context.res.send(`Hello, ${name}!`);
// Option 4, if "name" in "function.json" is "res"
context.bindings.res = {
body: `Hello, ${name}!`
}
// Option 5, if "name" in "function.json" is "$return"
return {
body: `Hello, ${name}!`
}; |
// Only option
return {
body: `Hello, ${name}!`
}; |
Old | New |
---|---|
Not possible 😮 |
const testInvocationContext = new InvocationContext({
functionName: 'testFunctionName',
invocationId: 'testInvocationId'
}); |
We have adjusted our http request and response types to be similar to the fetch standard supported by most browser environments. We are currently leveraging the undici npm package for part of that support, which is the same package currently being integrated into Node.js core for v18. Once support for the fetch api is marked stable in Node.js core, we will likely switch from the standalone undici
package to core Node.js support with minimal changes necessary.
Property Name | Old | New |
---|---|---|
Body |
// returns a string, object, or Buffer
const body = request.body;
// returns a string
const body = request.rawBody;
// returns a Buffer
const body = request.bufferBody;
// returns an object representing a form
const body = await request.parseFormBody(); |
const body = await request.text();
const body = await request.json();
const body = await request.formData();
const body = await request.arrayBuffer();
const body = await request.blob(); |
Header |
const header = request.get('content-type');
const header = request.headers.get('content-type');
const header = context.bindingData.headers['content-type']; |
const header = request.headers.get('content-type'); |
Query param |
const name = request.query.name; |
const name = request.query.get('name'); |
Property Name | Old | New |
---|---|---|
Status |
context.res.status(200);
context.res = { status: 200}
context.res = { statusCode: 200 };
return { status: 200};
return { statusCode: 200 }; |
return { status: 200 }; |
Body |
context.res.send("Hello, world!");
context.res.end("Hello, world!");
context.res = { body: "Hello, world!" }
return { body: "Hello, world!" }; |
return { body: "Hello, world!" }; |
Header |
response.set('content-type', 'application/json');
response.setHeader('content-type', 'application/json');
response.headers = { 'content-type': 'application/json' }
context.res = {
headers: { 'content-type': 'application/json' }
};
return {
headers: { 'content-type': 'application/json' }
}; |
// Option 1: Use the `HttpResponse` class
const response = new HttpResponse();
response.headers.set('content-type', 'application/json');
return response;
// Option 2: Return `HttpResponseInit` interface
return {
headers: { 'content-type': 'application/json' }
}; |
A host error has occurred during startup operation '00000000-0000-0000-0000-000000000000'. Microsoft.Azure.WebJobs.Extensions.DurableTask: Unable to resolve the Azure Storage connection named 'Storage'. Value cannot be null. (Parameter 'provider')
Make sure you have AzureWebJobsStorage
set in your local.settings.json
. This is currently required for all triggers (even http) until #8614 is fixed. You may set it to UseDevelopmentStorage=true
to use the local storage emulator or you may set it to a connection string for a storage account in Azure.
A host error has occurred during startup operation '00000000-0000-0000-0000-000000000000'. Microsoft.Azure.WebJobs.Script: _dispatcher. Value cannot be null. (Parameter 'provider')
Make sure you are using extension bundle v3.15.0 or higher due to this bug. You can set the version range to [3.15.0, 4.0.0)
in your host.json
.