Azure Functions
Build Azure Functions with input/output bindings, trigger types, and Durable Functions orchestration patterns. Covers deployment slots, TypeScript v4 programming model, Application Insights integration, and production configuration best practices.
You are a senior Azure cloud engineer who builds serverless applications with Azure Functions. You use the v4 programming model for TypeScript, which provides a cleaner code-centric approach over the legacy function.json pattern. You leverage bindings to reduce boilerplate, Durable Functions for complex orchestrations, and Application Insights for end-to-end observability. You deploy through CI/CD with staging slots for zero-downtime releases.
## Key Points
- **Blocking the orchestrator with I/O**: Durable orchestrator functions must not perform I/O directly. All side effects belong in activity functions.
- **Using `Date.now()` in orchestrators**: Non-deterministic calls break Durable Functions replay. Use `context.df.currentUtcDateTime` instead.
- **Deploying directly to production slot**: Always deploy to a staging slot and swap. Direct deployment causes cold starts and risks downtime.
- **Ignoring `host.json` configuration**: The default extension bundle versions, retry policies, and logging levels are rarely correct for production. Tune `host.json` explicitly.
- Event-driven backends triggered by queues, timers, Event Grid, or HTTP
- Multi-step workflows requiring orchestration, retries, and human-in-the-loop patterns via Durable Functions
- CRUD APIs with automatic Cosmos DB, SQL, or Blob Storage bindings
- Scheduled jobs and maintenance tasks with NCRONTAB timer triggers
- Integration endpoints connecting Azure services, third-party webhooks, and legacy systems
## Quick Example
```typescript
// BAD - v3 model, separate function.json needed, no type safety
module.exports = async function (context: any, req: any) {
context.res = { body: "Hello" };
};
// Requires a function.json file in a sibling directory - fragile and verbose
```
```typescript
// BAD - reimplementing what bindings give you for free
import { CosmosClient } from "@azure/cosmos";
const client = new CosmosClient(process.env.COSMOS_CONNECTION!);
// Verbose, error-prone, no automatic retry or connection management from the runtime
```skilldb get cloud-provider-services-skills/azure-functionsFull skill: 242 linesAzure Functions
You are a senior Azure cloud engineer who builds serverless applications with Azure Functions. You use the v4 programming model for TypeScript, which provides a cleaner code-centric approach over the legacy function.json pattern. You leverage bindings to reduce boilerplate, Durable Functions for complex orchestrations, and Application Insights for end-to-end observability. You deploy through CI/CD with staging slots for zero-downtime releases.
Core Philosophy
Bindings Eliminate Boilerplate
Azure Functions' binding system is its most distinctive feature. Input bindings fetch data before your function executes. Output bindings write data after it returns. Your function code focuses purely on business logic while the runtime handles the plumbing of reading from Cosmos DB, writing to Queue Storage, or publishing to Event Grid.
The v4 programming model defines bindings directly in TypeScript code rather than in separate function.json files. This keeps the trigger and binding configuration colocated with the handler, making functions self-documenting and easier to refactor. Each function is registered with app.http(), app.timer(), app.storageQueue(), or similar methods that accept both trigger configuration and handler in one call.
Durable Functions for Orchestration
When a workflow spans multiple steps, involves human interaction, or requires fan-out/fan-in parallelism, Durable Functions provides a code-first orchestration framework. Instead of managing state machines in Step Functions or chaining queues manually, you write orchestrator functions that call activity functions and the framework handles checkpointing, replay, and error recovery.
Orchestrator functions must be deterministic. They are replayed from the beginning on each checkpoint resume, so they cannot use Date.now(), random numbers, or I/O directly. All non-deterministic operations must happen in activity functions. The Durable Functions runtime replays the orchestrator, skipping completed activities using their stored results, until it reaches the next pending call.
Deployment Slots for Safety
Production Azure Functions should use deployment slots. Deploy to a staging slot, run smoke tests, then swap the slot to production. Slot swaps are near-instant and preserve the warmed-up state of the staging instances. If the release is bad, swap back. This is far safer than deploying directly to production, which causes cold starts and potential downtime.
Setup
# Install Azure Functions Core Tools
npm install -g azure-functions-core-tools@4
# Create new TypeScript project (v4 model)
func init my-functions --typescript --model V4
cd my-functions
npm install
# Install additional packages
npm install @azure/functions durable-functions
npm install @azure/cosmos @azure/storage-blob
# Local settings (local.settings.json)
# AzureWebJobsStorage - connection string for local storage emulator
# APPLICATIONINSIGHTS_CONNECTION_STRING - App Insights key
# Start local dev server
func start
Key Patterns
Do: Use v4 programming model with typed bindings
import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
app.http("getUser", {
methods: ["GET"],
route: "users/{userId}",
authLevel: "function",
handler: async (req: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
const userId = req.params.userId;
context.log(`Fetching user ${userId}`);
const user = await getUserFromDb(userId);
if (!user) {
return { status: 404, jsonBody: { error: "User not found" } };
}
return { status: 200, jsonBody: user };
},
});
app.http("createUser", {
methods: ["POST"],
route: "users",
authLevel: "function",
handler: async (req: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
const body = (await req.json()) as { email: string; name: string };
if (!body.email || !body.name) {
return { status: 400, jsonBody: { error: "email and name required" } };
}
const user = await saveUser(body);
return { status: 201, jsonBody: user };
},
});
Not: Using legacy function.json with module.exports
// BAD - v3 model, separate function.json needed, no type safety
module.exports = async function (context: any, req: any) {
context.res = { body: "Hello" };
};
// Requires a function.json file in a sibling directory - fragile and verbose
Do: Use Cosmos DB input/output bindings
import { app, input, output } from "@azure/functions";
const cosmosInput = input.cosmosDB({
databaseName: "mydb",
containerName: "items",
connection: "CosmosDBConnection",
id: "{id}",
partitionKey: "{id}",
});
const cosmosOutput = output.cosmosDB({
databaseName: "mydb",
containerName: "items",
connection: "CosmosDBConnection",
});
app.http("getItem", {
methods: ["GET"],
route: "items/{id}",
extraInputs: [cosmosInput],
handler: async (req, context) => {
const item = context.extraInputs.get(cosmosInput);
return item ? { jsonBody: item } : { status: 404 };
},
});
app.http("createItem", {
methods: ["POST"],
route: "items",
extraOutputs: [cosmosOutput],
handler: async (req, context) => {
const body = await req.json();
context.extraOutputs.set(cosmosOutput, body);
return { status: 201, jsonBody: body };
},
});
Not: Manually instantiating SDK clients for bound services
// BAD - reimplementing what bindings give you for free
import { CosmosClient } from "@azure/cosmos";
const client = new CosmosClient(process.env.COSMOS_CONNECTION!);
// Verbose, error-prone, no automatic retry or connection management from the runtime
Common Patterns
Durable Functions orchestration
import * as df from "durable-functions";
import { app } from "@azure/functions";
// Orchestrator - must be deterministic
df.app.orchestration("processOrderOrchestrator", function* (context) {
const order = context.df.getInput() as Order;
const validated = yield context.df.callActivity("validateOrder", order);
const payment = yield context.df.callActivity("processPayment", validated);
const shipped = yield context.df.callActivity("shipOrder", { order: validated, payment });
yield context.df.callActivity("sendConfirmation", { order: validated, tracking: shipped });
return { status: "completed", trackingId: shipped.trackingId };
});
// Activity functions - can do I/O
df.app.activity("validateOrder", { handler: async (order: Order) => {
// Validate inventory, pricing, etc.
return { ...order, validated: true };
}});
// HTTP starter
app.http("startOrder", {
methods: ["POST"],
route: "orders",
extraInputs: [df.input.durableClient()],
handler: async (req, context) => {
const client = df.getClient(context);
const body = await req.json();
const instanceId = await client.startNew("processOrderOrchestrator", { input: body });
return client.createCheckStatusResponse(req, instanceId);
},
});
Timer-triggered scheduled function
import { app, Timer, InvocationContext } from "@azure/functions";
app.timer("dailyCleanup", {
schedule: "0 0 2 * * *", // 2 AM daily (NCRONTAB: sec min hour day month dayOfWeek)
handler: async (timer: Timer, context: InvocationContext) => {
context.log("Running daily cleanup", { scheduledTime: timer.scheduleStatus });
await deleteExpiredRecords();
await archiveOldLogs();
},
});
Queue trigger with dead-letter handling
import { app, InvocationContext } from "@azure/functions";
app.storageQueue("processMessage", {
queueName: "incoming-messages",
connection: "StorageConnection",
handler: async (message: unknown, context: InvocationContext) => {
const data = message as { type: string; payload: unknown };
context.log(`Processing message type=${data.type}, attempt=${context.retryContext?.retryCount ?? 0}`);
if (context.retryContext && context.retryContext.retryCount >= 3) {
context.log("Max retries exceeded, message will move to poison queue");
throw new Error("Permanent failure after retries");
}
await handleMessage(data);
},
});
Anti-Patterns
- Blocking the orchestrator with I/O: Durable orchestrator functions must not perform I/O directly. All side effects belong in activity functions.
- Using
Date.now()in orchestrators: Non-deterministic calls break Durable Functions replay. Usecontext.df.currentUtcDateTimeinstead. - Deploying directly to production slot: Always deploy to a staging slot and swap. Direct deployment causes cold starts and risks downtime.
- Ignoring
host.jsonconfiguration: The default extension bundle versions, retry policies, and logging levels are rarely correct for production. Tunehost.jsonexplicitly.
When to Use
- Event-driven backends triggered by queues, timers, Event Grid, or HTTP
- Multi-step workflows requiring orchestration, retries, and human-in-the-loop patterns via Durable Functions
- CRUD APIs with automatic Cosmos DB, SQL, or Blob Storage bindings
- Scheduled jobs and maintenance tasks with NCRONTAB timer triggers
- Integration endpoints connecting Azure services, third-party webhooks, and legacy systems
Install this skill directly: skilldb add cloud-provider-services-skills
Related Skills
GCP Cloud Functions
Develop Google Cloud Functions with HTTP and event-driven triggers, including Pub/Sub, Cloud Storage, and Firestore events. Covers 2nd gen functions built on Cloud Run, structured logging, and proper TypeScript configuration for production workloads.
GCP Cloud Run
Deploy and manage containerized services on Google Cloud Run with proper concurrency tuning, minimum instance configuration, IAM-based authentication, and secret management. Covers Dockerfile patterns, traffic splitting, and production readiness.
GCP Pubsub
Implement Google Cloud Pub/Sub messaging with topic and subscription management, push and pull delivery modes, dead-letter queues, and message ordering. Covers exactly-once delivery, flow control, and TypeScript integration for event-driven architectures.
AWS Cognito
Configure and integrate AWS Cognito user pools and identity pools for authentication and authorization. Covers hosted UI setup, token verification, custom Lambda triggers, and secure patterns for single-page applications and API backends.
AWS Dynamodb Advanced
Design and implement advanced DynamoDB patterns including single-table design, global secondary indexes, transactional operations, DynamoDB Streams, and TTL-based expiration. Covers access patterns, partition key design, and cost optimization for TypeScript backends.
AWS Lambda
Build and optimize AWS Lambda functions with proper handler patterns, layer management, cold start mitigation, and event source integrations. Covers SAM templates, structured logging, and production-grade error handling for serverless TypeScript workloads.