A comprehensive Node.js application built with Azure Durable Functions, designed for demonstrating and load testing stateful serverless workflows. Features orchestrations, activities, durable entities, and dual storage backends.
This application showcases Azure Durable Functions patterns for load testing scenarios:
- HTTP-Triggered Orchestrations - Fan-out/fan-in and sequential workflow patterns
- Session Management - CRUD operations with Azure Table Storage or Cosmos DB
- Durable Entities - Stateful metrics tracking and session counters
- Activity Functions - Parallel and sequential task processing
- Application Insights - Full observability with custom telemetry
- Authentication - JWT validation with Azure Entra ID (optional bypass for testing)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Azure Durable Functions β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β HTTP APIs Orchestrators Activities β
β ββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Sessions βββββββΆβ Workflow βββββΆβ ProcessItem β β
β β - POST β β Orchestrator β β Aggregate β β
β β - GET β ββββββββββββββββ β UpdateMetricsβ β
β β - DELETEβ ββββββββββββββββ β
β ββββββββββββ ββββββββββββββββ β
β β Fan-Out/In β Durable Entities β
β ββββββββββββ β Orchestrator β ββββββββββββββββ β
β βOrchestrateββββββΆββββββββββββββββ β Metrics β β
β β - POST β β Counter β β
β β - GET β ββββββββββββββββ β
β ββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Storage Layer β
β ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ β
β β Azure Table Storage β β Azure Cosmos DB β β
β β (Default - Dev/Test) β β (Optional - Production)β β
β ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββ
β Application Insights β
β - Telemetry β
β - Custom Metrics β
β - Distributed Tracing β
βββββββββββββββββββββββββ
Before starting, ensure you have:
- Node.js v20.17.0 or higher (v22+ recommended)
- npm v10.8.2 or higher
- Azure Functions Core Tools v4.x - Install Guide
- Azurite (Azure Storage Emulator) - Install Guide
- Azure CLI v2.50.0+ - Install Guide
- Terraform v1.5.0+ - Install Guide
- Azure Subscription with appropriate permissions
node --version # Should be v20.17.0 or higher
npm --version # Should be v10.8.2 or higher
func --version # Should be 4.x
azurite --version # Should be 3.xgit clone <repository-url>
cd node-durable
npm installcp local.settings.json.example local.settings.jsonEdit local.settings.json if needed (defaults work for local development):
{
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "node",
"STORAGE_TYPE": "tables",
"BYPASS_AUTH": "true"
}
}Option A - Separate Terminal:
azurite --silentOption B - Background (Windows PowerShell):
Start-Process -NoNewWindow azurite -ArgumentList "--silent"Option C - NPX (one-time):
npx azurite --silentnpm run build
npm startYou should see output like:
Azure Functions Core Tools
Core Tools Version: 4.0.5907 Commit hash: N/A +591b8aec842e333a87ea9e23ba390bb5effe0655 (64-bit)
Function Runtime Version: 4.28.5.21810
Functions:
createSession: [POST] http://localhost:7071/api/sessions
deleteSession: [DELETE] http://localhost:7071/api/sessions/{id}
getSession: [GET] http://localhost:7071/api/sessions/{id}
orchestrateFanOut: [POST] http://localhost:7071/api/orchestrate/fanout
orchestrateStart: [POST] http://localhost:7071/api/orchestrate
orchestrateStatus: [GET] http://localhost:7071/api/orchestrate/{instanceId}
For detailed output, run func with --verbose flag.
Test Session Creation:
curl -X POST http://localhost:7071/api/sessions \
-H "Content-Type: application/json" \
-d '{"userId": "user123", "data": {"name": "Test User"}}'Expected response (HTTP 201):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "user123",
"data": { "name": "Test User" },
"createdAt": "2026-03-25T12:00:00.000Z",
"lastAccessedAt": "2026-03-25T12:00:00.000Z"
}Test Session Retrieval:
curl http://localhost:7071/api/sessions/{sessionId}Test Orchestration (Note: Known issue with durable client binding):
curl -X POST http://localhost:7071/api/orchestrate \
-H "Content-Type: application/json" \
-d '{"items": ["item1", "item2"], "userId": "user123"}'POST /api/sessions
Content-Type: application/json
{
"userId": "string (required)",
"data": {
// Any JSON object (optional)
}
}Response (201 Created):
{
"id": "uuid",
"userId": "string",
"data": {},
"createdAt": "ISO8601",
"lastAccessedAt": "ISO8601"
}GET /api/sessions/{id}Response (200 OK):
{
"id": "uuid",
"userId": "string",
"data": {},
"createdAt": "ISO8601",
"lastAccessedAt": "ISO8601"
}DELETE /api/sessions/{id}Response (200 OK):
{
"message": "Session deleted successfully"
}POST /api/orchestrate
Content-Type: application/json
{
"items": ["string"],
"userId": "string"
}Response (202 Accepted):
{
"instanceId": "uuid",
"statusQueryGetUri": "http://...",
"sendEventPostUri": "http://...",
"terminatePostUri": "http://...",
"purgeHistoryDeleteUri": "http://..."
}GET /api/orchestrate/{instanceId}Response (200 OK):
{
"name": "processWorkflowOrchestrator",
"instanceId": "uuid",
"runtimeStatus": "Completed|Running|Failed",
"input": {},
"output": {},
"createdTime": "ISO8601",
"lastUpdatedTime": "ISO8601"
}POST /api/orchestrate/fanout
Content-Type: application/json
{
"items": ["string"],
"userId": "string"
}| Variable | Description | Default | Required |
|---|---|---|---|
AzureWebJobsStorage |
Storage connection string | UseDevelopmentStorage=true |
Yes |
FUNCTIONS_WORKER_RUNTIME |
Runtime environment | node |
Yes |
FUNCTIONS_NODE_VERSION |
Node.js version | 22 |
No |
STORAGE_TYPE |
Storage backend (tables or cosmos) |
tables |
Yes |
STORAGE_ACCOUNT_NAME |
Azure Storage account name | - | If using Azure |
COSMOS_ENDPOINT |
Cosmos DB endpoint | - | If STORAGE_TYPE=cosmos |
BYPASS_AUTH |
Skip authentication for testing | true |
No |
ENTRA_TENANT_ID |
Azure Entra ID tenant | - | If auth enabled |
ENTRA_CLIENT_ID |
Azure Entra ID app client ID | - | If auth enabled |
APPLICATIONINSIGHTS_CONNECTION_STRING |
Application Insights connection | - | No |
- Pros: Lower cost, simpler setup, sufficient for most scenarios
- Cons: Limited query capabilities, single-region
- Configuration: Set
STORAGE_TYPE=tables
- Pros: Global distribution, better performance at scale, advanced queries
- Cons: Higher cost, more complexity
- Configuration:
- Set
STORAGE_TYPE=cosmos - Set
COSMOS_ENDPOINTto your Cosmos DB endpoint - Enable in Terraform:
enable_cosmos_db = true
- Set
See API Documentation above for endpoint examples.
- Azurite Running - Check
http://localhost:10000/accessible - Functions Running - Check logs show "Worker process started and initialized"
- Create Session - POST to
/api/sessionsreturns 201 - Get Session - GET to
/api/sessions/{id}returns 200 - Delete Session - DELETE to
/api/sessions/{id}returns 200 - Start Orchestration - POST to
/api/orchestratereturns 202 - Check Status - GET orchestration status
- Fan-Out Pattern - POST to
/api/orchestrate/fanout
Problem: On some machines, func start may fail with System.Net.Http, Version=8.0.0.0 assembly not found
Impact: Prevents local startup. Does not affect Azure deployments.
Fix: Update Azure Functions Core Tools to the latest version:
npm install -g azure-functions-core-tools@4 --unsafe-perm trueThe application includes comprehensive Application Insights telemetry:
- HTTP requests and responses
- Dependencies (Storage, Cosmos DB calls)
- Performance counters
- Exceptions and errors
- Console logs
- Heartbeat metrics
- Events:
SessionCreated,SessionRetrieved - Metrics:
SessionsCreatedcounter - Exceptions: Operation-specific error tracking
Application Insights gracefully skips telemetry when APPLICATIONINSIGHTS_CONNECTION_STRING is empty. Set the connection string to enable telemetry during local testing.
Once deployed to Azure:
- Navigate to Application Insights in Azure Portal
- View Live Metrics for real-time telemetry
- Query Logs with KQL for detailed analysis
- Check Performance for request insights
See detailed instructions in infra/README.md.
Quick Deploy:
cd infra
# Configure variables
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your values
# Initialize and deploy
terraform init
terraform plan
terraform apply
# Deploy application code
cd ..
npm run build
func azure functionapp publish $(terraform -chdir=infra output -raw function_app_name)# Login to Azure
az login
# Create resource group
az group create --name rg-durable-functions --location eastus
# Create storage account
az storage account create \
--name stdurabledev \
--resource-group rg-durable-functions \
--location eastus \
--sku Standard_LRS
# Create function app
az functionapp create \
--name func-durable-loadtest \
--resource-group rg-durable-functions \
--storage-account stdurabledev \
--consumption-plan-location eastus \
--runtime node \
--runtime-version 20 \
--functions-version 4 \
--os-type Linux
# Deploy code
npm run build
func azure functionapp publish func-durable-loadtestSee dedicated guide: LOAD_TESTING.md
Key considerations:
- Use Premium plan (EP1+) to avoid cold starts skewing results
- Disable authentication bypass (
BYPASS_AUTH=false) for production testing - Enable Application Insights for detailed performance metrics
- Test both session CRUD and orchestration patterns
- Monitor storage throttling and RU consumption
See dedicated guide: docs/SCALING_AND_CONFIGURATION.md
The host.json file controls how the function app scales under load. Key settings:
| Setting | Default | Purpose |
|---|---|---|
maxConcurrentActivityFunctions |
10 | Activity parallelism per instance |
maxConcurrentOrchestratorFunctions |
10 | Orchestrator parallelism per instance |
partitionCount |
4 | Max instances for orchestration processing |
extendedSessionsEnabled |
true | Reduces replay overhead |
maxConcurrentRequests |
100 | HTTP concurrency per instance |
dynamicConcurrencyEnabled |
true | Auto-tunes concurrency at runtime |
The scaling guide covers:
- What each
host.jsonsetting does and how to tune it - Scaling profiles for dev, load testing, and production
- KQL queries to monitor scaling behavior in Application Insights
- Common scaling pitfalls and how to fix them
node-durable/
βββ src/
β βββ activities/
β β βββ activities.ts # Activity functions for orchestrations
β βββ entities/
β β βββ index.ts # Entity exports
β β βββ metricsEntity.ts # Metrics tracking entity
β β βββ sessionCounterEntity.ts # Session counter entity
β βββ functions/
β β βββ orchestrationApi.ts # Orchestration HTTP endpoints
β β βββ sessionApi.ts # Session CRUD endpoints
β βββ models/
β β βββ types.ts # TypeScript interfaces
β βββ orchestrators/
β β βββ fanOutFanInOrchestrator.ts # Parallel processing pattern
β β βββ processWorkflowOrchestrator.ts # Sequential workflow pattern
β βββ services/
β β βββ AzureTableStorageService.ts # Table Storage implementation
β β βββ CosmosDbStorageService.ts # Cosmos DB implementation
β β βββ IStorageService.ts # Storage interface
β β βββ StorageServiceFactory.ts # Storage factory pattern
β βββ utils/
β β βββ appInsights.ts # Application Insights integration
β β βββ authMiddleware.ts # JWT authentication
β β βββ durableClient.ts # Durable Functions client helper
β βββ index.ts # Application entry point
βββ infra/
β βββ main.tf # Terraform main configuration
β βββ variables.tf # Terraform variables
β βββ outputs.tf # Terraform outputs
β βββ providers.tf # Provider configuration
β βββ terraform.tfvars.example # Example variables
β βββ README.md # Infrastructure documentation
βββ docs/
β βββ AUTHENTICATION.md # Authentication guide
β βββ plan/ # Project planning documents
βββ host.json # Azure Functions host configuration
βββ local.settings.json # Local environment settings (not in git)
βββ local.settings.json.example # Local settings template
βββ package.json # Node.js dependencies
βββ tsconfig.json # TypeScript configuration
βββ README.md # This file
The application supports Azure Entra ID (Azure AD) authentication:
Set BYPASS_AUTH=true in local.settings.json to skip authentication during local testing.
- Create Azure AD App Registration
- Configure
ENTRA_TENANT_ID,ENTRA_CLIENT_ID,ENTRA_AUDIENCE - Set
BYPASS_AUTH=false - Include JWT token in
Authorization: Bearer <token>header
See detailed guide: docs/AUTHENTICATION.md
# Check if port 10000 is in use
netstat -ano | findstr :10000
# Kill process using port (Windows)
taskkill /PID <pid> /F
# Or specify different ports
azurite --blobPort 10001 --queuePort 10002 --tablePort 10003# Clean and rebuild
npm run clean
npm install
npm run build
# Check for compilation errors
npm run build -- --listFilesThe application automatically creates the sessions table on first insert. If you see this error:
- Ensure Azurite is running
- Check
AzureWebJobsStorageconnection string is correct - Verify storage service initialization logs
# Test Azurite connectivity
curl http://localhost:10000/
# Expected: Azure Blob Storage Emulator response# Check TypeScript version
npx tsc --version
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm installThis is a known issue. See Known Issues section above.
npm run build # Compile TypeScript
npm run watch # Watch mode for development
npm run clean # Remove dist/ directorynpm run lint # Run ESLint (if configured)
npm test # Run tests (if configured)- Create function file in
src/functions/ - Import in
src/index.ts - Rebuild:
npm run build - Test locally:
func start
import { trackEvent, trackMetric } from './utils/appInsights';
// Track custom event
trackEvent('MyEvent', { userId: 'user123' }, { duration: 150 });
// Track custom metric
trackMetric('ItemsProcessed', 42, { operation: 'batch' });- Azure Durable Functions Documentation
- Azure Functions Node.js Developer Guide
- Azure Load Testing Documentation
- Application Insights for Node.js
- Azurite Storage Emulator
- Create feature branch
- Make changes with tests
- Ensure
npm run buildsucceeds - Submit pull request
MIT License - See LICENSE file for details
For issues or questions:
- Check Troubleshooting section
- Review Known Issues
- Search existing issues in repository
- Create new issue with:
- Error messages
- Steps to reproduce
- Environment details (OS, Node version, etc.)
Built with β€οΈ using Azure Durable Functions