azure-functions

$npx mdskill add TerminalSkills/skills/azure-functions

Build serverless Azure Functions with declarative bindings and durable workflows.

  • Automates creation of event-driven code without infrastructure management.
  • Integrates with Azure CLI, func-cli, and Terraform for project setup.
  • Executes HTTP, timer, queue, and blob triggers based on configured events.
  • Delivers stateful orchestration results through Durable Functions workflows.
SKILL.md
.github/skills/azure-functionsView on GitHub ↗
---
name: azure-functions
description: |
  Build serverless applications with Azure Functions. Create HTTP and event-driven
  functions with input/output bindings, configure triggers for queues, timers,
  and blob storage. Use Durable Functions for stateful orchestration workflows.
license: Apache-2.0
compatibility: 'azure-cli, func-cli, terraform'
metadata:
  author: terminal-skills
  version: 1.0.0
  category: devops
  tags:
    - azure
    - functions
    - serverless
    - durable-functions
---

# Azure Functions

Azure Functions is a serverless compute service that runs event-driven code without managing infrastructure. Its unique binding system connects to Azure services declaratively, and Durable Functions enable complex stateful workflows.

## Core Concepts

- **Trigger** — what causes a function to run (HTTP, timer, queue, blob, etc.)
- **Input Binding** — declaratively read data from a service when function runs
- **Output Binding** — declaratively write data to a service after function runs
- **Function App** — a container for one or more functions sharing configuration
- **Hosting Plan** — Consumption (pay-per-execution), Premium, or Dedicated
- **Durable Functions** — extension for stateful orchestrations and workflows

## Project Setup

```bash
# Create a new function project
func init my-functions --worker-runtime node --language javascript
cd my-functions
```

```bash
# Create a new HTTP-triggered function
func new --name HandleWebhook --template "HTTP trigger" --authlevel anonymous
```

```bash
# Create Azure resources
az group create --name my-app-rg --location eastus

az storage account create \
  --name myappfuncstorage \
  --resource-group my-app-rg \
  --sku Standard_LRS

az functionapp create \
  --name my-app-functions \
  --resource-group my-app-rg \
  --storage-account myappfuncstorage \
  --consumption-plan-location eastus \
  --runtime node \
  --runtime-version 20 \
  --functions-version 4
```

## HTTP Functions

```javascript
// src/functions/handleWebhook.js — HTTP triggered function
const { app } = require('@azure/functions');

app.http('handleWebhook', {
    methods: ['POST'],
    authLevel: 'anonymous',
    route: 'webhooks/{source}',
    handler: async (request, context) => {
        const source = request.params.source;
        const body = await request.json();

        context.log(`Webhook from ${source}:`, body);

        // Process webhook
        const result = await processWebhook(source, body);

        return {
            status: 200,
            jsonBody: { received: true, id: result.id }
        };
    }
});
```

## Timer Functions

```javascript
// src/functions/dailyCleanup.js — runs on a CRON schedule
const { app } = require('@azure/functions');

app.timer('dailyCleanup', {
    schedule: '0 0 2 * * *', // 2:00 AM daily
    handler: async (myTimer, context) => {
        context.log('Running daily cleanup at', new Date().toISOString());

        const deleted = await cleanupExpiredSessions();
        context.log(`Deleted ${deleted} expired sessions`);
    }
});
```

## Queue Trigger with Output Binding

```javascript
// src/functions/processOrder.js — triggered by queue, writes to Cosmos DB
const { app, output } = require('@azure/functions');

const cosmosOutput = output.cosmosDB({
    databaseName: 'app-db',
    containerName: 'processed-orders',
    connection: 'CosmosDBConnection',
    createIfNotExists: true
});

app.storageQueue('processOrder', {
    queueName: 'order-queue',
    connection: 'AzureWebJobsStorage',
    return: cosmosOutput,
    handler: async (queueItem, context) => {
        context.log('Processing order:', queueItem.orderId);

        const processedOrder = {
            id: queueItem.orderId,
            ...queueItem,
            status: 'processed',
            processedAt: new Date().toISOString()
        };

        // Returned value goes to Cosmos DB via output binding
        return processedOrder;
    }
});
```

## Blob Trigger

```javascript
// src/functions/processImage.js — triggered when blob is uploaded
const { app } = require('@azure/functions');

app.storageBlob('processImage', {
    path: 'uploads/{name}',
    connection: 'AzureWebJobsStorage',
    handler: async (blob, context) => {
        const fileName = context.triggerMetadata.name;
        context.log(`Processing blob: ${fileName}, size: ${blob.length} bytes`);

        // Resize image, extract metadata, etc.
        await generateThumbnail(blob, fileName);
    }
});
```

## Durable Functions

```javascript
// src/functions/orderOrchestrator.js — orchestrate multi-step order processing
const { app } = require('@azure/functions');
const df = require('durable-functions');

// Orchestrator function
df.app.orchestration('orderOrchestrator', function* (context) {
    const order = context.df.getInput();

    // Step 1: Validate inventory
    const inventory = yield context.df.callActivity('checkInventory', order.items);
    if (!inventory.available) {
        yield context.df.callActivity('notifyCustomer', {
            orderId: order.id,
            message: 'Items out of stock'
        });
        return { status: 'cancelled', reason: 'out_of_stock' };
    }

    // Step 2: Process payment
    const payment = yield context.df.callActivity('processPayment', {
        amount: order.total,
        customerId: order.customerId
    });

    // Step 3: Ship order (with retry)
    const retryOptions = new df.RetryOptions(5000, 3); // 5s interval, 3 attempts
    const shipment = yield context.df.callActivityWithRetry(
        'shipOrder', retryOptions, order
    );

    // Step 4: Notify customer
    yield context.df.callActivity('notifyCustomer', {
        orderId: order.id,
        message: `Shipped! Tracking: ${shipment.trackingNumber}`
    });

    return { status: 'completed', tracking: shipment.trackingNumber };
});

// Activity functions
df.app.activity('checkInventory', { handler: async (items) => { /* ... */ } });
df.app.activity('processPayment', { handler: async (payment) => { /* ... */ } });
df.app.activity('shipOrder', { handler: async (order) => { /* ... */ } });
df.app.activity('notifyCustomer', { handler: async (notification) => { /* ... */ } });

// HTTP starter
app.http('startOrder', {
    route: 'orders/start',
    methods: ['POST'],
    extraInputs: [df.input.durableClient()],
    handler: async (req, context) => {
        const client = df.getClient(context);
        const order = await req.json();
        const instanceId = await client.startNew('orderOrchestrator', { input: order });
        return client.createCheckStatusResponse(req, instanceId);
    }
});
```

## Deployment

```bash
# Deploy to Azure
func azure functionapp publish my-app-functions
```

```bash
# Set application settings
az functionapp config appsettings set \
  --name my-app-functions \
  --resource-group my-app-rg \
  --settings "CosmosDBConnection=AccountEndpoint=..."
```

```bash
# View function logs
func azure functionapp logstream my-app-functions
```

## Best Practices

- Use bindings to avoid boilerplate for reading/writing Azure services
- Choose Consumption plan for sporadic workloads, Premium for consistent traffic
- Use Durable Functions for multi-step workflows instead of chaining queues
- Store connection strings in Application Settings, not code
- Set `FUNCTIONS_WORKER_PROCESS_COUNT` for CPU-bound workloads
- Use managed identities instead of connection strings where possible
- Enable Application Insights for monitoring and diagnostics
- Keep functions small and focused — one trigger, one purpose
More from TerminalSkills/skills