How to deploy Node.js app to Azure Functions

Azure Functions provides serverless compute service for running Node.js code triggered by HTTP requests, timers, or Azure service events without infrastructure management. As the creator of CoreUI, a widely used open-source UI library, I’ve deployed serverless Node.js applications to Azure throughout my 11 years of backend development. The most efficient approach is using Azure Functions Core Tools for local development and Azure CLI or VS Code extension for deployment. This method enables event-driven serverless architecture, seamless Azure service integration, and consumption-based pricing with automatic scaling.

Install Azure Functions Core Tools, create HTTP trigger function, and deploy using Azure CLI for serverless execution.

# Install Azure Functions Core Tools
npm install -g azure-functions-core-tools@4

# Create new function app
func init my-function-app --worker-runtime node --language javascript
cd my-function-app

# Create HTTP trigger function
func new --template "HTTP trigger" --name api

Generated function structure:

// api/index.js - HTTP trigger function
module.exports = async function (context, req) {
  context.log('JavaScript HTTP trigger function processed a request.')

  const name = req.query.name || (req.body && req.body.name)
  const responseMessage = name
    ? `Hello, ${name}. This HTTP triggered function executed successfully.`
    : 'This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.'

  context.res = {
    status: 200,
    body: responseMessage
  }
}

Function configuration:

// api/function.json
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get", "post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

More complex API example:

// api/index.js - RESTful API
const users = [
  { id: '1', name: 'Alice', email: '[email protected]' },
  { id: '2', name: 'Bob', email: '[email protected]' }
]

module.exports = async function (context, req) {
  context.log(`HTTP trigger: ${req.method} ${req.url}`)

  // CORS headers
  context.res = {
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
      'Access-Control-Allow-Headers': 'Content-Type'
    }
  }

  // Handle preflight
  if (req.method === 'OPTIONS') {
    context.res.status = 204
    return
  }

  try {
    // Route handling
    const path = req.params.path || ''
    const method = req.method

    if (path === '' && method === 'GET') {
      // GET /api
      context.res.status = 200
      context.res.body = { message: 'API is running' }
    } else if (path === 'users' && method === 'GET') {
      // GET /api/users
      context.res.status = 200
      context.res.body = { users }
    } else if (path.startsWith('users/') && method === 'GET') {
      // GET /api/users/:id
      const id = path.split('/')[1]
      const user = users.find(u => u.id === id)

      if (user) {
        context.res.status = 200
        context.res.body = { user }
      } else {
        context.res.status = 404
        context.res.body = { error: 'User not found' }
      }
    } else if (path === 'users' && method === 'POST') {
      // POST /api/users
      const { name, email } = req.body
      const newUser = {
        id: String(users.length + 1),
        name,
        email
      }
      users.push(newUser)

      context.res.status = 201
      context.res.body = { user: newUser }
    } else {
      context.res.status = 404
      context.res.body = { error: 'Not found' }
    }
  } catch (error) {
    context.log.error('Error:', error)
    context.res.status = 500
    context.res.body = { error: 'Internal server error' }
  }
}

Timer trigger function:

// scheduled-task/index.js
module.exports = async function (context, myTimer) {
  const timeStamp = new Date().toISOString()

  if (myTimer.isPastDue) {
    context.log('Timer function is running late!')
  }

  context.log('Timer trigger function ran at:', timeStamp)

  // Perform scheduled task
  try {
    await performCleanup()
    context.log('Cleanup completed successfully')
  } catch (error) {
    context.log.error('Cleanup failed:', error)
    throw error
  }
}

async function performCleanup() {
  // Cleanup logic
  console.log('Performing cleanup...')
  return new Promise(resolve => setTimeout(resolve, 1000))
}

Timer configuration:

// scheduled-task/function.json
{
  "bindings": [
    {
      "name": "myTimer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 */5 * * * *"
    }
  ]
}

Local development:

# Start function app locally
func start

# Function will be available at:
# http://localhost:7071/api/api

# Test with curl
curl http://localhost:7071/api/api
curl http://localhost:7071/api/api?name=John
curl -X POST http://localhost:7071/api/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Charlie","email":"[email protected]"}'

Application configuration:

// local.settings.json - Local development settings
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "NODE_ENV": "development",
    "DATABASE_URL": "your-database-url",
    "API_KEY": "your-api-key"
  },
  "Host": {
    "CORS": "*"
  }
}

Deployment with Azure CLI:

# Install Azure CLI
# Visit: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli

# Login to Azure
az login

# Create resource group
az group create \
  --name my-functions-rg \
  --location eastus

# Create storage account
az storage account create \
  --name myfunctionsstorage \
  --resource-group my-functions-rg \
  --location eastus \
  --sku Standard_LRS

# Create function app
az functionapp create \
  --name my-function-app \
  --resource-group my-functions-rg \
  --storage-account myfunctionsstorage \
  --consumption-plan-location eastus \
  --runtime node \
  --runtime-version 20 \
  --functions-version 4

# Deploy function app
func azure functionapp publish my-function-app

# Set environment variables
az functionapp config appsettings set \
  --name my-function-app \
  --resource-group my-functions-rg \
  --settings \
    NODE_ENV=production \
    DATABASE_URL=your-production-db-url

# View logs
func azure functionapp logstream my-function-app

Using VS Code extension:

# Install Azure Functions extension for VS Code
# Extension ID: ms-azuretools.vscode-azurefunctions

# Steps:
# 1. Open VS Code
# 2. Install Azure Functions extension
# 3. Sign in to Azure
# 4. Click "Deploy to Function App" button
# 5. Select or create Function App
# 6. Extension deploys automatically

Production configuration:

// host.json - Function app configuration
{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "maxTelemetryItemsPerSecond": 20
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  },
  "functionTimeout": "00:05:00"
}

Package configuration:

{
  "name": "my-function-app",
  "version": "1.0.0",
  "scripts": {
    "start": "func start",
    "test": "jest"
  },
  "dependencies": {
    "@azure/functions": "^4.0.0"
  },
  "devDependencies": {
    "azure-functions-core-tools": "^4.0.0"
  }
}

Monitoring and management:

# View function status
az functionapp show \
  --name my-function-app \
  --resource-group my-functions-rg

# Get function URL
az functionapp function show \
  --name my-function-app \
  --resource-group my-functions-rg \
  --function-name api

# Restart function app
az functionapp restart \
  --name my-function-app \
  --resource-group my-functions-rg

# Delete function app
az functionapp delete \
  --name my-function-app \
  --resource-group my-functions-rg

# View Application Insights data
az monitor app-insights component show \
  --app my-function-app \
  --resource-group my-functions-rg

Here the Azure Functions Core Tools provide local development environment matching Azure runtime. The function.json defines trigger bindings, authentication level, and HTTP methods. The context object provides logging, input bindings, and response output. Timer triggers use cron expressions for scheduled execution. The local.settings.json stores environment variables for local development. Azure CLI commands create resource groups, storage accounts, and function apps for deployment. Application Insights integration enables production monitoring and diagnostics.

Best Practice Note:

This is the serverless deployment approach we use for CoreUI Azure-hosted microservices requiring seamless integration with Azure ecosystem services. Use Application Insights for comprehensive monitoring and distributed tracing, implement Durable Functions for complex stateful workflows, leverage Azure Key Vault for secure secret management instead of environment variables, and configure custom domains with Azure Front Door for production-grade API endpoints with global distribution.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
CSS Selector for Parent Element
CSS Selector for Parent Element

How to convert a string to boolean in JavaScript
How to convert a string to boolean in JavaScript

How to Redirect to a New URL Using JavaScript Redirect Techniques
How to Redirect to a New URL Using JavaScript Redirect Techniques

How to Achieve Perfectly Rounded Corners in CSS
How to Achieve Perfectly Rounded Corners in CSS

Answers by CoreUI Core Team