Azure Functions
Deploy your Vercube application to Azure Functions with complete support for HTTP triggers, efficient streaming, and seamless cookie management.
Basic Setup
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
import { toServerlessHandler } from '@vercube/serverless/azure-functions';
import { app as vercubeApp } from '../index';
const handler = toServerlessHandler(vercubeApp);
export async function httpTrigger(
request: HttpRequest,
context: InvocationContext
): Promise<HttpResponseInit> {
return await handler(request);
}
app.http('httpTrigger', {
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
authLevel: 'anonymous',
handler: httpTrigger,
route: '{*route}' // Catch all routes
});
Request Conversion
The adapter converts Azure Functions HttpRequest to standard web Request:
Input Format
// Azure HttpRequest
{
method: 'GET',
url: 'https://myapp.azurewebsites.net/api/users?page=1&limit=10',
headers: {
'content-type': 'application/json',
'authorization': 'Bearer token...',
'cookie': 'session_id=abc123; theme=dark'
},
query: {
page: '1',
limit: '10'
},
params: {
id: '123'
},
body: ReadableStream { ... }
}
Output Format
// Standard Request
{
method: 'GET',
url: 'https://myapp.azurewebsites.net/api/users?page=1&limit=10',
headers: Headers {
'content-type': 'application/json',
'authorization': 'Bearer token...',
'cookie': 'session_id=abc123; theme=dark'
},
body: ReadableStream { ... }
}
Response Conversion with Streaming
Azure Functions responses use AsyncIterableIterator for efficient data streaming:
@Controller('/data')
export class DataController {
@Inject(DataService)
private dataService!: DataService;
@Get('/export')
async exportData() {
const data = await this.dataService.getLargeDataset();
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Content-Disposition': 'attachment; filename="export.json"'
}
});
}
@Get('/stream')
async streamData() {
const stream = this.dataService.createDataStream();
return new Response(stream, {
headers: {
'Content-Type': 'application/json',
'Transfer-Encoding': 'chunked'
}
});
}
}
The adapter automatically:
- Reads the Response body
- Converts it to
AsyncIterableIterator<Uint8Array> - Sets proper headers
- Streams data efficiently to the client
Cookie Handling
Cookies are properly handled through Set-Cookie headers:
@Controller('/auth')
export class AuthController {
@Inject(AuthService)
private authService!: AuthService;
@Post('/login')
async login(@Body({ validationSchema: LoginSchema }) credentials: LoginDto) {
const token = await this.authService.generateToken(credentials);
// Set authentication cookie
return FastResponse.ok({ success: true })
.cookie('session_id', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600
})
.cookie('user_preferences', 'theme=dark', {
maxAge: 86400
});
}
@Post('/logout')
async logout() {
// Clear authentication cookie
return FastResponse.ok({ success: true })
.cookie('session_id', '', {
httpOnly: true,
secure: true,
maxAge: 0
});
}
@Get('/session')
async getSession(@Cookie('session_id') sessionId: string) {
if (!sessionId) {
throw new UnauthorizedException('No active session');
}
const session = await this.authService.validateSession(sessionId);
return { session };
}
}
How it works:
- Multiple cookies via multiple
Set-Cookieheaders - Proper cookie attribute handling (HttpOnly, Secure, SameSite)
- Cookie parsing from request headers
Azure-Specific Features
Invocation Context
Access Azure Functions execution context:
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
import { toServerlessHandler } from '@vercube/serverless/azure-functions';
import { app as vercubeApp } from '../index';
const handler = toServerlessHandler(vercubeApp);
export async function httpTrigger(
request: HttpRequest,
context: InvocationContext
): Promise<HttpResponseInit> {
// Log invocation details
context.log('HTTP trigger function processed request');
context.log('Request URL:', request.url);
context.log('Request method:', request.method);
context.log('Invocation ID:', context.invocationId);
// Set trace context
context.traceContext.traceparent = request.headers.get('traceparent');
try {
const result = await handler(request);
context.log('Request completed successfully');
return result;
} catch (error) {
context.error('Request failed:', error);
throw error;
}
}
app.http('httpTrigger', {
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
authLevel: 'anonymous',
handler: httpTrigger,
route: '{*route}'
});
Environment Variables
Access Azure Functions environment variables:
import { RuntimeConfig } from '@vercube/core';
@Controller('/config')
export class ConfigController {
@Get('/azure-info')
getAzureInfo() {
return {
// Azure Functions environment variables
siteName: process.env.WEBSITE_SITE_NAME,
resourceGroup: process.env.WEBSITE_RESOURCE_GROUP,
region: process.env.REGION_NAME,
instanceId: process.env.WEBSITE_INSTANCE_ID,
hostname: process.env.WEBSITE_HOSTNAME,
// Function execution details
functionsVersion: process.env.FUNCTIONS_EXTENSION_VERSION,
workerRuntime: process.env.FUNCTIONS_WORKER_RUNTIME
};
}
@Get('/app-settings')
getAppSettings() {
return {
nodeEnv: process.env.NODE_ENV,
databaseUrl: process.env.DATABASE_URL ? 'configured' : 'not configured',
customSetting: process.env.CUSTOM_SETTING
};
}
}
Function Configuration
Basic host.json
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"maxTelemetryItemsPerSecond": 20,
"excludedTypes": "Request"
}
},
"logLevel": {
"default": "Information",
"Function": "Information"
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
Advanced host.json
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"maxTelemetryItemsPerSecond": 20
},
"enableDependencyTracking": true
},
"logLevel": {
"default": "Information",
"Host.Results": "Error",
"Function.httpTrigger": "Debug"
}
},
"http": {
"routePrefix": "api",
"maxOutstandingRequests": 200,
"maxConcurrentRequests": 100,
"dynamicThrottlesEnabled": true
},
"functionTimeout": "00:05:00",
"healthMonitor": {
"enabled": true,
"healthCheckInterval": "00:00:10",
"healthCheckWindow": "00:02:00",
"healthCheckThreshold": 6,
"counterThreshold": 0.80
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
Function-Specific Configuration
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
import { toServerlessHandler } from '@vercube/serverless/azure-functions';
import { app as vercubeApp } from '../index';
const handler = toServerlessHandler(vercubeApp);
export async function httpTrigger(
request: HttpRequest,
context: InvocationContext
): Promise<HttpResponseInit> {
return await handler(request);
}
app.http('httpTrigger', {
// HTTP methods
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
// Authentication level
authLevel: 'anonymous', // or 'function', 'admin'
// Route pattern
route: '{*route}',
// Handler function
handler: httpTrigger
});
// Add more function endpoints
app.http('adminTrigger', {
methods: ['GET', 'POST'],
authLevel: 'admin',
route: 'admin/{*route}',
handler: async (request: HttpRequest) => {
// Admin-only endpoint
return await handler(request);
}
});
Database Connections
Handle database connections efficiently in Azure Functions:
Connection Pooling
export class DatabaseService {
private static pool: Pool | null = null;
async getPool() {
// Reuse connection pool across invocations
if (DatabaseService.pool) {
return DatabaseService.pool;
}
DatabaseService.pool = new Pool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
// Azure Functions optimized settings
max: 10, // Higher than Lambda due to concurrent request handling
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
return DatabaseService.pool;
}
async query(sql: string, params?: any[]) {
const pool = await this.getPool();
return await pool.query(sql, params);
}
}
Azure SQL Database
import sql from 'mssql';
export class AzureSqlService {
private static pool: sql.ConnectionPool | null = null;
async getPool() {
if (AzureSqlService.pool) {
return AzureSqlService.pool;
}
const config = {
server: process.env.AZURE_SQL_SERVER!,
database: process.env.AZURE_SQL_DATABASE!,
authentication: {
type: 'azure-active-directory-default' as const
},
options: {
encrypt: true,
enableArithAbort: true
},
pool: {
max: 10,
min: 0,
idleTimeoutMillis: 30000
}
};
AzureSqlService.pool = await sql.connect(config);
return AzureSqlService.pool;
}
async query(queryText: string) {
const pool = await this.getPool();
const result = await pool.request().query(queryText);
return result.recordset;
}
}
Application Insights Integration
Enable Application Insights
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "",
"APPINSIGHTS_INSTRUMENTATIONKEY": "your-instrumentation-key",
"APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=your-key;..."
}
}
Custom Telemetry
import { TelemetryClient } from 'applicationinsights';
const telemetry = new TelemetryClient(
process.env.APPLICATIONINSIGHTS_CONNECTION_STRING
);
@Controller('/tracked')
export class TrackedController {
@Post('/order')
async createOrder(@Body() order: OrderDto) {
const startTime = Date.now();
try {
// Track custom event
telemetry.trackEvent({
name: 'OrderCreated',
properties: {
userId: order.userId,
items: order.items.length
}
});
const result = await this.orderService.create(order);
// Track custom metric
telemetry.trackMetric({
name: 'OrderProcessingTime',
value: Date.now() - startTime
});
return result;
} catch (error) {
// Track exception
telemetry.trackException({
exception: error,
properties: {
userId: order.userId
}
});
throw error;
}
}
}
Advanced Response Handling
Custom Headers
@Controller('/api')
export class ApiController {
@Get('/cached-data')
getCachedData() {
return FastResponse.ok({ data: 'cached' })
.header('Cache-Control', 'public, max-age=3600')
.header('X-Custom-Header', 'custom-value')
.header('Access-Control-Allow-Origin', '*');
}
@Get('/download')
downloadFile() {
const fileContent = Buffer.from('file content');
return new Response(fileContent, {
headers: {
'Content-Type': 'application/octet-stream',
'Content-Disposition': 'attachment; filename="file.txt"',
'Content-Length': fileContent.length.toString()
}
});
}
}
Error Handling
import { NotFoundException, BadRequestException, InternalServerErrorException } from '@vercube/core';
import { InvocationContext } from '@azure/functions';
export async function httpTrigger(
request: HttpRequest,
context: InvocationContext
): Promise<HttpResponseInit> {
try {
return await handler(request);
} catch (error) {
// Log to Azure
context.error('Request failed:', error);
// Return appropriate error response
if (error instanceof NotFoundException) {
return {
status: 404,
jsonBody: {
statusCode: 404,
message: error.message
}
};
}
return {
status: 500,
jsonBody: {
statusCode: 500,
message: 'Internal server error'
}
};
}
}
Deployment
Deploy Using Azure CLI
# Login to Azure
az login
# Create resource group
az group create --name MyResourceGroup --location eastus
# Create storage account
az storage account create \
--name mystorageaccount \
--resource-group MyResourceGroup \
--location eastus \
--sku Standard_LRS
# Create function app
az functionapp create \
--name MyFunctionApp \
--resource-group MyResourceGroup \
--storage-account mystorageaccount \
--consumption-plan-location eastus \
--runtime node \
--runtime-version 18 \
--functions-version 4
# Deploy
func azure functionapp publish MyFunctionApp
Deploy Using Azure Functions Core Tools
# Install Azure Functions Core Tools
npm install -g azure-functions-core-tools@4
# Initialize function app
func init --worker-runtime node --language typescript
# Start local development
func start
# Deploy to Azure
func azure functionapp publish <APP_NAME>
Deploy with GitHub Actions
name: Deploy to Azure Functions
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to Azure Functions
uses: Azure/functions-action@v1
with:
app-name: ${{ secrets.AZURE_FUNCTIONAPP_NAME }}
package: .
publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}
Environment Configuration
Local Development
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
// Application settings
"NODE_ENV": "development",
"DATABASE_URL": "postgresql://localhost:5432/myapp",
"REDIS_URL": "redis://localhost:6379",
"JWT_SECRET": "dev-secret",
// Azure services
"AZURE_STORAGE_CONNECTION_STRING": "...",
"COSMOS_DB_ENDPOINT": "...",
"COSMOS_DB_KEY": "...",
// Application Insights
"APPINSIGHTS_INSTRUMENTATIONKEY": "...",
"APPLICATIONINSIGHTS_CONNECTION_STRING": "..."
},
"Host": {
"CORS": "*",
"CORSCredentials": false
}
}
Production Configuration
Configure application settings in Azure Portal:
# Using Azure CLI
az functionapp config appsettings set \
--name MyFunctionApp \
--resource-group MyResourceGroup \
--settings \
NODE_ENV=production \
DATABASE_URL="postgresql://..." \
JWT_SECRET="production-secret"
Monitoring and Debugging
Application Insights Queries
// Query failed requests
requests
| where success == false
| where timestamp > ago(1h)
| project timestamp, name, resultCode, duration
| order by timestamp desc
// Query slow requests
requests
| where duration > 1000
| where timestamp > ago(1h)
| project timestamp, name, duration, resultCode
| order by duration desc
// Query exceptions
exceptions
| where timestamp > ago(1h)
| project timestamp, type, outerMessage, innerMessage
| order by timestamp desc
// Custom events
customEvents
| where name == "OrderCreated"
| where timestamp > ago(24h)
| summarize count() by bin(timestamp, 1h)
Live Metrics
Monitor your function in real-time:
# View live metrics in portal
# Navigate to: Function App > Monitoring > Live Metrics
Log Streaming
# Stream logs using Azure CLI
az webapp log tail \
--name MyFunctionApp \
--resource-group MyResourceGroup
# Or using Core Tools
func azure functionapp logstream MyFunctionApp
Performance Optimization
Cold Start Reduction
// Initialize at module level
import { createApp } from '@vercube/core';
import { toServerlessHandler } from '@vercube/serverless/azure-functions';
const app = createApp({
setup: async (app) => {
// Initialize heavy services once
app.container.bind(DatabaseService);
app.container.bind(CacheService);
const db = app.container.get(DatabaseService);
await db.connect();
}
});
const handler = toServerlessHandler(app);
export async function httpTrigger(
request: HttpRequest,
context: InvocationContext
): Promise<HttpResponseInit> {
// Fast execution - just handle the request
return await handler(request);
}
Always-On Setting
Enable Always On to prevent cold starts:
# Using Azure CLI
az functionapp config set \
--name MyFunctionApp \
--resource-group MyResourceGroup \
--always-on true
Premium Plan
Use Azure Functions Premium Plan for better performance:
# Create Premium plan
az functionapp plan create \
--name MyPremiumPlan \
--resource-group MyResourceGroup \
--location eastus \
--sku EP1 \
--is-linux false
# Create function app with Premium plan
az functionapp create \
--name MyFunctionApp \
--resource-group MyResourceGroup \
--plan MyPremiumPlan \
--runtime node \
--runtime-version 18
Troubleshooting
Common Issues
Function not triggering
Error: No HTTP triggers found
Solution: Ensure route configuration is correct:
app.http('httpTrigger', {
route: '{*route}', // Catch all routes
handler: httpTrigger
});
CORS errors
Access to fetch has been blocked by CORS policy
Solution: Configure CORS in host.json:
{
"version": "2.0",
"extensions": {
"http": {
"routePrefix": "api",
"cors": {
"allowedOrigins": ["*"],
"allowedMethods": ["GET", "POST", "PUT", "DELETE"],
"allowedHeaders": ["*"]
}
}
}
}
Request timeout
Function execution timed out
Solution: Increase timeout in host.json:
{
"functionTimeout": "00:10:00"
}
Memory issues
JavaScript heap out of memory
Solution: Increase memory by upgrading plan or optimizing code
Connection string not found
AzureWebJobsStorage connection string not found
Solution: Set storage connection string:
{
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;..."
}
}
Best Practices
Use Application Insights for monitoring
// Always enable Application Insights
const telemetry = new TelemetryClient();
telemetry.trackEvent({ name: 'CustomEvent' });
Handle concurrent requests efficiently
// Use proper connection pooling
const pool = new Pool({
max: 10, // Azure Functions can handle multiple concurrent requests
idleTimeoutMillis: 30000
});
Implement proper error handling
export async function httpTrigger(
request: HttpRequest,
context: InvocationContext
): Promise<HttpResponseInit> {
try {
return await handler(request);
} catch (error) {
context.error('Error:', error);
return {
status: 500,
jsonBody: { error: 'Internal server error' }
};
}
}
Use managed identity for Azure services
// Instead of connection strings, use managed identity
import { DefaultAzureCredential } from '@azure/identity';
const credential = new DefaultAzureCredential();
const client = new BlobServiceClient(
`https://${accountName}.blob.core.windows.net`,
credential
);