|
| 1 | +--- |
| 2 | +title: Upgrade to v4 of the Node.js model for Azure Functions |
| 3 | +description: This article shows you how to upgrade your existing function apps running on v3 of the Node.js programming model to v4. |
| 4 | +ms.service: azure-functions |
| 5 | +ms.date: 03/15/2023 |
| 6 | +ms.devlang: javascript, typescript |
| 7 | +ms.topic: how-to |
| 8 | +--- |
| 9 | + |
| 10 | +# Upgrade to version 4 of the Node.js programming model for Azure Functions |
| 11 | + |
| 12 | +This article discusses the differences between version 3 and version 4 of the Node.js programming model and how to upgrade an existing v3 app. If you want to create a brand new v4 app instead of upgrading an existing v3 app, see the tutorial for either [VS Code](./create-first-function-cli-node.md) or [Azure Functions Core Tools](./create-first-function-vs-code-node.md). This article uses "TIP" sections to highlight the most important concrete actions you should take to upgrade your app. |
| 13 | + |
| 14 | +Version 4 was designed with the following goals in mind: |
| 15 | + |
| 16 | +- Provide a familiar and intuitive experience to Node.js developers |
| 17 | +- Make the file structure flexible with support for full customization |
| 18 | +- Switch to a code-centric approach for defining function configuration |
| 19 | + |
| 20 | +[!INCLUDE [Programming Model Considerations](../../includes/functions-nodejs-model-considerations.md)] |
| 21 | + |
| 22 | +## Requirements |
| 23 | + |
| 24 | +Version 4 of the Node.js programming model requires the following minimum versions: |
| 25 | + |
| 26 | +- [`@azure/functions`](https://www.npmjs.com/package/@azure/functions) npm package v4.0.0-alpha.8+ |
| 27 | +- [Node.js](https://nodejs.org/en/download/releases/) v18+ |
| 28 | +- [TypeScript](https://www.typescriptlang.org/) v4+ |
| 29 | +- [Azure Functions Runtime](./functions-versions.md) v4.16+ |
| 30 | +- [Azure Functions Core Tools](./functions-run-local.md) v4.0.4915+ (if running locally) |
| 31 | + |
| 32 | +## Include the npm package |
| 33 | + |
| 34 | +For the first time, the [`@azure/functions`](https://www.npmjs.com/package/@azure/functions) npm package contains the primary source code that backs the Node.js programming model. In previous versions, that code shipped directly in Azure and the npm package only had the TypeScript types. Moving forward, you need to include this package for both TypeScript and JavaScript apps. You _can_ include the package for existing v3 apps, but it isn't required. |
| 35 | + |
| 36 | +> [!TIP] |
| 37 | +> Make sure the `@azure/functions` package is listed in the `dependencies` section (not `devDependencies`) of your `package.json` file. You can install v4 with the command |
| 38 | +> ``` |
| 39 | +> npm install @azure/functions@preview |
| 40 | +> ``` |
| 41 | +
|
| 42 | +## Set your app entry point |
| 43 | +
|
| 44 | +In v4 of the programming model, you can structure your code however you want. The only files you need at the root of your app are `host.json` and `package.json`. Otherwise, you define the file structure by setting the `main` field in your `package.json` file. The `main` field can be set to a single file or multiple files by using a [glob pattern](https://wikipedia.org/wiki/Glob_(programming)). Common values for the `main` field may be: |
| 45 | +- TypeScript |
| 46 | + - `dist/src/index.js` |
| 47 | + - `dist/src/functions/*.js` |
| 48 | +- JavaScript |
| 49 | + - `src/index.js` |
| 50 | + - `src/functions/*.js` |
| 51 | +
|
| 52 | +> [!TIP] |
| 53 | +> Make sure you define a `main` field in your `package.json` file |
| 54 | +
|
| 55 | +## Switch the order of arguments |
| 56 | +
|
| 57 | +The trigger input is now the first argument to your function handler instead of the invocation context. The invocation context, now the second argument, was simplified in v4 and isn't as required as the trigger input - it can be left off if you aren't using it. |
| 58 | +
|
| 59 | +> [!TIP] |
| 60 | +> Switch the order of your arguments. For example if you are using an http trigger, switch `(context, request)` to either `(request, context)` or just `(request)` if you aren't using the context. |
| 61 | +
|
| 62 | +## Define your function in code |
| 63 | +
|
| 64 | +Say goodbye 👋 to `function.json` files! All of the configuration that was previously specified in a `function.json` file is now defined directly in your TypeScript or JavaScript files. In addition, many properties now have a default so that you don't have to specify them every time. |
| 65 | +
|
| 66 | +# [v4](#tab/v4) |
| 67 | +
|
| 68 | +```javascript |
| 69 | +const { app } = require("@azure/functions"); |
| 70 | +
|
| 71 | +app.http('helloWorld1', { |
| 72 | + methods: ['GET', 'POST'], |
| 73 | + handler: async (request, context) => { |
| 74 | + context.log('Http function processed request'); |
| 75 | +
|
| 76 | + const name = request.query.get('name') |
| 77 | + || await request.text() |
| 78 | + || 'world'; |
| 79 | +
|
| 80 | + return { body: `Hello, ${name}!` }; |
| 81 | + } |
| 82 | +}); |
| 83 | +``` |
| 84 | +
|
| 85 | +# [v3](#tab/v3) |
| 86 | + |
| 87 | +```javascript |
| 88 | +module.exports = async function (context, req) { |
| 89 | + context.log('HTTP function processed a request'); |
| 90 | + |
| 91 | + const name = req.query.name |
| 92 | + || req.body |
| 93 | + || 'world'; |
| 94 | + |
| 95 | + context.res = { |
| 96 | + body: `Hello, ${name}!` |
| 97 | + }; |
| 98 | +}; |
| 99 | +``` |
| 100 | + |
| 101 | +```json |
| 102 | +{ |
| 103 | + "bindings": [ |
| 104 | + { |
| 105 | + "authLevel": "anonymous", |
| 106 | + "type": "httpTrigger", |
| 107 | + "direction": "in", |
| 108 | + "name": "req", |
| 109 | + "methods": [ |
| 110 | + "get", |
| 111 | + "post" |
| 112 | + ] |
| 113 | + }, |
| 114 | + { |
| 115 | + "type": "http", |
| 116 | + "direction": "out", |
| 117 | + "name": "res" |
| 118 | + } |
| 119 | + ] |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +--- |
| 124 | + |
| 125 | +> [!TIP] |
| 126 | +> Move the config from your `function.json` file to your code. The type of the trigger will correspond to a method on the `app` object in the new model. For example, if you use an `httpTrigger` type in `function.json`, you will now call `app.http()` in your code to register the function. If you use `timerTrigger`, you will now call `app.timer()` and so on. |
| 127 | +
|
| 128 | + |
| 129 | +## Review your usage of context |
| 130 | + |
| 131 | +The `context` object has been simplified to reduce duplication and make it easier to write unit tests. For example, we streamlined the primary input and output so that they're only accessed as the argument and return value of your function handler. The primary input and output can't be accessed on the `context` object anymore, but you must still access _secondary_ inputs and outputs on the `context` object. For more information about secondary inputs and outputs, see the [Node.js developer guide](./functions-reference-node.md#extra-inputs-and-outputs). |
| 132 | + |
| 133 | +### Get the primary input as an argument |
| 134 | + |
| 135 | +The primary input is also called the "trigger" and is the only required input or output. You must have one and only one trigger. |
| 136 | + |
| 137 | +# [v4](#tab/v4) |
| 138 | + |
| 139 | +v4 only supports one way of getting the trigger input, as the first argument. |
| 140 | + |
| 141 | +```javascript |
| 142 | +async function helloWorld1(request, context) { |
| 143 | + const onlyOption = request; |
| 144 | +``` |
| 145 | +
|
| 146 | +# [v3](#tab/v3) |
| 147 | +
|
| 148 | +v3 supports several different ways of getting the trigger input. |
| 149 | +
|
| 150 | +```javascript |
| 151 | +async function helloWorld1(context, request) { |
| 152 | + const option1 = request; |
| 153 | + const option2 = context.req; |
| 154 | + const option3 = context.bindings.req; |
| 155 | +``` |
| 156 | +
|
| 157 | +--- |
| 158 | +
|
| 159 | +> [!TIP] |
| 160 | +> Make sure you aren't using `context.req` or `context.bindings` to get the input. |
| 161 | +
|
| 162 | +### Set the primary output as your return value |
| 163 | +
|
| 164 | +# [v4](#tab/v4) |
| 165 | +
|
| 166 | +v4 only supports one way of setting the primary output, through the return value. |
| 167 | +
|
| 168 | +```javascript |
| 169 | +return { |
| 170 | + body: `Hello, ${name}!` |
| 171 | +}; |
| 172 | +``` |
| 173 | +
|
| 174 | +# [v3](#tab/v3) |
| 175 | +
|
| 176 | +v3 supports several different ways of setting the primary output. |
| 177 | +
|
| 178 | +```javascript |
| 179 | +// Option 1 |
| 180 | +context.res = { |
| 181 | + body: `Hello, ${name}!` |
| 182 | +}; |
| 183 | +// Option 2, but you can't use this option with any async code: |
| 184 | +context.done(null, { |
| 185 | + body: `Hello, ${name}!` |
| 186 | +}); |
| 187 | +// Option 3, but you can't use this option with any async code: |
| 188 | +context.res.send(`Hello, ${name}!`); |
| 189 | +// Option 4, if "name" in "function.json" is "res": |
| 190 | +context.bindings.res = { |
| 191 | + body: `Hello, ${name}!` |
| 192 | +} |
| 193 | +// Option 5, if "name" in "function.json" is "$return": |
| 194 | +return { |
| 195 | + body: `Hello, ${name}!` |
| 196 | +}; |
| 197 | +``` |
| 198 | +
|
| 199 | +--- |
| 200 | +
|
| 201 | +> [!TIP] |
| 202 | +> Make sure you are always returning the output in your function handler, instead of setting it with the `context` object. |
| 203 | +
|
| 204 | +### Create a test context |
| 205 | +
|
| 206 | +v3 doesn't support creating an invocation context outside of the Azure Functions runtime, making it difficult to author unit tests. v4 allows you to create an instance of the invocation context, although the information during tests isn't detailed unless you add it yourself. |
| 207 | +
|
| 208 | +# [v4](#tab/v4) |
| 209 | +
|
| 210 | +```javascript |
| 211 | +const testInvocationContext = new InvocationContext({ |
| 212 | + functionName: 'testFunctionName', |
| 213 | + invocationId: 'testInvocationId' |
| 214 | +}); |
| 215 | +``` |
| 216 | +
|
| 217 | +# [v3](#tab/v3) |
| 218 | +
|
| 219 | +Not possible 😮 |
| 220 | +
|
| 221 | +--- |
| 222 | +
|
| 223 | +## Review your usage of HTTP types |
| 224 | +
|
| 225 | +The http request and response types are now a subset of the [fetch standard](https://developer.mozilla.org/docs/Web/API/fetch) instead of being types unique to Azure Functions. The types use Node.js's [`undici`](https://undici.nodejs.org/) package, which follows the fetch standard and is [currently being integrated](https://github.com/nodejs/undici/issues/1737) into Node.js core. |
| 226 | +
|
| 227 | +### HttpRequest |
| 228 | +
|
| 229 | +# [v4](#tab/v4) |
| 230 | +- _**Body**_. You can access the body using a method specific to the type you would like to receive: |
| 231 | + ```javascript |
| 232 | + const body = await request.text(); |
| 233 | + const body = await request.json(); |
| 234 | + const body = await request.formData(); |
| 235 | + const body = await request.arrayBuffer(); |
| 236 | + const body = await request.blob(); |
| 237 | + ``` |
| 238 | +- _**Header**_: |
| 239 | + ```javascript |
| 240 | + const header = request.headers.get('content-type'); |
| 241 | + ``` |
| 242 | +- _**Query param**_: |
| 243 | + ```javascript |
| 244 | + const name = request.query.get('name'); |
| 245 | + ``` |
| 246 | +
|
| 247 | +# [v3](#tab/v3) |
| 248 | +- _**Body**_. You can access the body in several ways, but the type returned isn't always consistent: |
| 249 | + ```javascript |
| 250 | + // returns a string, object, or Buffer |
| 251 | + const body = request.body; |
| 252 | + // returns a string |
| 253 | + const body = request.rawBody; |
| 254 | + // returns a Buffer |
| 255 | + const body = request.bufferBody; |
| 256 | + // returns an object representing a form |
| 257 | + const body = await request.parseFormBody(); |
| 258 | + ``` |
| 259 | +- _**Header**_. A header can be retrieved in several different ways: |
| 260 | + ```javascript |
| 261 | + const header = request.get('content-type'); |
| 262 | + const header = request.headers.get('content-type'); |
| 263 | + const header = context.bindingData.headers['content-type']; |
| 264 | + ``` |
| 265 | +- _**Query param**_: |
| 266 | + ```javascript |
| 267 | + const name = request.query.name; |
| 268 | + ``` |
| 269 | +--- |
| 270 | +
|
| 271 | +### HttpResponse |
| 272 | +
|
| 273 | +# [v4](#tab/v4) |
| 274 | +- _**Status**_: |
| 275 | + ```javascript |
| 276 | + return { status: 200 }; |
| 277 | + ``` |
| 278 | +- _**Body**_: |
| 279 | + ```javascript |
| 280 | + return { body: "Hello, world!" }; |
| 281 | + ``` |
| 282 | +- _**Header**_. You can set the header in two ways, depending if you're using the `HttpResponse` class or `HttpResponseInit` interface: |
| 283 | + ```javascript |
| 284 | + const response = new HttpResponse(); |
| 285 | + response.headers.set('content-type', 'application/json'); |
| 286 | + return response; |
| 287 | + ``` |
| 288 | + ```javascript |
| 289 | + return { |
| 290 | + headers: { 'content-type': 'application/json' } |
| 291 | + }; |
| 292 | + ``` |
| 293 | +
|
| 294 | +# [v3](#tab/v3) |
| 295 | +- _**Status**_. A status can be set in several different ways: |
| 296 | + ```javascript |
| 297 | + context.res.status(200); |
| 298 | + context.res = { status: 200} |
| 299 | + context.res = { statusCode: 200 }; |
| 300 | + return { status: 200}; |
| 301 | + return { statusCode: 200 }; |
| 302 | + ``` |
| 303 | +- _**Body**_. A body can be set in several different ways: |
| 304 | + ```javascript |
| 305 | + context.res.send("Hello, world!"); |
| 306 | + context.res.end("Hello, world!"); |
| 307 | + context.res = { body: "Hello, world!" } |
| 308 | + return { body: "Hello, world!" }; |
| 309 | + ``` |
| 310 | +- _**Header**_. A header can be set in several different ways: |
| 311 | + ```javascript |
| 312 | + response.set('content-type', 'application/json'); |
| 313 | + response.setHeader('content-type', 'application/json'); |
| 314 | + response.headers = { 'content-type': 'application/json' } |
| 315 | + context.res = { |
| 316 | + headers: { 'content-type': 'application/json' } |
| 317 | + }; |
| 318 | + return { |
| 319 | + headers: { 'content-type': 'application/json' } |
| 320 | + }; |
| 321 | + ``` |
| 322 | +
|
| 323 | +--- |
| 324 | +
|
| 325 | +> [!TIP] |
| 326 | +> Update any logic using the http request or response types to match the new methods. If you are using TypeScript, you should receive build errors if you use old methods. |
| 327 | +
|
| 328 | +## Troubleshooting |
| 329 | +
|
| 330 | +If you see the following error, make sure you [set the `EnableWorkerIndexing` flag](./functions-reference-node.md#enable-v4-programming-model) and you're using the minimum version of all [requirements](#requirements): |
| 331 | +
|
| 332 | +> No job functions found. Try making your job classes and methods public. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.). |
0 commit comments