Skip to content

Azure Functions Node.js Programming Model v4

Eric Jizba edited this page Mar 3, 2023 · 6 revisions

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!

Prerequisites

  • 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)

Setup

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:

  1. Make sure to reference the following preview npm package in your package.json dependencies: "@azure/functions": "^4.0.0-alpha.8"
  2. The AzureWebJobsStorage setting is currently required in your local.settings.json for all trigger types (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.
  3. You must set AzureWebJobsFeatureFlags to EnableWorkerIndexing in your local.settings.json when running locally or in your app settings when running in Azure

Other support

  • 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.

Changes from v3

Order of arguments

Rather than accepting (context, request) as the inputs to your function, the order has been switched to (request, context). See #34 for more information.

NPM package

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.

App entry point

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

Define function in code

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

Simplified context, inputs, and outputs

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.

Getting the primary input, aka trigger

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;

Setting the primary output

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}!` 
};

Create a test context

Old New

Not possible 😮

const testInvocationContext = new InvocationContext({
  functionName: 'testFunctionName',
  invocationId: 'testInvocationId'
});

New HTTP types

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.

HttpRequest

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');

HttpResponse

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' }
};

Troubleshooting

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.

Clone this wiki locally