01What Is Azure Functions?
Azure Functions is a serverless compute service that runs event-triggered code without provisioning or managing servers. You write a function, define what triggers it, and Azure handles scaling, patching, and infrastructure — you pay only for what you use.
02Core Concepts & Terminology
Understanding the core terminology is essential before building serverless solutions with Azure Functions. These concepts define how your code is organized, triggered, and connected to other Azure services. Mastering them early prevents misconfiguration and helps you design scalable, maintainable function apps.
| Term | Definition |
|---|---|
| Function App | The deployment and management unit — contains one or more functions |
| Function | A single unit of code with one trigger and optional bindings |
| Trigger | The event that causes a function to execute — exactly one per function |
| Input Binding | Declarative way to read data from a service (e.g. Blob contents, Table row) |
| Output Binding | Declarative way to write data to a service (e.g. send queue message, write Blob) |
| Hosting Plan | The infrastructure model — Consumption, Flex Consumption, Premium, or Dedicated |
| host.json | Global configuration for the function app — scale, timeouts, logging, extension settings |
| local.settings.json | Local development settings — connection strings, app settings (not deployed) |
| Durable Functions | Extension enabling stateful workflows with orchestrators, activities, and entities |
| Cold Start | Latency when a Consumption-plan function scales up from zero instances |
| Extension Bundle | NuGet package containing all binding extensions — declared in host.json |
| Managed Identity | Passwordless authentication to Azure services from Function App code |
03Hosting Plans
Choosing the right hosting plan directly impacts cost, latency, and scaling behavior of your serverless workloads. Each plan offers different trade-offs between cold start times, VNet support, and pricing models. Evaluate your traffic patterns and latency requirements to select the plan that best fits your production needs.
| Plan | Scale | Cold Start | VNet | Max Timeout | Best For |
|---|---|---|---|---|---|
| Consumption | 0 → auto | Yes | ✗ No | 5–10 min | Cost-optimized, sporadic traffic |
| Flex Consumption | 0 → auto (faster) | Reduced | ✓ Yes | Unlimited | Best of both — new recommended default |
| Premium (EP1/EP2/EP3) | Pre-warmed → auto | None | ✓ Yes | Unlimited | Low latency, VNet, always-warm |
| Dedicated (App Service) | Manual / auto | None | ✓ Yes | Unlimited | Predictable load, existing ASP |
| Container Apps | 0 → auto | Yes | ✓ Yes | Unlimited | Containerized functions |
Premium Plan SKUs
| SKU | vCPU | Memory | Pre-warmed |
|---|---|---|---|
| EP1 | 1 | 3.5 GB | 1 always-warm instance |
| EP2 | 2 | 7 GB | 1 always-warm instance |
| EP3 | 4 | 14 GB | 1 always-warm instance |
04Triggers — All Types
Triggers are the entry point for every Azure Function — they define what event causes your code to execute. Each function has exactly one trigger, and selecting the right trigger type determines how your function integrates with the broader Azure ecosystem. Understanding all available triggers helps you design event-driven architectures that react to changes in real time.
| Trigger | Description | Common Use Case |
|---|---|---|
| HTTP | Expose a function as an HTTP endpoint | APIs, webhooks, APIM backends |
| Timer | Cron-scheduled execution | Batch jobs, cleanup, reports |
| Service Bus Queue | Fire when a message arrives in a queue | Order processing, async tasks |
| Service Bus Topic | Fire when subscription receives a message | Event fan-out processing |
| Blob Storage | Fire when a blob is created/modified | File processing pipeline |
| Queue Storage | Fire when a Storage Queue message arrives | Simple task queuing |
| Event Grid | React to Event Grid events | Real-time event routing |
| Event Hubs | Process Event Hub event streams | IoT, telemetry, log analytics |
| Cosmos DB | Fire on Change Feed updates | Real-time change propagation |
| SQL (Preview) | Fire on SQL table changes | CDC-style change processing |
| Table Storage | Not a trigger — input/output binding only | — |
| SignalR | Negotiate + broadcast in real-time | Live dashboards, chat apps |
| RabbitMQ | React to RabbitMQ queue messages | On-prem messaging bridge |
| Kafka | Consume Kafka topic partitions | High-throughput stream processing |
| Dapr | Pub/Sub, service invocation via Dapr | Microservice orchestration |
05Bindings — Input & Output
Bindings let you connect to Azure services declaratively — no SDK initialization, connection management, or boilerplate. Just annotate your function parameter.
| Service | Input Binding | Output Binding |
|---|---|---|
| Blob Storage | Read blob content by path | Write blob content to path |
| Queue Storage | Not available | Send message to queue |
| Table Storage | Read entity or query | Insert/merge entity |
| Service Bus | Not available | Send message to queue/topic |
| Event Hubs | Not available | Send event to hub |
| Cosmos DB | Read document(s) by query | Upsert document(s) |
| SQL Database | Read rows by query | Insert/upsert rows |
| SignalR | Get connection info | Broadcast message to clients |
| Twilio | Not available | Send SMS |
| SendGrid | Not available | Send email |
| Event Grid | Not available | Publish event to topic |
| HTTP | Request body | Response to caller |
Multiple Output Bindings Example
[FunctionName("ProcessOrder")]
public static async Task Run(
[ServiceBusTrigger("orders", Connection = "ServiceBusConn")]
ServiceBusReceivedMessage message,
ServiceBusMessageActions msgActions,
// Input binding — read config from Blob
[Blob("config/settings.json", FileAccess.Read, Connection = "StorageConn")]
string configJson,
// Output binding — write result to Blob
[Blob("results/{rand-guid}.json", FileAccess.Write, Connection = "StorageConn")]
TextWriter resultBlob,
// Output binding — send confirmation to another queue
[ServiceBus("confirmations", Connection = "ServiceBusConn")]
IAsyncCollector<ServiceBusMessage> confirmQueue,
// Output binding — upsert to Cosmos DB
[CosmosDB("orders-db", "processed", Connection = "CosmosConn")]
IAsyncCollector<dynamic> cosmosOutput,
ILogger log)
{
var order = JsonSerializer.Deserialize<Order>(message.Body.ToString());
var config = JsonSerializer.Deserialize<Config>(configJson);
// Write to blob
await resultBlob.WriteAsync(JsonSerializer.Serialize(order));
// Send confirmation
await confirmQueue.AddAsync(new ServiceBusMessage(order.OrderId));
// Upsert to Cosmos DB
await cosmosOutput.AddAsync(new { id = order.OrderId, status = "processed" });
// Complete Service Bus message
await msgActions.CompleteMessageAsync(message);
}06Supported Languages & Models
| Language | Runtime | Model | Notes |
|---|---|---|---|
| C# | .NET 8 / 9 | Isolated Worker (recommended) or In-Process | Best performance + full .NET ecosystem |
| JavaScript | Node 18/20 | v4 model (recommended) | ES modules, TypeScript-friendly |
| TypeScript | Node 18/20 | v4 model | Compiled to JS, full type safety |
| Python | 3.10/3.11/3.12 | v2 model (recommended) | Decorator-based, great for ML/data |
| Java | Java 11/17/21 | Standard | Maven/Gradle project structure |
| PowerShell | PS 7.2 | Standard | Great for ops/automation scripts |
| F# | .NET 8 | Isolated Worker | Functional programming on .NET |
C# Isolated Worker Model (.NET 8)
// Program.cs — host setup
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddSingleton<IOrderService, OrderService>();
services.AddApplicationInsightsTelemetryWorkerService();
})
.Build();
await host.RunAsync();
// OrderFunction.cs
public class OrderFunction
{
private readonly IOrderService _orderService;
public OrderFunction(IOrderService orderService)
{
_orderService = orderService;
}
[Function("ProcessOrder")]
public async Task Run(
[ServiceBusTrigger("orders", Connection = "ServiceBusConn")]
ServiceBusReceivedMessage message,
ServiceBusMessageActions actions)
{
await _orderService.ProcessAsync(message.Body.ToString());
await actions.CompleteMessageAsync(message);
}
}Python v2 Model
import azure.functions as func
import json, logging
app = func.FunctionApp()
@app.service_bus_queue_trigger(
arg_name="msg",
queue_name="orders",
connection="ServiceBusConn")
def process_order(msg: func.ServiceBusMessage):
body = msg.get_body().decode('utf-8')
order = json.loads(body)
logging.info(f"Processing order: {order['orderId']}")
# Process...
@app.route(route="orders", methods=["POST"])
def create_order(req: func.HttpRequest) -> func.HttpResponse:
body = req.get_json()
return func.HttpResponse(
json.dumps({"created": True, "id": "123"}),
mimetype="application/json",
status_code=201)07HTTP Trigger — Deep Dive
HTTP triggers turn your functions into fully-featured REST API endpoints, making them the most common trigger type for building serverless APIs and webhook receivers. Authorization levels control access without external infrastructure, while route parameters enable clean RESTful URL patterns. In production, pair HTTP-triggered functions with APIM for enterprise-grade security and traffic management.
Authorization Levels
| Level | Key Required | Use Case |
|---|---|---|
| Anonymous | None | Public APIs, APIM-fronted endpoints (APIM handles auth) |
| Function | Function-specific key in header or query | Direct API access with per-function key |
| Admin | Host-level master key | Admin operations only |
Full HTTP Function Example (C# Isolated)
[Function("CreateOrder")]
public async Task<HttpResponseData> CreateOrder(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "orders")]
HttpRequestData req,
FunctionContext context)
{
var logger = context.GetLogger<OrderFunction>();
// Validate content type
if (!req.Headers.TryGetValues("Content-Type", out var ct)
|| !ct.First().Contains("application/json"))
{
var badReq = req.CreateResponse(HttpStatusCode.BadRequest);
await badReq.WriteAsJsonAsync(new { error = "Content-Type must be application/json" });
return badReq;
}
// Deserialize body
Order? order;
try
{
order = await req.ReadFromJsonAsync<Order>();
if (order == null) throw new JsonException("Null body");
}
catch (JsonException ex)
{
logger.LogError(ex, "Invalid request body");
var badReq = req.CreateResponse(HttpStatusCode.BadRequest);
await badReq.WriteAsJsonAsync(new { error = "Invalid JSON" });
return badReq;
}
// Process
var result = await _orderService.CreateAsync(order);
// Return 201 Created
var response = req.CreateResponse(HttpStatusCode.Created);
response.Headers.Add("Location", $"/api/orders/{result.Id}");
response.Headers.Add("X-Request-Id", context.InvocationId.ToString());
await response.WriteAsJsonAsync(result);
return response;
}Route Parameters
// Route: GET /api/orders/{orderId}/items/{itemId}
[Function("GetOrderItem")]
public async Task<HttpResponseData> GetOrderItem(
[HttpTrigger(AuthorizationLevel.Anonymous, "get",
Route = "orders/{orderId}/items/{itemId}")]
HttpRequestData req,
string orderId,
string itemId)
{
var item = await _orderService.GetItemAsync(orderId, itemId);
if (item == null)
{
return req.CreateResponse(HttpStatusCode.NotFound);
}
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(item);
return response;
}08Timer & Schedule Trigger
Timer triggers use NCRONTAB expression format:{second} {minute} {hour} {day} {month} {day-of-week}.
| CRON Expression | Meaning |
|---|---|
| 0 */5 * * * * | Every 5 minutes |
| 0 0 * * * * | Every hour (at :00) |
| 0 0 9 * * 1-5 | Every weekday at 09:00 UTC |
| 0 0 0 * * * | Every day at midnight |
| 0 0 0 1 * * | 1st of every month at midnight |
| 0 30 8 * * 1 | Every Monday at 08:30 UTC |
[Function("DailyReport")]
public async Task Run(
[TimerTrigger("0 0 9 * * 1-5")] TimerInfo timer,
FunctionContext context)
{
var logger = context.GetLogger<ReportFunction>();
if (timer.ScheduleStatus?.Last != null)
logger.LogInformation($"Last run: {timer.ScheduleStatus.Last}");
if (timer.IsPastDue)
logger.LogWarning("Timer is past due — missed schedule");
// Run daily report logic
var report = await _reportService.GenerateDailyAsync(DateTime.UtcNow);
await _emailService.SendAsync("team@mycompany.com", "Daily Report", report);
}09Service Bus Integration
Azure Service Bus is the enterprise messaging backbone for Functions, enabling reliable async communication between microservices with features like dead-lettering, sessions, and scheduled delivery. Functions integrate natively with Service Bus queues and topic subscriptions as both triggers and output bindings. Proper message handling patterns — including explicit completion, abandonment, and dead-letter routing — are critical for building resilient serverless pipelines.
Queue Trigger
[Function("ProcessOrder")]
public async Task Run(
[ServiceBusTrigger("orders", Connection = "ServiceBusConn")]
ServiceBusReceivedMessage message,
ServiceBusMessageActions actions,
ILogger log)
{
try
{
string body = message.Body.ToString();
var order = JsonSerializer.Deserialize<Order>(body)!;
log.LogInformation($"Processing order {order.OrderId}");
await _orderService.ProcessAsync(order);
await actions.CompleteMessageAsync(message); // ✅ Success
}
catch (Exception ex)
{
log.LogError(ex, "Failed to process order");
if (message.DeliveryCount >= 3)
{
await actions.DeadLetterMessageAsync(message,
"MaxRetriesExceeded", ex.Message);
}
else
{
await actions.AbandonMessageAsync(message); // Retry
}
}
}Topic Subscription Trigger
[Function("HandleBillingEvent")]
public void Run(
[ServiceBusTrigger("order-events", "billing", Connection = "ServiceBusConn")]
string messageBody,
int deliveryCount,
string messageId,
ILogger log)
{
log.LogInformation($"Billing event [{messageId}]: {messageBody}");
}Session-Enabled Trigger
[Function("ProcessSessionOrder")]
public async Task Run(
[ServiceBusTrigger("session-orders",
IsSessionsEnabled = true,
Connection = "ServiceBusConn")]
ServiceBusReceivedMessage message,
ServiceBusSessionMessageActions sessionActions)
{
string sessionId = message.SessionId;
// Get/set session state for ordered processing
byte[] currentState = await sessionActions.GetSessionStateAsync();
// Process in order...
await sessionActions.CompleteMessageAsync(message);
await sessionActions.SetSessionStateAsync(BinaryData.FromString("step-complete"));
}Output Binding — Send to Queue
[Function("CreateOrder")]
[ServiceBusOutput("orders", Connection = "ServiceBusConn")]
public ServiceBusMessage Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]
HttpRequestData req)
{
string body = req.ReadAsString();
return new ServiceBusMessage(body)
{
ContentType = "application/json",
Subject = "OrderCreated",
ApplicationProperties = { ["Source"] = "API" }
};
}DLQ Trigger
[Function("HandleDLQ")]
public void Run(
[ServiceBusTrigger("orders/$deadletterqueue", Connection = "ServiceBusConn")]
ServiceBusReceivedMessage dlqMsg,
ILogger log)
{
log.LogError("DLQ: {Id} | Reason: {Reason} | Desc: {Desc}",
dlqMsg.MessageId,
dlqMsg.DeadLetterReason,
dlqMsg.DeadLetterErrorDescription);
// Store to Blob for analysis, alert on-call team, replay etc.
}host.json — Service Bus Settings
{
"version": "2.0",
"extensions": {
"serviceBus": {
"prefetchCount": 20,
"transportType": "amqpWebSockets",
"messageHandlerOptions": {
"autoComplete": false,
"maxConcurrentCalls": 16,
"maxAutoRenewDuration": "00:10:00"
},
"sessionHandlerOptions": {
"autoComplete": false,
"messageWaitTimeout": "00:00:30",
"maxConcurrentSessions": 8
}
}
}
}10Storage Integration
Azure Storage is deeply integrated with Functions — it serves as both the runtime's backing store and a rich source of triggers and bindings for Blob, Queue, and Table storage. Blob triggers enable file-processing pipelines that react to uploads automatically, while Queue triggers provide simple, cost-effective task distribution. Use input and output bindings to read and write storage data declaratively without managing SDK connections.
Blob Trigger
[Function("ProcessDocument")]
public async Task Run(
[BlobTrigger("uploads/{name}", Connection = "StorageConn")]
Stream blobStream,
string name,
BlobClient blobClient,
ILogger log)
{
log.LogInformation($"Processing blob: {name}, Size: {blobStream.Length}");
BlobProperties props = await blobClient.GetPropertiesAsync();
string contentType = props.ContentType;
// Process the blob...
using var reader = new StreamReader(blobStream);
string content = await reader.ReadToEndAsync();
}Blob Input + Output Binding
[Function("TransformDocument")]
public async Task Run(
[QueueTrigger("transform-jobs", Connection = "StorageConn")]
string jobId,
// Read source blob
[BlobInput("raw/{queueTrigger}.json", Connection = "StorageConn")]
string sourceJson,
// Write result blob
[BlobOutput("processed/{queueTrigger}-result.json", Connection = "StorageConn")]
TextWriter outputBlob)
{
var data = JsonDocument.Parse(sourceJson);
var transformed = TransformData(data);
await outputBlob.WriteAsync(JsonSerializer.Serialize(transformed));
}Queue Trigger
[Function("ProcessQueueTask")]
public void Run(
[QueueTrigger("task-queue", Connection = "StorageConn")]
QueueMessage message,
ILogger log)
{
log.LogInformation($"Task: {message.MessageText}");
log.LogInformation($"Dequeue count: {message.DequeueCount}");
}Table Input/Output Binding
// Write to Table Storage
[Function("LogEvent")]
[TableOutput("AuditLog", Connection = "StorageConn")]
public TableEntity Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]
HttpRequestData req)
{
return new TableEntity("2025-01", Guid.NewGuid().ToString())
{
["EventType"] = "UserAction",
["UserId"] = req.Headers.GetValues("X-User-Id").First()
};
}
// Read from Table Storage
[Function("GetUser")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "users/{id}")]
HttpRequestData req,
[TableInput("Users", "Profiles", "{id}", Connection = "StorageConn")]
UserEntity? user)
{
if (user == null) return req.CreateResponse(HttpStatusCode.NotFound);
var res = req.CreateResponse(HttpStatusCode.OK);
await res.WriteAsJsonAsync(user);
return res;
}11Event Grid Integration
Event Grid provides a fully-managed event routing service that delivers events to Functions with high reliability and low latency. It enables reactive architectures where your functions respond to resource-level events across Azure — such as blob creation, resource group changes, or custom application events. Use Event Grid triggers for real-time event processing and output bindings to publish your own domain events.
[Function("HandleBlobCreated")]
public void Run(
[EventGridTrigger] CloudEvent cloudEvent,
ILogger log)
{
log.LogInformation($"Event type: {cloudEvent.Type}");
log.LogInformation($"Event subject: {cloudEvent.Subject}");
if (cloudEvent.Type == "Microsoft.Storage.BlobCreated")
{
var data = cloudEvent.Data?.ToObjectFromJson<StorageBlobCreatedEventData>();
log.LogInformation($"Blob URL: {data?.Url}");
log.LogInformation($"Content type: {data?.ContentType}");
// Trigger downstream processing...
}
}Output Binding — Publish to Event Grid
[Function("PublishEvent")]
[EventGridOutput(TopicEndpointUri = "EventGridTopicUri",
TopicKeySetting = "EventGridTopicKey")]
public EventGridEvent Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
{
var body = req.ReadFromJsonAsync<OrderCreatedEvent>().Result;
return new EventGridEvent(
subject: $"orders/{body?.OrderId}",
eventType: "OrderCreated",
dataVersion: "1.0",
data: body);
}12Event Hubs Integration
Event Hubs is Azure's high-throughput streaming platform, and Functions provides native batch-processing triggers that scale with partition count. This integration is ideal for IoT telemetry ingestion, log analytics, and real-time data pipelines where millions of events per second need processing. Configure batch sizes and checkpoint frequency in host.json to balance throughput against processing latency.
// Process batch of Event Hub events
[Function("ProcessTelemetry")]
public async Task Run(
[EventHubTrigger("telemetry-hub",
Connection = "EventHubConn",
ConsumerGroup = "functions-consumer")]
EventData[] events,
ILogger log)
{
foreach (var evt in events)
{
string data = Encoding.UTF8.GetString(evt.Body.ToArray());
log.LogInformation($"Event: {data}");
log.LogInformation($"Partition: {evt.PartitionKey}");
log.LogInformation($"Offset: {evt.Offset}");
log.LogInformation($"Sequence: {evt.SequenceNumber}");
// Process telemetry...
}
}
// host.json — Event Hub batch settings
// "eventHub": {
// "maxBatchSize": 64,
// "prefetchCount": 256,
// "batchCheckpointFrequency": 5
// }Output Binding — Send to Event Hub
[Function("ForwardToEventHub")]
[EventHubOutput("output-hub", Connection = "EventHubConn")]
public string Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]
HttpRequestData req)
{
return req.ReadAsString();
}13Durable Functions
Durable Functions extend Azure Functions with stateful workflows — fan-out/fan-in, human approval patterns, async long-running processes, and saga compensations — all in code.
| Component | Role |
|---|---|
| Orchestrator | Defines the workflow — calls activities, handles timers/events (must be deterministic) |
| Activity Function | Does the actual work — calls APIs, reads DB, processes data (can be non-deterministic) |
| Entity Function | Stateful, actor-like object — accumulate state across calls |
| Client Function | Starts, queries, signals, or terminates orchestrations |
Fan-Out / Fan-In Pattern
// Orchestrator — fan-out to parallel activities, fan-in results
[Function("OrderOrchestrator")]
public static async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var order = context.GetInput<Order>();
// Fan-out — start all tasks in parallel
var tasks = new List<Task<string>>
{
context.CallActivityAsync<string>("ValidatePayment", order),
context.CallActivityAsync<string>("CheckInventory", order),
context.CallActivityAsync<string>("CalculateShipping", order)
};
// Fan-in — wait for ALL to complete
var results = await Task.WhenAll(tasks);
// Continue with all results available
await context.CallActivityAsync("FinalizeOrder",
new { Order = order, Results = results });
return results.ToList();
}
// Activity Function
[Function("ValidatePayment")]
public static async Task<string> ValidatePayment(
[ActivityTrigger] Order order, ILogger log)
{
log.LogInformation($"Validating payment for order {order.OrderId}");
var result = await _paymentService.ValidateAsync(order);
return result.Status;
}Human Approval Pattern
[Function("ApprovalOrchestrator")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var order = context.GetInput<Order>();
// Send approval request email
await context.CallActivityAsync("SendApprovalEmail", order);
// Wait for external event (up to 48 hours)
using var timeoutCts = new CancellationTokenSource();
var deadline = context.CurrentUtcDateTime.AddHours(48);
var approvalTask = context.WaitForExternalEvent<bool>("ApprovalResult");
var timeoutTask = context.CreateTimer(deadline, timeoutCts.Token);
var winner = await Task.WhenAny(approvalTask, timeoutTask);
if (winner == approvalTask && approvalTask.Result)
{
timeoutCts.Cancel();
await context.CallActivityAsync("ProcessApprovedOrder", order);
}
else
{
await context.CallActivityAsync("NotifyTimeout", order);
}
}Durable Patterns Summary
| Pattern | Description | Use Case |
|---|---|---|
| Function Chaining | Sequential activity execution | ETL pipelines, ordered steps |
| Fan-Out / Fan-In | Parallel tasks, collect all results | Parallel API calls, bulk processing |
| Async HTTP (Polling) | Return 202 + status URL, client polls | Long-running API responses |
| Monitor | Polling loop until condition met | Watch for status change, price alerts |
| Human Interaction | Wait for external event with timeout | Approval workflows, 2FA |
| Aggregator (Entity) | Stateful accumulator per key | Event counting, real-time totals |
| Saga / Compensation | Track steps, compensate on failure | Distributed transactions |
14Logic Apps Integration
Functions and Logic Apps are highly complementary. Functions provide the custom code execution that Logic Apps call as steps, and Logic Apps provide the visual orchestration around functions.
Call Function from Logic App
{
"Call_function": {
"type": "Function",
"inputs": {
"function": {
"id": "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Web/sites/<funcApp>/functions/ProcessData"
},
"method": "POST",
"body": "@triggerBody()"
}
}
}Function Calls Logic App via HTTP
// Function triggers a Logic App HTTP workflow
[Function("TriggerApproval")]
public async Task Run(
[ServiceBusTrigger("high-value-orders", Connection = "ServiceBusConn")]
string orderJson)
{
using var http = new HttpClient();
// Logic App HTTP trigger URL stored in Key Vault / App Settings
var logicAppUrl = Environment.GetEnvironmentVariable("APPROVAL_LOGIC_APP_URL");
var response = await http.PostAsync(
logicAppUrl,
new StringContent(orderJson, Encoding.UTF8, "application/json"));
response.EnsureSuccessStatusCode();
}15APIM Integration
APIM is the recommended front door for Function App HTTP triggers in production — it handles auth, rate limiting, versioning, and developer portal, while the Function App stays anonymous at the network level (or secured via Managed Identity).
- 1Import Function App into APIMAPIM → APIs → Add API → Function App. Auto-discovers all HTTP-triggered functions.
- 2Secure with Managed IdentityEnable Entra ID auth on the Function App. APIM gets a token via its Managed Identity to call the function.
- 3Validate JWT at APIM layerAdd validate-jwt policy on APIM operations — Function App never sees unauthenticated requests.
- 4Rate limit at APIM layerAdd rate-limit-by-key policy — Function App is protected from abuse without any code changes.
- 5Strip internal headersRemove x-functions-key and other internal headers in APIM outbound policy before returning to clients.
16Security & Authentication
Securing serverless functions requires a layered approach — from HTTP authorization levels and Entra ID authentication to Managed Identity for service-to-service communication. Proper security configuration eliminates hardcoded secrets, prevents unauthorized access, and enables zero-trust architectures. Always use Managed Identity with Key Vault references in production to keep credentials out of your code and configuration.
HTTP Authorization Levels
| Level | Key | Recommended Scenario |
|---|---|---|
| Anonymous | None | Behind APIM (APIM handles auth), internal VNet apps |
| Function | Function-specific key | Direct access from known clients |
| Admin | Host master key | Admin/management operations only |
Entra ID Authentication (Easy Auth)
# Enable Entra ID auth on Function App
az functionapp auth update \
--name myFuncApp \
--resource-group myRG \
--enabled true \
--action LoginWithAzureActiveDirectory
# Configure Entra ID provider
az functionapp auth microsoft update \
--name myFuncApp \
--resource-group myRG \
--client-id <entra-app-client-id> \
--allowed-audiences api://my-func-app-idManaged Identity — Access Azure Services
// Enable System Assigned Identity in Portal or CLI
// az functionapp identity assign --name myFuncApp --resource-group myRG
// In function code — no connection strings needed
var credential = new DefaultAzureCredential();
// Access Blob Storage
var blobClient = new BlobServiceClient(
new Uri("https://mystorageaccount.blob.core.windows.net"), credential);
// Access Service Bus
var sbClient = new ServiceBusClient(
"mynamespace.servicebus.windows.net", credential);
// Access Key Vault
var kvClient = new SecretClient(
new Uri("https://mykeyvault.vault.azure.net"), credential);
var secret = await kvClient.GetSecretAsync("my-secret");App Settings → Key Vault References
# Reference Key Vault secret in App Settings
az functionapp config appsettings set \
--name myFuncApp \
--resource-group myRG \
--settings "DB_CONNECTION=@Microsoft.KeyVault(SecretUri=https://myKeyVault.vault.azure.net/secrets/db-conn)"
# Function reads it as a normal environment variable
string connStr = Environment.GetEnvironmentVariable("DB_CONNECTION")!;17Network Security & VNet
Network isolation is critical for enterprise serverless deployments that must access private resources or comply with regulatory requirements. VNet integration enables outbound access to private databases and services, while private endpoints restrict inbound traffic to your virtual network. Choose Premium or Flex Consumption plans when VNet connectivity is a requirement for your architecture.
| Feature | Consumption | Flex Consumption | Premium |
|---|---|---|---|
| VNet Integration (outbound) | ✗ No | ✓ Yes | ✓ Yes |
| Private Endpoints (inbound) | ✗ No | ✓ Yes | ✓ Yes |
| IP Restrictions | ✓ Yes | ✓ Yes | ✓ Yes |
| Service Endpoints | ✗ No | ✓ Yes | ✓ Yes |
| Private DNS resolution | ✗ No | ✓ Yes | ✓ Yes |
VNet Integration (Premium/Flex)
# Add VNet integration for outbound traffic
az functionapp vnet-integration add \
--name myFuncApp \
--resource-group myRG \
--vnet myVNet \
--subnet myFunctionSubnet
# Route ALL outbound traffic through VNet (access private resources)
az functionapp config appsettings set \
--name myFuncApp --resource-group myRG \
--settings WEBSITE_VNET_ROUTE_ALL=1Inbound Private Endpoint
# Create private endpoint for Function App (inbound)
az network private-endpoint create \
--name myFuncPrivateEndpoint \
--resource-group myRG \
--vnet-name myVNet \
--subnet myPESubnet \
--private-connection-resource-id $(az functionapp show --name myFuncApp --resource-group myRG --query id -o tsv) \
--group-ids sites \
--connection-name myFuncConn18Configuration & Settings
Proper configuration management is the foundation of reliable serverless applications — host.json controls runtime behavior like timeouts, concurrency, and extension settings, while app settings manage connection strings and feature flags. Understanding these configuration files helps you tune performance, control scaling behavior, and manage environment-specific settings across development and production. Use Managed Identity connection formats to eliminate secrets from your configuration entirely.
host.json
{
"version": "2.0",
"functionTimeout": "00:10:00",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
},
"logLevel": {
"default": "Information",
"Host.Results": "Error",
"Function": "Information"
}
},
"extensions": {
"serviceBus": {
"prefetchCount": 20,
"messageHandlerOptions": {
"autoComplete": false,
"maxConcurrentCalls": 16
}
},
"http": {
"routePrefix": "api",
"maxConcurrentRequests": 100,
"maxOutstandingRequests": 200
},
"queues": {
"maxPollingInterval": "00:00:02",
"visibilityTimeout": "00:00:30",
"maxDequeueCount": 5,
"batchSize": 16
}
}
}local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"ServiceBusConn": "Endpoint=sb://...",
"StorageConn": "DefaultEndpointsProtocol=https;AccountName=...",
// For Managed Identity (no connection string!)
"ServiceBusConn__fullyQualifiedNamespace": "<ns>.servicebus.windows.net",
"StorageConn__accountName": "mystorageaccount",
"CosmosConn__accountEndpoint": "https://myaccount.documents.azure.com:443/"
}
}19Monitoring & Diagnostics
Observability is essential for serverless applications where you don't control the infrastructure — Application Insights provides automatic telemetry collection for every function invocation, dependency call, and exception. Structured logging and custom metrics enable deep diagnostics and alerting, while KQL queries help you identify performance bottlenecks and failure patterns. Invest in monitoring early to maintain visibility as your function apps scale.
Application Insights — Auto Collected
| Telemetry Type | What's Captured |
|---|---|
| Requests | Each function invocation — duration, status, trigger type |
| Dependencies | Outbound HTTP, SQL, Service Bus, Storage calls |
| Exceptions | Unhandled exceptions with full stack trace |
| Traces | All ILogger log entries |
| Custom Events | Via TelemetryClient.TrackEvent() |
| Custom Metrics | Via TelemetryClient.TrackMetric() |
| Live Metrics | Real-time streaming of all telemetry |
Structured Logging
// Structured logging — queryable in App Insights
_logger.LogInformation(
"Processing order {OrderId} for customer {CustomerId} amount {Amount:C}",
order.OrderId, order.CustomerId, order.Amount);
// Custom metric
_telemetryClient.TrackMetric("OrderAmount", order.Amount,
new Dictionary<string, string> { ["Region"] = order.Region });
// Custom event
_telemetryClient.TrackEvent("OrderProcessed",
new Dictionary<string, string>
{
["OrderId"] = order.OrderId,
["ProcessingTimeMs"] = stopwatch.ElapsedMilliseconds.ToString()
});KQL — Function Failures
// Failed function invocations in last hour
requests
| where success == false
| where timestamp > ago(1h)
| where cloud_RoleName == "my-function-app"
| summarize count() by name, resultCode
| order by count_ desc
// P99 latency by function
requests
| where timestamp > ago(24h)
| where cloud_RoleName == "my-function-app"
| summarize p99 = percentile(duration, 99) by name
| order by p99 desc20CI/CD & DevOps
Automated deployment pipelines are critical for serverless applications where frequent, small releases are the norm. GitHub Actions and Azure DevOps provide native integration with Function Apps for build, test, and deploy workflows. Use deployment slots for blue/green deployments to achieve zero-downtime releases and instant rollback capability in production.
GitHub Actions — Deploy Function App
name: Deploy Azure Functions
on:
push:
branches: [main]
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Build
run: dotnet publish src/MyFunctionApp/MyFunctionApp.csproj \
--configuration Release --output ./publish
- name: Login to Azure
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy to Function App
uses: Azure/functions-action@v1
with:
app-name: myFuncApp
package: ./publish
respect-funcignore: trueSlot Deployment (Blue/Green)
# Deploy to staging slot
az functionapp deployment source config-zip \
--name myFuncApp \
--resource-group myRG \
--slot staging \
--src ./publish.zip
# Run smoke tests against staging slot
# https://myFuncApp-staging.azurewebsites.net/api/...
# Swap staging → production (zero downtime)
az functionapp deployment slot swap \
--name myFuncApp \
--resource-group myRG \
--slot staging \
--target-slot production21Performance & Scaling
Performance optimization in serverless requires different thinking than traditional applications — cold starts, connection management, and concurrency settings have outsized impact on latency and throughput. Following these best practices ensures your functions scale efficiently without hitting resource limits or accumulating unnecessary costs. Profile under realistic load to identify bottlenecks before they affect production users.
| Technique | Description |
|---|---|
| Avoid cold starts | Use Premium (pre-warmed) or Flex Consumption for latency-sensitive HTTP functions |
| Reuse connections | Declare HttpClient, ServiceBusClient as static singletons — not per-invocation |
| Prefetch messages | Set prefetchCount in host.json for Service Bus and Event Hubs triggers |
| Increase concurrency | Raise maxConcurrentCalls for Service Bus, batchSize for Queue triggers |
| Use Managed Identity | Avoids HTTPS token refresh overhead vs connection string auth |
| Stream large blobs | Use Stream instead of byte[] for large blob input to avoid OOM |
| Async all the way | Use async/await throughout — never .Result or .Wait() in async functions |
| Right-size the plan | Profile under load — EP1 may be over/under for your workload |
Singleton Pattern for Clients
// Program.cs — register as singletons via DI
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
// Registered once, reused across all invocations
services.AddSingleton(_ =>
new ServiceBusClient(
"<namespace>.servicebus.windows.net",
new DefaultAzureCredential()));
services.AddSingleton(_ =>
new BlobServiceClient(
new Uri("https://account.blob.core.windows.net"),
new DefaultAzureCredential()));
services.AddHttpClient("backend", c =>
{
c.BaseAddress = new Uri("https://api.mybackend.com");
c.Timeout = TimeSpan.FromSeconds(30);
});
})
.Build();22Architecture Patterns
23Functions vs Logic Apps vs APIM
Azure offers three complementary serverless services that are often confused — Functions for custom code execution, Logic Apps for visual workflow orchestration, and APIM for API gateway management. Understanding their strengths and boundaries helps you compose them effectively rather than forcing one tool to do everything. In practice, most enterprise solutions use all three together in a layered architecture.
| Dimension | Azure Functions | Logic Apps | APIM |
|---|---|---|---|
| Nature | Serverless code execution | Visual workflow orchestration | API gateway / proxy |
| Programming | Code — C#, Python, JS, Java | Declarative JSON + expressions | XML policies + config |
| State | Stateless (Durable = stateful) | Stateful runs built-in | Stateless per request |
| Connectors | Via SDK bindings (~20) | 500+ managed connectors | HTTP via send-request |
| Long running | Only with Durable | Days/weeks natively | Not applicable |
| Best for | Custom logic, data transforms, APIs | SaaS integration, approvals, ETL | Security, routing, rate limiting |
| Local dev | Full local emulation (func start) | VS Code + Azurite (Standard) | Limited |
| Cold start | Consumption: yes / Premium: no | Minimal | None |
24Pricing Overview
Azure Functions pricing varies significantly by hosting plan — from true pay-per-execution on Consumption to fixed monthly costs on Premium. Understanding the pricing model helps you estimate costs accurately and choose the right plan for your workload profile. Monitor GB-seconds consumption closely, as memory-intensive functions can accumulate costs faster than expected.
Consumption Plan
| Resource | Free Grant/Month | Price After |
|---|---|---|
| Executions | 1 million/month | ~$0.20 per million |
| GB-seconds | 400,000 GB-s/month | ~$0.000016 per GB-s |
Premium Plan (Always-Warm)
| SKU | vCPU | Memory | Price (approx) |
|---|---|---|---|
| EP1 | 1 vCPU | 3.5 GB | ~$140/month (1 instance) |
| EP2 | 2 vCPU | 7 GB | ~$280/month (1 instance) |
| EP3 | 4 vCPU | 14 GB | ~$560/month (1 instance) |
25Quick Reference Cheat Sheet
Keep this cheat sheet handy for the most commonly referenced Azure Functions syntax, CLI commands, and configuration patterns. These quick references cover CRON expressions, Managed Identity connection formats, and essential Azure CLI commands for daily serverless development. Bookmark this section for fast lookups during development and troubleshooting.
Format: {second} {minute} {hour} {day} {month} {weekday}
0 */5 * * * * → Every 5 minutes
0 0 * * * * → Every hour
0 0 9 * * 1-5 → Weekdays at 09:00 UTC
0 0 0 * * * → Daily at midnight
0 0 0 1 * * → Monthly (1st day)
0 30 8 * * 1 → Every Monday at 08:30// local.settings.json — use these for Managed Identity (no secrets)
{
"ServiceBusConn__fullyQualifiedNamespace": "<ns>.servicebus.windows.net",
"StorageConn__accountName": "mystorageaccount",
"CosmosConn__accountEndpoint": "https://myaccount.documents.azure.com:443/",
"EventHubConn__fullyQualifiedNamespace": "<ns>.servicebus.windows.net"
}# Create Function App (Flex Consumption)
az functionapp create \
--name myFuncApp --resource-group myRG \
--storage-account mystorageaccount \
--environment myContainerAppEnv \
--runtime dotnet-isolated --runtime-version 8
# Deploy via zip
func azure functionapp publish myFuncApp
# Stream live logs
az functionapp log tail --name myFuncApp --resource-group myRG
# Set app setting
az functionapp config appsettings set \
--name myFuncApp --resource-group myRG \
--settings "MY_SETTING=myvalue"
# Enable Managed Identity
az functionapp identity assign \
--name myFuncApp --resource-group myRG
# Scale out Premium plan
az functionapp plan update \
--name myPremiumPlan --resource-group myRG \
--max-burst 20| Limit | Consumption | Premium |
|---|---|---|
| Max timeout | 5 min (10 min configurable) | Unlimited |
| Max memory | 1.5 GB | 3.5–14 GB (by SKU) |
| Max scale-out (Consumption) | 200 instances | 100 instances |
| Max HTTP request size | 100 MB | 100 MB |
| Max function app size | 1 GB (deployed) | No limit |
| Min instances | 0 (scale to zero) | 1 pre-warmed |
| Durable Functions | ✓ Yes | ✓ Yes |
| VNet | ✗ No | ✓ Yes |