Edit

Quickstart: Respond to database changes in Azure Cosmos DB using Azure Functions

In this Quickstart, you use Visual Studio Code to build an app that responds to database changes in a No SQL database in Azure Cosmos DB. After testing the code locally, you deploy it to a new serverless function app you create running in a Flex Consumption plan in Azure Functions.

The project source uses the Azure Developer CLI (azd) extension with Visual Studio Code to simplify initializing and verifying your project code locally, as well as deploying your code to Azure. This deployment follows current best practices for secure and scalable Azure Functions deployments.

While the Flex Consumption plan follows a pay-for-what-you-use billing model, this code project creates additional Azure resources, including an Azure Cosmos DB instance. Make sure to clean up resources when you're done to avoid ongoing charges.

This article supports version 4 of the Node.js programming model for Azure Functions.

This article supports version 2 of the Python programming model for Azure Functions.

Prerequisites

  • Node.js 18.x or above. Use the node --version command to check your version.

Initialize the project

You can use the azd init command from the command palette to create a local Azure Functions code project from a template.

  1. In Visual Studio Code, open a folder or workspace in which you want to create your project.

  2. Press F1 to open the command palette, search for and run the command Azure Developer CLI (azd): Initialize App (init), and then choose Select a template.

    There might be a slight delay while azd initializes the current folder or workspace.

  1. When prompted, choose Select a template, then search for and select Azure Functions with Cosmos DB Bindings (.NET).

  2. When prompted, enter a unique environment name, such as cosmosdbchanges-dotnet.

    This command pulls the project files from the template repository and initializes the project in the current folder or workspace. In azd, the environment is used to maintain a unique deployment context for your app, and you can define more than one. It's also part of the name of the resource group you create in Azure.

  1. When prompted, choose Select a template, then search for and select Azure Functions with Cosmos DB Bindings (Java).

  2. When prompted, enter a unique environment name, such as cosmosdbchanges-java.

    This command pulls the project files from the template repository and initializes the project in the current folder or workspace. In azd, the environment is used to maintain a unique deployment context for your app, and you can define more than one. It's also part of the name of the resource group you create in Azure.

  1. When prompted, choose Select a template, then search for and select Azure Functions JavaScript CosmosDB trigger.

  2. When prompted, enter a unique environment name, such as cosmosdbchanges-js.

    This command pulls the project files from the template repository and initializes the project in the current folder or workspace. In azd, the environment is used to maintain a unique deployment context for your app, and you can define more than one. It's also part of the name of the resource group you create in Azure.

  1. When prompted, choose Select a template, then search for and select Azure Functions PowerShell CosmosDB trigger.

  2. When prompted, enter a unique environment name, such as cosmosdbchanges-ps.

    This command pulls the project files from the template repository and initializes the project in the current folder or workspace. In azd, the environment is used to maintain a unique deployment context for your app, and you can define more than one. It's also part of the name of the resource group you create in Azure.

  1. When prompted, choose Select a template, then search for and select Azure Functions TypeScript CosmosDB trigger.

  2. When prompted, enter a unique environment name, such as cosmosdbchanges-ts.

    This command pulls the project files from the template repository and initializes the project in the current folder or workspace. In azd, the environment is used to maintain a unique deployment context for your app, and you can define more than one. It's also part of the name of the resource group you create in Azure.

  1. When prompted, choose Select a template, then search for and select Azure Functions Python with CosmosDB triggers and bindings....

  2. When prompted, enter a unique environment name, such as cosmosdbchanges-py.

    This command pulls the project files from the template repository and initializes the project in the current folder or workspace. In azd, the environment is used to maintain a unique deployment context for your app, and you can define more than one. It's also part of the name of the resource group you create in Azure.

  1. Run this command, depending on your local operating system, to grant configuration scripts the required permissions:

    Run this command with sufficient privileges:

    chmod +x ./infra/scripts/*.sh
    

Before you can run your app locally, you must create the resources in Azure. This project doesn't use local emulation for Azure Cosmos DB.

Create Azure resources

This project is configured to use the azd provision command to create a function app in a Flex Consumption plan, along with other required Azure resources that follows current best practices.

  1. In Visual Studio Code, press F1 to open the command palette, search for and run the command Azure Developer CLI (azd): Sign In with Azure Developer CLI, and then sign in using your Azure account.

  2. Press F1 to open the command palette, search for and run the command Azure Developer CLI (azd): Provision Azure resources (provision) to create the required Azure resources:

  3. When prompted in the Terminal window, provide these required deployment parameters:

    Prompt Description
    Select an Azure Subscription to use Choose the subscription in which you want your resources to be created.
    location deployment parameter Azure region in which to create the resource group that contains the new Azure resources. Only regions that currently support the Flex Consumption plan are shown.
    vnetEnabled deployment parameter While the template supports creating resources inside a virtual network, to simplify deployment and testing, choose False.

    The azd provision command uses your response to these prompts with the Bicep configuration files to create and configure these required Azure resources, following the latest best practices:

    • Flex Consumption plan and function app
    • Azure Cosmos DB account
    • Azure Storage (required) and Application Insights (recommended)
    • Access policies and roles for your account
    • Service-to-service connections using managed identities (instead of stored connection strings)

    Post-provision hooks also generate the local.settings.json file required when running locally. This file also contains the settings required to connect to your Azure Cosmos DB database in Azure.

    Tip

    Should any steps fail during provisioning, you can rerun the azd provision command again after resolving any issues.

    After the command completes successfully, you can run your project code locally and trigger on the Azure Cosmos DB database in Azure.

Run the function locally

Visual Studio Code integrates with Azure Functions Core tools to let you run this project on your local development computer before you publish to your new function app in Azure.

  1. Press F1 and in the command palette search for and run the command Azurite: Start.

  2. To start the function locally, press F5 or the Run and Debug icon in the left-hand side Activity bar. The Terminal panel displays the output from Core Tools. Your app starts in the Terminal panel, and you can see the name of the function that's running locally.

    If you have trouble running on Windows, make sure that the default terminal for Visual Studio Code isn't set to WSL Bash.

  3. With Core Tools still running in Terminal, press F1 and in the command palette search for and run the command NoSQL: Create Item... and select both the document-db database and the documents container.

  4. Replace the contents of the New Item.json file with this JSON data and select Save:

    {
        "id": "doc1", 
        "title": "Sample document", 
        "content": "This is a sample document for testing my Azure Cosmos DB trigger in Azure Functions."
    } 
    

    After you select Save, you see the execution of the function in the terminal and the local document is updated to include metadata added by the service.

  5. When you're done, press Ctrl+C in the terminal window to stop the func.exe host process.

Review the code (optional)

The function is triggered based on the change feed in an Azure Cosmos DB NoSQL database.

These environment variables configure how the trigger monitors the change feed:

  • COSMOS_CONNECTION__accountEndpoint: The Cosmos DB account endpoint
  • COSMOS_DATABASE_NAME: The name of the database to monitor
  • COSMOS_CONTAINER_NAME: The name of the container to monitor

These environment variables are created for you both in Azure (function app settings) and locally (local.settings.json) during the azd provision operation.

The COSMOS_CONNECTION environment variable configures the Cosmos DB account endpoint used by the trigger. This environment variable is created for you both in Azure (function app settings) and locally (local.settings.json) during the azd provision operation. The database and container names are defined in the trigger configuration.

You can review the code that defines the Azure Cosmos DB trigger:

using System;
using System.Collections.Generic;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace Company.Function
{
    public class CosmosTrigger
    {
        private readonly ILogger _logger;

        public CosmosTrigger(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<CosmosTrigger>();
        }

        [Function("cosmos_trigger")]
        public void Run([CosmosDBTrigger(
            databaseName: "%COSMOS_DATABASE_NAME%",
            containerName: "%COSMOS_CONTAINER_NAME%",
            Connection = "COSMOS_CONNECTION",
            LeaseContainerName = "leases",
            CreateLeaseContainerIfNotExists = true)] IReadOnlyList<MyDocument> input)
        {
            if (input != null && input.Count > 0)
            {
                _logger.LogInformation("Documents modified: " + input.Count);
                _logger.LogInformation("First document Id: " + input[0].id);
            }
        }
    }
    public class MyDocument
    {
        /// <summary>
        /// The unique identifier for the document.
        /// </summary>
        public required string id { get; set; }

        /// <summary>
        /// A text field in the document.
        /// </summary>
        public required string Text { get; set; }

        /// <summary>
        /// A numeric field in the document.
        /// </summary>
        public int Number { get; set; }

        /// <summary>
        /// A boolean field in the document.
        /// </summary>
        public bool Boolean { get; set; }
    }
}

You can review the complete template project here.

package com.function;

import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.annotation.CosmosDBTrigger;
import com.microsoft.azure.functions.annotation.FunctionName;

public class CosmosTrigger {

    @FunctionName("cosmos_trigger")
    public void run(
        @CosmosDBTrigger(
            name = "input",
            databaseName = "%COSMOS_DATABASE_NAME%",
            containerName = "%COSMOS_CONTAINER_NAME%",
            connection = "COSMOS_CONNECTION",
            leaseContainerName = "leases",
            createLeaseContainerIfNotExists = true
        ) Object[] items,
        final ExecutionContext context
    ) {
        if (items != null && items.length > 0) {
            context.getLogger().info("Documents modified: " + items.length);
            context.getLogger().info("First document Id: " + items[0].toString());
        }
    }
}

You can review the complete template project here.

const { app } = require('@azure/functions');

app.cosmosDB('cosmos_trigger', {
    connection: 'COSMOS_CONNECTION',
    databaseName: '%COSMOS_DATABASE_NAME%',
    containerName: '%COSMOS_CONTAINER_NAME%',
    leaseContainerName: 'leases',
    createLeaseContainerIfNotExists: true,
    handler: (documents, context) => {
        if (documents && documents.length > 0) {
            context.log(`Documents modified: ${documents.length}`);
            context.log(`First document Id: ${documents[0].id}`);
        }
    }
});

You can review the complete template project here.

import { app, InvocationContext } from "@azure/functions";

export async function cosmos_trigger(documents: unknown[], context: InvocationContext): Promise<void> {
    context.log(`Cosmos DB function processed ${documents.length} documents`);

    if (documents && documents.length > 0) {
        for (const doc of documents) {
            context.log(`First document: ${JSON.stringify(doc)}`);
            if (doc && typeof doc === "object" && "id" in doc) {
                context.log(`First document id: ${(doc as { id?: string }).id}`);
            }
        }
    } else {
        context.log("No documents found.");
    }
}


app.cosmosDB('cosmos_trigger', {
    connection: 'COSMOS_CONNECTION',
    databaseName: 'documents-db',
    containerName: 'documents',
    createLeaseContainerIfNotExists: true,
    handler: cosmos_trigger
});

You can review the complete template project here.

The trigger is defined in this function.json file:

{
  "bindings": [
    {
      "type": "cosmosDBTrigger",
      "name": "InputDocuments",
      "direction": "in",
      "databaseName": "%COSMOS_DATABASE_NAME%",
      "containerName": "%COSMOS_CONTAINER_NAME%",
      "connection": "COSMOS_CONNECTION",
      "leaseContainerName": "leases",
      "createLeaseContainerIfNotExists": true
    }
  ]
}

The following code runs when the trigger executes:

param($InputDocuments, $TriggerMetadata)

if ($InputDocuments -and $InputDocuments.Count -gt 0) {
    Write-Host "Documents modified: $($InputDocuments.Count)"
    Write-Host "First document Id: $($InputDocuments[0].id)"
}

You can review the complete template project here.

import os
import azure.functions as func
import logging

app = func.FunctionApp()


@app.cosmos_db_trigger(
    arg_name="documents",
    container_name=os.environ.get("COSMOS_CONTAINER_NAME"),
    database_name=os.environ.get("COSMOS_DATABASE_NAME"),
    connection="COSMOS_CONNECTION",
    create_lease_container_if_not_exists="true",
)
def cosmos_trigger(documents: func.DocumentList):
    logging.info("Python CosmosDB triggered.")
    logging.info(f"Documents modified: {len(documents)}")
    if documents:
        for doc in documents:
            logging.info(f"First document: {doc.to_json()}")
            logging.info(f"First document id: {doc.get('id')}")
    else:
        logging.info("No documents found.")

You can review the complete template project here.

After you review and verify your function code locally, it's time to publish the project to Azure.

Deploy to Azure

You can run the azd deploy command from Visual Studio Code to deploy the project code to your already provisioned resources in Azure.

  1. Press F1 to open the command palette.

  2. Search for and run the command Azure Developer CLI (azd): Deploy to Azure (deploy).

    The azd deploy command packages and deploys your code to the deployment container. The app is then started and runs in the deployed package.

    After the command completes successfully, your app is running in Azure.

Invoke the function on Azure

  1. In Visual Studio Code, press F1 and in the command palette search for and run the command Azure: Open in portal, select Function app, and choose your new app. Sign in with your Azure account, if necessary.

    This command opens your new function app in the Azure portal.

  2. In the Overview tab on the main page, select your function app name and then the Logs tab.

  3. Use the NoSQL: Create Item command in Visual Studio Code to again add a document to the container as before.

  4. Verify again that the function gets triggered by an update in the monitored container.

Redeploy your code

You can run the azd deploy command as many times as you need to deploy code updates to your function app.

Note

Deployed code files are always overwritten by the latest deployment package.

Your initial responses to azd prompts and any environment variables generated by azd are stored locally in your named environment. Use the azd env get-values command to review all of the variables in your environment that were used when creating Azure resources.

Clean up resources

When you're done working with your function app and related resources, you can use this command to delete the function app and its related resources from Azure and avoid incurring any further costs:

azd down --no-prompt

Note

The --no-prompt option instructs azd to delete your resource group without a confirmation from you.

This command doesn't affect your local code project.