Azure Functions JavaScript developer guide

This guide contains information about the intricacies of writing Azure Functions with JavaScript.

A JavaScript function is an exported function that executes when triggered (triggers are configured in function.json). The first argument passed to every function is a context object, which is used for receiving and sending binding data, logging, and communicating with the runtime.

This article assumes that you have already read the Azure Functions developer reference. Complete the Functions quickstart to create your first function, using Visual Studio Code or in the portal.

This article also supports TypeScript app development.

Folder structure

The required folder structure for a JavaScript project looks like the following. This default can be changed. For more information, see the scriptFile section below.

FunctionsProject
 | - MyFirstFunction
 | | - index.js
 | | - function.json
 | - MySecondFunction
 | | - index.js
 | | - function.json
 | - SharedCode
 | | - myFirstHelperFunction.js
 | | - mySecondHelperFunction.js
 | - node_modules
 | - host.json
 | - package.json
 | - extensions.csproj

At the root of the project, there's a shared host.json file that can be used to configure the function app. Each function has a folder with its own code file (.js) and binding configuration file (function.json). The name of function.json's parent directory is always the name of your function.

The binding extensions required in version 2.x of the Functions runtime are defined in the extensions.csproj file, with the actual library files in the bin folder. When developing locally, you must register binding extensions. When developing functions in the Azure portal, this registration is done for you.

Exporting a function

JavaScript functions must be exported via module.exports (or exports). Your exported function should be a JavaScript function that executes when triggered.

By default, the Functions runtime looks for your function in index.js, where index.js shares the same parent directory as its corresponding function.json. In the default case, your exported function should be the only export from its file or the export named run or index. To configure the file location and export name of your function, read about configuring your function's entry point below.

Your exported function is passed a number of arguments on execution. The first argument it takes is always a context object. If your function is synchronous (doesn't return a Promise), you must pass the context object, as calling context.done is required for correct use.

// You should include context, other arguments are optional
module.exports = function(context, myTrigger, myInput, myOtherInput) {
    // function logic goes here :)
    context.done();
};

Exporting an async function

When using the async function declaration or plain JavaScript Promises in version 2.x of the Functions runtime, you do not need to explicitly call the context.done callback to signal that your function has completed. Your function completes when the exported async function/Promise completes. For functions targeting the version 1.x runtime, you must still call context.done when your code is done executing.

The following example is a simple function that logs that it was triggered and immediately completes execution.

module.exports = async function (context) {
    context.log('JavaScript trigger function processed a request.');
};

When exporting an async function, you can also configure an output binding to take the return value. This is recommended if you only have one output binding.

To assign an output using return, change the name property to $return in function.json.

{
  "type": "http",
  "direction": "out",
  "name": "$return"
}

In this case, your function should look like the following example:

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');
    // You can call and await an async method here
    return {
        body: "Hello, world!"
    };
}

Bindings

In JavaScript, bindings are configured and defined in a function's function.json. Functions interact with bindings a number of ways.

Inputs

Input are divided into two categories in Azure Functions: one is the trigger input and the other is the additional input. Trigger and other input bindings (bindings of direction === "in") can be read by a function in three ways:

  • [Recommended] As parameters passed to your function. They are passed to the function in the same order that they are defined in function.json. The name property defined in function.json does not need to match the name of your parameter, although it should.

    module.exports = async function(context, myTrigger, myInput, myOtherInput) { ... };
    
  • As members of the context.bindings object. Each member is named by the name property defined in function.json.

    module.exports = async function(context) { 
        context.log("This is myTrigger: " + context.bindings.myTrigger);
        context.log("This is myInput: " + context.bindings.myInput);
        context.log("This is myOtherInput: " + context.bindings.myOtherInput);
    };
    
  • As inputs using the JavaScript arguments object. This is essentially the same as passing inputs as parameters, but allows you to dynamically handle inputs.

    module.exports = async function(context) { 
        context.log("This is myTrigger: " + arguments[1]);
        context.log("This is myInput: " + arguments[2]);
        context.log("This is myOtherInput: " + arguments[3]);
    };
    

Outputs

Outputs (bindings of direction === "out") can be written to by a function in a number of ways. In all cases, the name property of the binding as defined in function.json corresponds to the name of the object member written to in your function.

You can assign data to output bindings in one of the following ways (don't combine these methods):

  • [Recommended for multiple outputs] Returning an object. If you are using an async/Promise returning function, you can return an object with assigned output data. In the example below, the output bindings are named "httpResponse" and "queueOutput" in function.json.

    module.exports = async function(context) {
        let retMsg = 'Hello, world!';
        return {
            httpResponse: {
                body: retMsg
            },
            queueOutput: retMsg
        };
    };
    

    If you are using a synchronous function, you can return this object using context.done (see example).

  • [Recommended for single output] Returning a value directly and using the $return binding name. This only works for async/Promise returning functions. See example in exporting an async function.

  • Assigning values to context.bindings You can assign values directly to context.bindings.

    module.exports = async function(context) {
        let retMsg = 'Hello, world!';
        context.bindings.httpResponse = {
            body: retMsg
        };
        context.bindings.queueOutput = retMsg;
        return;
    };
    

Bindings data type

To define the data type for an input binding, use the dataType property in the binding definition. For example, to read the content of an HTTP request in binary format, use the type binary:

{
    "type": "httpTrigger",
    "name": "req",
    "direction": "in",
    "dataType": "binary"
}

Options for dataType are: binary, stream, and string.

context object

The runtime uses a context object to pass data to and from your function and to let you communicate with the runtime. The context object can be used for reading and setting data from bindings, writing logs, and using the context.done callback when your exported function is synchronous.

The context object is always the first parameter to a function. It should be included because it has important methods such as context.done and context.log. You can name the object whatever you would like (for example, ctx or c).

// You must include a context, but other arguments are optional
module.exports = function(ctx) {
    // function logic goes here :)
    ctx.done();
};

context.bindings property

context.bindings

Returns a named object that is used to read or assign binding data. Input and trigger binding data can be accessed by reading properties on context.bindings. Output binding data can be assigned by adding data to context.bindings

For example, the following binding definitions in your function.json let you access the contents of a queue from context.bindings.myInput and assign outputs to a queue using context.bindings.myOutput.

{
    "type":"queue",
    "direction":"in",
    "name":"myInput"
    ...
},
{
    "type":"queue",
    "direction":"out",
    "name":"myOutput"
    ...
}
// myInput contains the input data, which may have properties such as "name"
var author = context.bindings.myInput.name;
// Similarly, you can set your output data
context.bindings.myOutput = { 
        some_text: 'hello world', 
        a_number: 1 };

You can choose to define output binding data using the context.done method instead of the context.binding object (see below).

context.bindingData property

context.bindingData

Returns a named object that contains trigger metadata and function invocation data (invocationId, sys.methodName, sys.utcNow, sys.randGuid). For an example of trigger metadata, see this event hubs example.

context.done method

context.done([err],[propertyBag])

Lets the runtime know that your code has completed. When your function uses the async function declaration, you do not need to use context.done(). The context.done callback is implicitly called. Async functions are available in Node 8 or a later version, which requires version 2.x of the Functions runtime.

If your function is not an async function, you must call context.done to inform the runtime that your function is complete. The execution times out if it is missing.

The context.done method allows you to pass back both a user-defined error to the runtime and a JSON object containing output binding data. Properties passed to context.done overwrite anything set on the context.bindings object.

// Even though we set myOutput to have:
//  -> text: 'hello world', number: 123
context.bindings.myOutput = { text: 'hello world', number: 123 };
// If we pass an object to the done function...
context.done(null, { myOutput: { text: 'hello there, world', noNumber: true }});
// the done method overwrites the myOutput binding to be: 
//  -> text: 'hello there, world', noNumber: true

context.log method

context.log(message)

Allows you to write to the streaming function logs at the default trace level. On context.log, additional logging methods are available that let you write function logs at other trace levels:

Method Description
error(message) Writes to error level logging, or lower.
warn(message) Writes to warning level logging, or lower.
info(message) Writes to info level logging, or lower.
verbose(message) Writes to verbose level logging.

The following example writes a log at the warning trace level:

context.log.warn("Something has happened."); 

You can configure the trace-level threshold for logging in the host.json file. For more information on writing logs, see writing trace outputs below.

Read monitoring Azure Functions to learn more about viewing and querying function logs.

Writing trace output to the console

In Functions, you use the context.log methods to write trace output to the console. In Functions v2.x, trace outputs using console.log are captured at the Function App level. This means that outputs from console.log are not tied to a specific function invocation and aren't displayed in a specific function's logs. They do, however, propagate to Application Insights. In Functions v1.x, you cannot use console.log to write to the console.

When you call context.log(), your message is written to the console at the default trace level, which is the info trace level. The following code writes to the console at the info trace level:

context.log({hello: 'world'});  

This code is equivalent to the code above:

context.log.info({hello: 'world'});  

This code writes to the console at the error level:

context.log.error("An error has occurred.");  

Because error is the highest trace level, this trace is written to the output at all trace levels as long as logging is enabled.

All context.log methods support the same parameter format that's supported by the Node.js util.format method. Consider the following code, which writes function logs by using the default trace level:

context.log('Node.js HTTP trigger function processed a request. RequestUri=' + req.originalUrl);
context.log('Request Headers = ' + JSON.stringify(req.headers));

You can also write the same code in the following format:

context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);
context.log('Request Headers = ', JSON.stringify(req.headers));

Configure the trace level for console logging

Functions 1.x lets you define the threshold trace level for writing to the console, which makes it easy to control the way traces are written to the console from your function. To set the threshold for all traces written to the console, use the tracing.consoleLevel property in the host.json file. This setting applies to all functions in your function app. The following example sets the trace threshold to enable verbose logging:

{
    "tracing": {
        "consoleLevel": "verbose"
    }
}  

Values of consoleLevel correspond to the names of the context.log methods. To disable all trace logging to the console, set consoleLevel to off. For more information, see host.json reference.

HTTP triggers and bindings

HTTP and webhook triggers and HTTP output bindings use request and response objects to represent the HTTP messaging.

Request object

The context.req (request) object has the following properties:

Property Description
body An object that contains the body of the request.
headers An object that contains the request headers.
method The HTTP method of the request.
originalUrl The URL of the request.
params An object that contains the routing parameters of the request.
query An object that contains the query parameters.
rawBody The body of the message as a string.

Response object

The context.res (response) object has the following properties:

Property Description
body An object that contains the body of the response.
headers An object that contains the response headers.
isRaw Indicates that formatting is skipped for the response.
status The HTTP status code of the response.

Accessing the request and response

When you work with HTTP triggers, you can access the HTTP request and response objects in a number of ways:

  • From req and res properties on the context object. In this way, you can use the conventional pattern to access HTTP data from the context object, instead of having to use the full context.bindings.name pattern. The following example shows how to access the req and res objects on the context:

    // You can access your http request off the context ...
    if(context.req.body.emoji === ':pizza:') context.log('Yay!');
    // and also set your http response
    context.res = { status: 202, body: 'You successfully ordered more coffee!' }; 
    
  • From the named input and output bindings. In this way, the HTTP trigger and bindings work the same as any other binding. The following example sets the response object by using a named response binding:

    {
        "type": "http",
        "direction": "out",
        "name": "response"
    }
    
    context.bindings.response = { status: 201, body: "Insert succeeded." };
    
  • [Response only] By calling context.res.send(body?: any). An HTTP response is created with input body as the response body. context.done() is implicitly called.

  • [Response only] By calling context.done(). A special type of HTTP binding returns the response that is passed to the context.done() method. The following HTTP output binding defines a $return output parameter:

    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
    
     // Define a valid response object.
    res = { status: 201, body: "Insert succeeded." };
    context.done(null, res);   
    

Node version

The following table shows the Node.js version used by each major version of the Functions runtime:

Functions version Node.js version
1.x 6.11.2 (locked by the runtime)
2.x Active LTS and Maintenance LTS Node.js versions (~10 recommended). Target the version in Azure by setting the WEBSITE_NODE_DEFAULT_VERSION app setting to ~10.

You can see the current version that the runtime is using by checking the above app setting or by printing process.version from any function.

Dependency management

In order to use community libraries in your JavaScript code, as is shown in the below example, you need to ensure that all dependencies are installed on your Function App in Azure.

// Import the underscore.js library
var _ = require('underscore');
var version = process.version; // version === 'v6.5.0'

module.exports = function(context) {
    // Using our imported underscore.js library
    var matched_names = _
        .where(context.bindings.myInput.names, {first: 'Carla'});

Note

You should define a package.json file at the root of your Function App. Defining the file lets all functions in the app share the same cached packages, which gives the best performance. If a version conflict arises, you can resolve it by adding a package.json file in the folder of a specific function.

When deploying Function Apps from source control, any package.json file present in your repo, will trigger an npm install in its folder during deployment. But when deploying via the Portal or CLI, you will have to manually install the packages.

There are two ways to install packages on your Function App:

Deploying with Dependencies

  1. Install all requisite packages locally by running npm install.

  2. Deploy your code, and ensure that the node_modules folder is included in the deployment.

Using Kudu

  1. Go to https://<function_app_name>.scm.azurewebsites.net.

  2. Click Debug Console > CMD.

  3. Go to D:\home\site\wwwroot, and then drag your package.json file to the wwwroot folder at the top half of the page.
    You can upload files to your function app in other ways also. For more information, see How to update function app files.

  4. After the package.json file is uploaded, run the npm install command in the Kudu remote execution console.
    This action downloads the packages indicated in the package.json file and restarts the function app.

Environment variables

In Functions, app settings, such as service connection strings, are exposed as environment variables during execution. You can access these settings using process.env, as shown here in the second and third calls to context.log() where we log the AzureWebJobsStorage and WEBSITE_SITE_NAME environment variables:

module.exports = async function (context, myTimer) {
    var timeStamp = new Date().toISOString();

    context.log('Node.js timer trigger function ran!', timeStamp);
    context.log("AzureWebJobsStorage: " + process.env["AzureWebJobsStorage"]);
    context.log("WEBSITE_SITE_NAME: " + process.env["WEBSITE_SITE_NAME"]);
};

There are several ways that you can add, update, and delete function app settings:

When running locally, app settings are read from the local.settings.json project file.

Configure function entry point

The function.json properties scriptFile and entryPoint can be used to configure the location and name of your exported function. These properties can be important when your JavaScript is transpiled.

Using scriptFile

By default, a JavaScript function is executed from index.js, a file that shares the same parent directory as its corresponding function.json.

scriptFile can be used to get a folder structure that looks like the following example:

FunctionApp
 | - host.json
 | - myNodeFunction
 | | - function.json
 | - lib
 | | - sayHello.js
 | - node_modules
 | | - ... packages ...
 | - package.json

The function.json for myNodeFunction should include a scriptFile property pointing to the file with the exported function to run.

{
  "scriptFile": "../lib/sayHello.js",
  "bindings": [
    ...
  ]
}

Using entryPoint

In scriptFile (or index.js), a function must be exported using module.exports in order to be found and run. By default, the function that executes when triggered is the only export from that file, the export named run, or the export named index.

This can be configured using entryPoint in function.json, as in the following example:

{
  "entryPoint": "logFoo",
  "bindings": [
    ...
  ]
}

In Functions v2.x, which supports the this parameter in user functions, the function code could then be as in the following example:

class MyObj {
    constructor() {
        this.foo = 1;
    };

    logFoo(context) { 
        context.log("Foo is " + this.foo); 
        context.done(); 
    }
}

const myObj = new MyObj();
module.exports = myObj;

In this example, it is important to note that although an object is being exported, there are no guarantees for preserving state between executions.

Local Debugging

When started with the --inspect parameter, a Node.js process listens for a debugging client on the specified port. In Azure Functions 2.x, you can specify arguments to pass into the Node.js process that runs your code by adding the environment variable or App Setting languageWorkers:node:arguments = <args>.

To debug locally, add "languageWorkers:node:arguments": "--inspect=5858" under Values in your local.settings.json file and attach a debugger to port 5858.

When debugging using VS Code, the --inspect parameter is automatically added using the port value in the project's launch.json file.

In version 1.x, setting languageWorkers:node:arguments will not work. The debug port can be selected with the --nodeDebugPort parameter on Azure Functions Core Tools.

TypeScript

When you target version 2.x of the Functions runtime, both Azure Functions for Visual Studio Code and the Azure Functions Core Tools let you create function apps using a template that support TypeScript function app projects. The template generates package.json and tsconfig.json project files that make it easier to transpile, run, and publish JavaScript functions from TypeScript code with these tools.

A generated .funcignore file is used to indicate which files are excluded when a project is published to Azure.

TypeScript files (.ts) are transpiled into JavaScript files (.js) in the dist output directory. TypeScript templates use the scriptFile parameter in function.json to indicate the location of the corresponding .js file in the dist folder. The output location is set by the template by using outDir parameter in the tsconfig.json file. If you change this setting or the name of the folder, the runtime is not able to find the code to run.

Note

Experimental support for TypeScript exists version 1.x of the Functions runtime. The experimental version transpiles TypeScript files into JavaScript files when the function is invoked. In version 2.x, this experimental support has been superseded by the tool-driven method that does transpilation before the host is initialized and during the deployment process.

The way that you locally develop and deploy from a TypeScript project depends on your development tool.

Visual Studio Code

The Azure Functions for Visual Studio Code extension lets you develop your functions using TypeScript. The Core Tools is a requirement of the Azure Functions extension.

To create a TypeScript function app in Visual Studio Code, choose TypeScript as your language when you create a function app.

When you press F5 to run the app locally, transpilation is done before the host (func.exe) is initialized.

When you deploy your function app to Azure using the Deploy to function app... button, the Azure Functions extension first generates a production-ready build of JavaScript files from the TypeScript source files.

Azure Functions Core Tools

There are several ways in which a TypeScript project differs from a JavaScript project when using the Core Tools.

Create project

To create a TypeScript function app project using Core Tools, you must specify the TypeScript language option when you create your function app. You can do this in one of the following ways:

  • Run the func init command, select node as your language stack, and then select typescript.

  • Run the func init --worker-runtime typescript command.

Run local

To run your function app code locally using Core Tools, use the following commands instead of func host start:

npm install
npm start

The npm start command is equivalent to the following commands:

  • npm run build
  • func extensions install
  • tsc
  • func start

Publish to Azure

Before you use the func azure functionapp publish command to deploy to Azure, you create a production-ready build of JavaScript files from the TypeScript source files.

The following commands prepare and publish your TypeScript project using Core Tools:

npm run build:production 
func azure functionapp publish <APP_NAME>

In this command, replace <APP_NAME> with the name of your function app.

Considerations for JavaScript functions

When you work with JavaScript functions, be aware of the considerations in the following sections.

Choose single-vCPU App Service plans

When you create a function app that uses the App Service plan, we recommend that you select a single-vCPU plan rather than a plan with multiple vCPUs. Today, Functions runs JavaScript functions more efficiently on single-vCPU VMs, and using larger VMs does not produce the expected performance improvements. When necessary, you can manually scale out by adding more single-vCPU VM instances, or you can enable autoscale. For more information, see Scale instance count manually or automatically.

Cold Start

When developing Azure Functions in the serverless hosting model, cold starts are a reality. Cold start refers to the fact that when your function app starts for the first time after a period of inactivity, it takes longer to start up. For JavaScript functions with large dependency trees in particular, cold start can be significant. To speed up the cold start process, run your functions as a package file when possible. Many deployment methods use the run from package model by default, but if you're experiencing large cold starts and are not running this way, this change can offer a significant improvement.

Connection Limits

When you use a service-specific client in an Azure Functions application, don't create a new client with every function invocation. Instead, create a single, static client in the global scope. For more information, see managing connections in Azure Functions.

Use async and await

When writing Azure Functions in JavaScript, you should write code using the async and await keywords. Writing code using async and await instead of callbacks or .then and .catch with Promises helps avoid two common problems:

  • Throwing uncaught exceptions that crash the Node.js process, potentially affecting the execution of other functions.
  • Unexpected behavior, such as missing logs from context.log, caused by asynchronous calls that are not properly awaited.

In the example below, the asynchronous method fs.readFile is invoked with an error-first callback function as its second parameter. This code causes both of the issues mentioned above. An exception that is not explicitly caught in the correct scope crashed the entire process (issue #1). Calling context.done() outside of the scope of the callback function means that the function invocation may end before the file is read (issue #2). In this example, calling context.done() too early results in missing log entries starting with Data from file:.

// NOT RECOMMENDED PATTERN
const fs = require('fs');

module.exports = function (context) {
    fs.readFile('./hello.txt', (err, data) => {
        if (err) {
            context.log.error('ERROR', err);
            // BUG #1: This will result in an uncaught exception that crashes the entire process
            throw err;
        }
        context.log(`Data from file: ${data}`);
        // context.done() should be called here
    });
    // BUG #2: Data is not guaranteed to be read before the Azure Function's invocation ends
    context.done();
}

Using the async and await keywords helps avoid both of these errors. You should use the Node.js utility function util.promisify to turn error-first callback-style functions into awaitable functions.

In the example below, any unhandled exceptions thrown during the function execution only fail the individual invocation that raised an exception. The await keyword means that steps following readFileAsync only execute after readFile is complete. With async and await, you also don't need to call the context.done() callback.

// Recommended pattern
const fs = require('fs');
const util = require('util');
const readFileAsync = util.promisify(fs.readFile);

module.exports = async function (context) {
    let data;
    try {
        data = await readFileAsync('./hello.txt');
    } catch (err) {
        context.log.error('ERROR', err);
        // This rethrown exception will be handled by the Functions Runtime and will only fail the individual invocation
        throw err;
    }
    context.log(`Data from file: ${data}`);
}

Next steps

For more information, see the following resources: