Azure Functions Essentials
Azure Functions Essentials What It Is: Triggers & Bindings Azure Functions is Microsoft's serverless compute platform — you write a single function, declare wha…
Azure Functions Essentials
What It Is: Triggers & Bindings
Azure Functions is Microsoft's serverless compute platform — you write a single function, declare what invokes it (a trigger) and what data flows in/out of it (bindings), and Azure handles hosting, scaling, and the event plumbing. The defining Azure-specific concept is the triggers-and-bindings model: instead of manually calling SDKs inside your function body to read a queue message or write a blob, you declare input/output bindings in configuration and the runtime injects/collects the data for you. Exactly one trigger per function; any number of input/output bindings.
Trigger — what invokes the function: HTTP, Timer, Queue Storage, Service Bus, Event Grid, Blob Storage, Cosmos DB change feed
Input binding — data pulled in automatically before your code runs (e.g. a Cosmos DB document by id from the route)
Output binding — data your function returns that Azure writes out for you (e.g. push a message to a Service Bus queue without an SDK call)
Function App — the deployment/hosting unit that groups one or more functions and shares config, scaling, and a hosting plan
HTTP & Queue Triggers (Node.js v4 model)
const { app } = require('@azure/functions')
// HTTP trigger — the most common entry point for APIs
app.http('createOrder', {
methods: ['POST'],
authLevel: 'function', // anonymous | function | admin
route: 'orders',
handler: async (request, context) => {
const body = await request.json()
if (!body.items || body.items.length === 0) {
return { status: 400, jsonBody: { error: 'items is required' } }
}
context.log(`Creating order for ${body.customerId}`)
const order = await createOrder(body)
// Output binding: push a message onto a queue for async processing
context.extraOutputs.set(queueOutput, JSON.stringify({ orderId: order.id }))
return { status: 201, jsonBody: order }
},
})
// Queue trigger — fires when a message lands in the queue
app.storageQueue('processOrder', {
queueName: 'order-processing',
connection: 'AzureWebJobsStorage',
handler: async (queueItem, context) => {
const { orderId } = JSON.parse(queueItem)
context.log(`Processing order ${orderId}`)
await fulfillOrder(orderId)
},
})
// Timer trigger — CRON-style schedule
app.timer('nightlyCleanup', {
schedule: '0 0 2 * * *', // 2 AM daily, NCronTab format (includes seconds)
handler: async (myTimer, context) => {
context.log('Running nightly cleanup')
await purgeExpiredSessions()
},
})Durable Functions: Orchestration
Durable Functions is an extension for writing stateful, long-running workflows in normal-looking code — orchestrations that survive restarts, coordinate multiple functions, and can run for hours or months. The orchestrator function itself must be deterministic (no direct I/O, no random/DateTime.Now calls) because the runtime replays its history to rebuild state after every checkpoint; all real work happens in activity functions the orchestrator calls out to.
const df = require('durable-functions')
// Orchestrator — deterministic, describes the workflow shape
df.app.orchestration('processOrderOrchestrator', function* (context) {
const order = context.df.getInput()
const paymentResult = yield context.df.callActivity('chargePayment', order)
if (!paymentResult.success) {
yield context.df.callActivity('notifyFailure', order)
return { status: 'failed' }
}
// Fan-out/fan-in: run these activities in parallel, wait for all
const tasks = [
context.df.callActivity('reserveInventory', order),
context.df.callActivity('notifyWarehouse', order),
]
yield context.df.Task.all(tasks)
// Durable timer — sleeps without holding compute or billing for wait time
yield context.df.createTimer(
new Date(context.df.currentUtcDateTime.getTime() + 24 * 60 * 60 * 1000)
)
yield context.df.callActivity('sendReviewRequest', order)
return { status: 'completed' }
})
// Activity — where actual I/O happens
df.app.activity('chargePayment', {
handler: async (order) => {
return await paymentGateway.charge(order.total, order.paymentToken)
},
})
// HTTP trigger that starts the orchestration
df.app.orchestration('processOrderOrchestrator')
app.http('startOrderProcessing', {
methods: ['POST'],
extraInputs: [df.input.durableClient()],
handler: async (request, context) => {
const client = df.getClient(context)
const order = await request.json()
const instanceId = await client.startNew('processOrderOrchestrator', { input: order })
return client.createCheckStatusResponse(request, instanceId)
},
})Hosting Plans & Scaling
The hosting plan you pick determines cold starts, scaling behavior, and how you're billed. This is a major architectural decision, not a deploy-time detail.
Consumption plan — true pay-per-execution, scales to zero, generous free grant monthly, but cold starts on scale-from-zero and a max 10-minute execution timeout by default
Premium plan — pre-warmed instances (no cold starts), VNet integration, longer/unbounded execution, billed for pre-warmed capacity even when idle
Dedicated (App Service) plan — runs on VMs you already pay for (e.g. alongside a web app), predictable cost, manual/autoscale rules, no scale-to-zero
# Create a Consumption-plan function app
az functionapp create \
--resource-group my-app-rg \
--consumption-plan-location eastus \
--runtime node \
--runtime-version 20 \
--functions-version 4 \
--name my-func-app \
--storage-account myfuncstorage001
# Deploy local project
func azure functionapp publish my-func-app
# Stream logs while testing
func azure functionapp logstream my-func-app
# Local dev — Azure Functions Core Tools
func startPitfalls & Practical Tips
Orchestrator functions must be deterministic — no direct HTTP calls, no
Date.now(), no random numbers; the runtime replays orchestrator code from history, so any non-deterministic call must go through a durable API (createTimer, activity calls) insteadConsumption plan cold starts can add several seconds of latency on the first request after idle — use Premium plan or a min instance count for latency-sensitive HTTP APIs
Queue/blob/Cosmos triggers deliver at-least-once — design activity functions and handlers to be idempotent, since the same message can be retried after a partial failure
Consumption plan has a 10-minute default execution timeout (configurable up to 10 min max on Consumption; unbounded on Premium/Dedicated) — long-running work belongs in Durable Functions or a queue-based fan-out, not a single long HTTP handler
authLevel: 'function' requires a function key on every request — use 'anonymous' behind API Management or your own auth layer instead of hand-rolling key distribution to clients
VNet integration (private resource access) requires the Premium or Dedicated plan — Consumption-plan functions can't join a VNet