{
"$type": "site.standard.document",
"canonicalUrl": "https://johnnyreilly.com/posts/migrating-azure-functions-node-js-v4-typescript",
"description": "Learn how to migrate a TypeScript Azure Functions app to the v4 Node.js programming model.",
"path": "/posts/migrating-azure-functions-node-js-v4-typescript",
"publishedAt": "2023-10-24T00:00:00.000Z",
"site": "at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.publication/3mova7c4nho2b",
"tags": [
"typescript",
"azure",
"azure functions",
"azure static web apps"
],
"textContent": "There's a new programming model available for Node.js Azure Functions known as v4. There's documentation out there for how to migrate JavaScript Azure Functions from v3 to v4, but at the time of writing, TypeScript wasn't covered.\n\nThis post fills in the gaps for a TypeScript Azure Function. It's probably worth mentioning that my blog is an Azure Static Web App with a TypeScript Node.js Azure Functions back end. So, this post is based on my experience migrating my blog to v4.\n\n\n\nI'm going to walk through the migration of my blog from v3 to v4. This takes place in this pull request. I'll probably cover some of the ground of the offical JavaScript upgrade docs, but I'll also cover some of the TypeScript specific stuff.\n\nThere will be two main parts to this post:\n\n1. Changes to make to package.json\n2. Migrating a Function\n\nThe second part will be the bulk of the post, but the first part is important too.\n\n1. Changes to make to the package.json\n\nSo, starting with the first part, there are three changes to make to the package.json:\n\n1. Update the @azure/functions dependency to ^4.0.1 (or later)\n2. The @azure/functions dev dependency becomes a regular dependency - this is because we'll be using the package at runtime now - previously we just used it to get the types at build time\n3. Add a main property to the package.json with a glob that matches the functions in your project; in my case dist/src/functions//index.js - which will be our output from the TypeScript build\n\nAs I took care of 3., I found myself changing the folder structure of my functions. Actually, this isn't mandatory, but it was tricky for me to come up with a glob for my current structure. So I moved things around - you may not need to. All that matters is that your glob matches the output of your build.\n\n2. Migrating a Function\n\nIn order that we can understand what migration looks like, we must first take a look at the v3 version of a function. Here's the fallback function from my blog:\n\nThe above is the code I use to power dynamic redirects in my Azure Static Web App with the Azure Function back-end. It's a TypeScript Azure Function that takes a request, redirects to a new location and saves metadata about the redirect to a database.\n\nLooking at the code now, I rather think I should have called the function redirect rather than fallback. I'll leave it as is for now, but I'll probably change it in the future.\n\nWhat the fallback function does isn't significant for this post, but the structure is. Now let's look at the migrated version:\n\nAs we can see, the logic looks pretty much the same. But a lot has changed. What's different? We'll go through the changes one by one.\n\nimports used\n\nStarting at the top, the imports we use are different:\n\nWe're no longer just importing types, we're importing the app function from @azure/functions also. The types that are being imported are different too. We're no longer importing AzureFunction, Context, HttpRequest - instead we're importing HttpRequest, HttpResponseInit, InvocationContext.\n\nHello app, goodbye function.json\n\nAs we saw, we're making use of the app function from @azure/functions. This is a new function that we use to register our Azure Functions. We no longer use function.json. Instead we use app. In the case of my fallback Azure Functions, we register it like this:\n\nWe're registering an HTTP trigger called fallback that responds to GET requests. The handler is the function that will be called when the trigger is invoked. There's more options available, but this is the minimum we need to register our function.\n\nThis minimal TypeScript/JavaScript replaces the more verbose function.json that used to sit alongside:\n\nAll of this is gone, replaced by the app function usage. There's one part of the function.json that isn't covered by the app function, and that's the scriptFile property. This is covered by the main property we added to the package.json earlier.\n\nThe rest of the function.json is covered by the app.http call. Much terser.\n\nSignature and types of our function\n\nThe signature of our function has changed too:\n\nYou'll see here that we're using a function declaration rather than a function expression. Our new function takes our new types, the subtly different HttpRequest and InvocationContext, which are similar to, but different from, the previous Context and HttpRequest types. The order of these parameters has changed also.\n\nThe return type of the function is now Promise<HttpResponseInit> rather than Promise<void>. What this means is, we're going to return values from our function, which we didn't do previously. Let's look at the implications of this.\n\nFrom context.res to Promise<HttpResponseInit>\n\nWith a v3 function, we'd set the context.res property to return values from our function. With a v4 function, we return values from our function directly. What does this look like?\n\nI rather like this change. My reasoning is that, in the event that there is subsequent code that would otherwise run after context.res was set, we no longer need to remember to subsequently return to prevent that running. (And yes, I have made that mistake on multiple occasions.) All we need do is return the value we want to return from our function.\n\nbody -> jsonBody\n\nAnother difference is that we no longer set the body property of the context.res. Instead we return an object with a jsonBody property, assuming we're returning JSON from our API. (And that's the most common case, right?)\n\nThis wasn't illustrated in the fallback function above, but here's an example of migrating a function that returns JavaScript object literal named redirectSummary:\n\nThe body property still exists, but it's for returning strings and other things, rather than JSON. If you're returning JSON, the easiest approach is to use the jsonBody property.\n\nRuntime APIs\n\nFinally, the APIs offered by the request and context objects are different. I shan't go into detail here as it's well covered in the official documentation. But I will show you one of the changes I made to my fallback function:\n\nNot too significant a tweak, but there's a number of slight changes like this to make. (Related to this, the logging API on the context object is also different - but not significantly.)\n\n3. Running locally\n\nIf you're a fan of running locally then you may need to make this tweak to your host.json:\n\nA complete host.json might look like this:\n\nAs discussed with [Eric Jizba on this GitHub issue, updating the host.json may not actually be necessary. For me it seemed to be the thing that turned a not working setup into a working setup; but it's possible I was mistaken. Certainly if I revert the change now I'm still able to run locally. What I'm saying is: your mileage may vary.\n\nConclusion\n\nMigrating an Azure Function from v3 to v4 with TypeScript is a little more involved than I'd expected. But I do like that this moves us to a code style that feels more \"Node-y\". The official documentation is good, but it's not complete right now. There's now a decent upgrade guide available: https://learn.microsoft.com/en-us/azure/azure-functions/functions-node-upgrade-v4?tabs=v4\n\nHopefully this post will help you migrate your TypeScript Azure Functions to v4.",
"title": "Migrating to v4 Azure Functions Node.js with TypeScript"
}