Here we'll start with Azure DevOps and set up a pipeline that can build, test, and deploy a TypeScript Azure function completely in the cloud. Once the pipeline is created, we'll add code to our function that can receive and handle an HTTP request. Most of the article will be spent setting up the build/test/deploy pipeline, so we won't have space to make the serverless function do much more than say Hello, world!
Cloud native development involves more than just running code in the cloud. As we’ve discussed in part one of this series, DevOps with continuous integration and continuous deployment (CI/CD) is also part of cloud native, especially when running microservices.
In this article, we’ll focus on CI/CD and not so much on writing code. In fact, we’ll set up our Azure DevOps environment before we write any code at all. We’ll then be able to set up a new Function App with TypeScript in mere minutes, after which our code will deploy automatically after every push to our Git repository.
Setting Up the Project
We first need to create a Git repository in Azure DevOps. This repository is necessary to create a build pipeline. When you’re in Azure DevOps, either create a new project, which will automatically create a Git repository we can use, or add a new Git repository to one of your existing projects. Be sure to initialize the repository by adding a README file or a (Node) gitignore file. After that, clone the repository to your machine.
Next, we’ll need a Function App in Azure. Go into the Azure portal, add a new resource, and search for "Function". For quick cleanup later, put your new Function App in a new resource group, which you can create in the blade that opens up. The name of your Function App must be unique across Azure.
Since a Function is serverless, you’ll need a storage account to store your code. It’s good practice to use a dedicated storage account, so create a new one in Create Function App. The Consumption Plan is the serverless plan. Note that you can run your functions on a regular hosting plan, but this will limit your scaling options. You can pick either Linux or Windows as your operating system, but we picked Linux in this example. Next, disable Application Insights.
Next, click Create and wait for your Function App to be provisioned.
Note that while this is the easiest method to create a Function App, it may not always be the best choice because it still requires manual work.
Creating a Build Pipeline
Now, go back to Azure DevOps. This is where we’ll create our pipeline to build, test, and deploy our project. Go to Pipelines and click New pipeline. We’ll use the Azure Repos Git option, but notice that you can use other Git sources as well, such as GitHub, which takes you through an authorization process, but otherwise works exactly the same.
After you select your Git repository, pick a pipeline template. You’ll find some templates for ASP.NET Core (and .NET Framework), Node.js, Android, and Ant. When you click Show more, you find additional templates for C/C++, Go, Python, PHP, and much more.
The one we’re looking for is Node.js Function App to Linux on Azure. Clicking it prompts you for an Azure subscription and a Function App. So, choose your subscription and the Function App we’ve just created.
After that, you get a YAML script that describes the steps necessary to deploy your code to your Azure Function. We don’t have to change it right now as it will work right out of the box with the default Node.js Azure Function template. I’ll get into some nitty-gritty details in a bit. First, let’s verify it works by deploying some code to Azure.
Creating an Azure Function Using TypeScript
The easiest method of creating an Azure Function with TypeScript is using Visual Studio Code. Install the Azure Functions extension then go to the newly-added Azure page.
There, click Create New Project in the upper-right corner.
Select a folder: the repository you cloned while setting up this project.
After that, pick your language: TypeScript.
Choose HTTP trigger, name it (or leave the default HttpTrigger1), and press Enter.
For authorization level, pick Function.
Finally, opt to open your project in the current window. This may prompt you to install some additional Azure Functions extensions, which you should accept.
Once that’s all done, a new Function is scaffolded. Check that it runs by pressing F5 or selecting Start Debugging from the Run menu. You’ll see some stuff happening in the terminal, then finally you’ll see:
[Function name]: [GET,POST] http://localhost:[port]/api/[Function name]
Open the link in your browser and you should see "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
So, add something like "?name=Bill" to the end of the URL, and you should now see "Hello, Bill. This HTTP triggered function executed successfully." That means everything works.
Now, it’s time to commit and push the code back to Azure DevOps and watch the magic happen! Go to your build pipeline and you should see a couple of green checks indicating everything went well. To check that your code is actually running correctly, go to the Azure portal and find your Function App. Click Functions and select your function. Here, click Code + Test then click Get function URL and copy the URL (it has a code, which is the Function authorization that we added, so not just anyone can invoke your function). The result should be the same as when you run it locally.
Congratulations, you’ve just automatically built and deployed your TypeScript Azure Function!
How It Works
So what did we just do?
First of all, when we created a pipeline in Azure DevOps, we created a YAML file that was pushed to our Git repository. This YAML file describes the steps to build, test, and deploy our code. Azure DevOps can read and execute it. For example the following command tells Azure DevOps to start executing when something is pushed to our main branch:
trigger:
– main
The important pieces are in the following section:
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
displayName: 'Install Node.js'
- script: |
if [ -f extensions.csproj ]
then
dotnet build extensions.csproj --runtime ubuntu.16.04-x64 --output ./bin
fi
displayName: 'Build extensions'
- script: |
npm install
npm run build --if-present
npm run test --if-present
displayName: 'Prepare binaries'
In this code, we define some steps to run. The first task is to install Node.js, using the NodeTool task. In Azure DevOps, go to the settings above a task to get some visual help with writing your YAML script.
After that, we run two Shell scripts. The first runs the dotnet build
command, if necessary. The second runs npm install
and runs the build and test scripts from our package.json file. Meanwhile, the default Node.js TypeScript Function template scaffolded a project with the TypeScript package from npm, as well as built a script that transpiles our TypeScript into JavaScript.
The next task, omitted here for brevity, puts our root folder in a ZIP file in the artifact staging folder. After that, the artifact staging folder is published as an artifact named "drop". Subsequent processes can reuse these artifacts.
Next is the deploy stage. We only have a single build environment, but we want to deploy our Function to Development, Test, Acceptance and Production (DTAP) eventually. So, for this, we use a release pipeline. You may remove the -stage: Deploy
code completely, but before you do, look at the AzureFunctionApp@1
task settings. We’ll use this exact same step when we build a release pipeline.
Release Pipelines
Go to Releases in Azure DevOps. Unfortunately, release pipelines don’t work with YAML scripts, so we’ll have to do some clicking here.
First, create a new release pipeline. When prompted for a template, pick: Deploy a function app to Azure Functions.
Rename your pipeline to something like "TypeScript Function release".
Next, rename "Stage 1" to "Development".
Click Add an artifact and, from your project, choose the Function App build. Enable continuous deployment via the lightning button on the artifact.
Next, go to your tasks. In your Development deployment process, pick an Azure subscription, set App type to Function App on Linux.
For your App service name, set $(AppServiceName)
. "AppServiceName" is a variable that we can set per environment, so we can specify a different name for our test environment when we set that up.
Under Run on agent, set the Agent Specification to ubuntu-20.04. The Deploy Azure Function App is actually the same task as the AzureFunctionApp@1
we had in our YAML script.
Next, go to variables and add "AppServiceName". For value, enter the name of your App Service and set it to the Development scope.
Save, then run your pipeline. If all goes well, your Azure Function URL still works.
It is now possible to clone the Development stage and rename the clone to "Test". You can then give your variable another name in the Test scope. Your development and test releases will then be the same process using the same artifact, created by the same build, but will deploy your code to another Azure Function App. However, we aren’t going to clone to a new environment just yet. First, we’re going to add a few extra steps to the pipeline, as described in the next article.
Next Steps
Creating and deploying a Node.js Function App with TypeScript is easy to do, and you can do it in mere minutes. The default templates actually provide a good foundation.
In the next installment of this series, we add a back end to our Function App, as well as add DTAP to our release pipeline, turning our Function App into a microservice.