You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Building serverless applications requires a different mindset from traditional application development. This lesson covers essential best practices for Azure serverless — from function design and cold start mitigation to security, cost optimisation, and comprehensive monitoring with Application Insights.
Each function should do one thing well:
Good: validateOrder, processPayment, sendConfirmation
Bad: handleEverything, processData, myFunction
Serverless functions may execute multiple times for the same event (at-least-once delivery). Every function must produce the same result regardless of how many times it runs:
// Idempotent — uses upsert, safe to run multiple times
app.storageQueue('processOrder', {
handler: async (message, context) => {
await cosmosContainer.items.upsert({
id: message.orderId, // Same ID = same document
status: 'processed',
processedAt: new Date().toISOString()
});
}
});
Functions are stateless. Never rely on in-memory state between invocations:
| State Type | Storage Option |
|---|---|
| Session data | Redis Cache |
| Application data | Cosmos DB, SQL Database |
| File uploads | Blob Storage |
| Configuration | App Configuration, Key Vault |
| Workflow state | Durable Functions |
| Cache | Redis Cache, APIM caching |
Cold starts are the most common complaint in serverless architectures. Here are strategies to reduce their impact:
| Plan | Cold Start | Cost |
|---|---|---|
| Consumption | 1–10 seconds | Lowest |
| Premium (pre-warmed) | Minimal | Moderate |
| Dedicated (always on) | None | Highest |
# Bad — installs everything including dev dependencies
npm install
# Good — production dependencies only
npm install --production
# Better — use esbuild or webpack to bundle and tree-shake
npx esbuild src/index.js --bundle --platform=node --outfile=dist/index.js
// Bad — loaded on every cold start even if not used
const sharp = require('sharp');
const pdfLib = require('pdf-lib');
// Good — loaded only when needed
app.http('resizeImage', {
handler: async (request, context) => {
const sharp = require('sharp'); // Loaded only for this function
// ...
}
});
Initialise clients outside the handler so they persist across warm invocations:
// Good — client created once, reused across invocations
const { CosmosClient } = require('@azure/cosmos');
const client = new CosmosClient(process.env.COSMOS_CONNECTION);
const container = client.database('mydb').container('items');
app.http('getItem', {
handler: async (request, context) => {
// Reuses the existing connection
const { resource } = await container.item(request.params.id).read();
return { jsonBody: resource };
}
});
Schedule a timer function to keep the host warm:
app.timer('keepAlive', {
schedule: '0 */4 * * * *', // Every 4 minutes
handler: async (timer, context) => {
context.log('Keep-alive ping');
}
});
Note: This is a workaround. For production workloads, the Premium plan is a better solution.
// Use function-level auth keys for service-to-service calls
app.http('internalApi', {
authLevel: 'function', // Requires function key
handler: async (request, context) => { /* ... */ }
});
// Use APIM with JWT validation for public APIs
// Configure in APIM policy, not in function code
Never hardcode secrets. Use Azure Key Vault with managed identity:
# Grant the function app access to Key Vault
az keyvault set-policy \
--name my-keyvault \
--object-id {function-app-managed-identity-id} \
--secret-permissions get list
# Reference Key Vault in application settings
az functionapp config appsettings set \
--name my-function-app \
--resource-group rg-serverless \
--settings "DB_PASSWORD=@Microsoft.KeyVault(SecretUri=https://my-keyvault.vault.azure.net/secrets/db-password/)"
Always validate and sanitise input:
app.http('createUser', {
methods: ['POST'],
handler: async (request, context) => {
const body = await request.json();
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.