Play Around With Your First Function App


Function Apps might seem intimidating until you actually start to build one. Join me in creating something useful instead of another “Hello World” example.

I get it - Azure Function Apps can feel overwhelming when you’re just starting out. All the talk about serverless computing, triggers, bindings, and deployment can make your head spin. But here’s the thing: at their core, Function Apps are just small pieces of code that run when something happens (a trigger).

Instead of walking through another boring example that adds two numbers together, let’s build something you might actually use: a URL health checker and a QR code generator.

Two simple but practical functions:

  1. UrlHealthChecker - Takes a URL and tells you if it’s responding (great for monitoring)
  2. QrCodeGenerator - Takes text and returns a QR code image (handy for quick sharing)

Both functions will be HTTP-triggered, so you can call them from anywhere.

First, let’s get the tools we need using PowerShell:

1
2
3
4
5
6
7
8
9
# Install Azure Functions Core Tools using winget
winget install --id Microsoft.Azure.FunctionsCoreTools --exact --source winget

# Install Azure PowerShell module
Install-Module -Name Az -Repository PSGallery -Force -AllowClobber

# Verify installations
func --version
Get-Module -Name Az -ListAvailable

Let’s start by creating the Azure resources. Save this as main.bicep:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@description('Name of the Function App')
param functionAppName string = 'func-${uniqueString(resourceGroup().id)}'

@description('Location for all resources')
param location string = resourceGroup().location

@description('Storage account name')
param storageAccountName string = 'st${uniqueString(resourceGroup().id)}'

// Storage Account (required for Function Apps)
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
}

// App Service Plan (Consumption plan = pay per execution)
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
  name: 'plan-${functionAppName}'
  location: location
  sku: {
    name: 'Y1'
    tier: 'Dynamic'
  }
  properties: {
    reserved: true
  }
}

// Function App
resource functionApp 'Microsoft.Web/sites@2023-01-01' = {
  name: functionAppName
  location: location
  kind: 'functionapp,linux'
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      linuxFxVersion: 'PowerShell|7.4'
      appSettings: [
        {
          name: 'AzureWebJobsStorage'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
        }
        {
          name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
        }
        {
          name: 'WEBSITE_CONTENTSHARE'
          value: toLower(functionAppName)
        }
        {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~4'
        }
        {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: 'powershell'
        }
      ]
    }
  }
}

output functionAppName string = functionApp.name
output functionAppUrl string = 'https://${functionApp.properties.defaultHostName}'

Deploy using PowerShell:

1
2
3
4
5
6
7
8
# Connect to Azure
Connect-AzAccount

# Create resource group (if needed)
New-AzResourceGroup -Name "rg-functionapp-demo" -Location "West Europe"

# Deploy the Bicep template
New-AzResourceGroupDeployment -ResourceGroupName "rg-functionapp-demo" -TemplateFile "main.bicep"

Now we need to create the local project structure for our functions. Let’s use PowerShell to set everything up:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Create the main project folder
$projectPath = "C:\Dev\MyFirstFunctionApp"
New-Item -ItemType Directory -Path $projectPath -Force

# Navigate to the project folder
Set-Location $projectPath

# Create the folder structure
New-Item -ItemType Directory -Path "UrlHealthChecker" -Force
New-Item -ItemType Directory -Path "QrCodeGenerator" -Force

# Open VS Code in the project folder
code .

Now let’s create all the necessary files. Important: These files need to be in specific locations for the Function App to work properly.

These files must be in the root of your Function App project:

host.json (Function App global configuration):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "version": "2.0",
  "functionTimeout": "00:05:00",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[3.*, 4.0.0)"
  }
}

This file tells Azure Functions what version to use, how long functions can run, and what extensions to load.

requirements.psd1 (PowerShell modules your functions need):

1
2
3
4
@{
    # Add PowerShell modules here if your functions need them
    # For example: 'Az.Accounts' = '2.*'
}

This file lists any PowerShell modules your functions require. Azure will automatically install them when your Function App starts.

profile.ps1 (Optional - runs when the PowerShell worker starts):

1
2
3
# This file runs when the PowerShell worker starts
# Use it for one-time setup, like importing modules or setting variables
Write-Host "PowerShell Function App starting up!"

Each function needs its own folder with two files:

UrlHealthChecker/function.json (Function configuration):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "Request",
      "methods": ["get", "post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "Response"
    }
  ]
}

This file tells Azure Functions that this function responds to HTTP requests and returns HTTP responses.

UrlHealthChecker/run.ps1 (The actual function code):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using namespace System.Net

param($Request, $TriggerMetadata)

$url = $Request.Query.url
if (-not $url) {
    $url = $Request.Body.url
}

if (-not $url) {
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::BadRequest
        Body = @{ error = "Please provide a 'url' parameter" } | ConvertTo-Json
        Headers = @{ "Content-Type" = "application/json" }
    })
    return
}

try {
    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    $response = Invoke-WebRequest -Uri $url -Method Head -TimeoutSec 10
    $stopwatch.Stop()
    
    $result = @{
        url = $url
        status = "healthy"
        statusCode = $response.StatusCode
        responseTime = $stopwatch.ElapsedMilliseconds
        timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    }
    
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::OK
        Body = $result | ConvertTo-Json
        Headers = @{ "Content-Type" = "application/json" }
    })
}
catch {
    $result = @{
        url = $url
        status = "unhealthy"
        error = $_.Exception.Message
        timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    }
    
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::OK
        Body = $result | ConvertTo-Json
        Headers = @{ "Content-Type" = "application/json" }
    })
}

QrCodeGenerator/function.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "Request",
      "methods": ["get", "post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "Response"
    }
  ]
}

QrCodeGenerator/run.ps1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using namespace System.Net

param($Request, $TriggerMetadata)

$text = $Request.Query.text
if (-not $text) {
    $text = $Request.Body.text
}

if (-not $text) {
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::BadRequest
        Body = @{ error = "Please provide a 'text' parameter" } | ConvertTo-Json
        Headers = @{ "Content-Type" = "application/json" }
    })
    return
}

# Using a free QR API (in production, consider hosting your own)
$encodedText = [System.Web.HttpUtility]::UrlEncode($text)
$qrApiUrl = "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=$encodedText"

try {
    $qrResponse = Invoke-WebRequest -Uri $qrApiUrl
    
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::OK
        Body = $qrResponse.Content
        Headers = @{ 
            "Content-Type" = "image/png"
            "Content-Disposition" = "inline; filename=qrcode.png"
        }
    })
}
catch {
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::InternalServerError
        Body = @{ error = "Failed to generate QR code: $($_.Exception.Message)" } | ConvertTo-Json
        Headers = @{ "Content-Type" = "application/json" }
    })
}

Run your functions locally to make sure they work:

1
2
# In your project folder (C:\Dev\MyFirstFunctionApp)
func start

Test with PowerShell:

1
2
3
4
5
6
# URL Health Checker
$response = Invoke-RestMethod -Uri "http://localhost:7071/api/UrlHealthChecker?url=https://google.com"
$response | ConvertTo-Json

# QR Code Generator (saves the image)
Invoke-WebRequest -Uri "http://localhost:7071/api/QrCodeGenerator?text=Hello World" -OutFile "qrcode.png"
  1. Install the Azure Functions extension in VS Code
  2. Sign in to Azure (Ctrl+Shift+P → “Azure: Sign In”)
  3. Right-click your project folder → “Deploy to Function App”
  4. Select your Function App
1
2
# Deploy using Azure Functions Core Tools
func azure functionapp publish your-function-app-name
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
MyFirstFunctionApp/
├── host.json                    # Global Function App configuration
├── requirements.psd1            # PowerShell modules to install
├── profile.ps1                 # Startup script (optional)
├── UrlHealthChecker/
│   ├── function.json           # Function trigger/binding config
│   └── run.ps1                 # Function code
└── QrCodeGenerator/
    ├── function.json           # Function trigger/binding config
    └── run.ps1                 # Function code

You now have two working functions that do something useful!
Using the Azure Functions PowerShell Developer Guide you can try to:

  • Add error handling and logging
  • Create timer-triggered functions for scheduled tasks
  • Explore Azure storage bindings
  • Add authentication to secure your functions

Function Apps aren’t scary once you see them in action. Start with simple, practical functions like these, and you’ll quickly understand how powerful serverless computing can be.

Happy scripting! 😀