You can implement your Azure Functions using JavaScript. The Azure Functions Runtime leverages Node.js to host and run your functions written in JavaScript.
At the time of writing this book, the runtime uses even-numbered Node.js versions (8.11.1 and 10.14.1 are recommended). You can set the Node.js version using the WEBSITE_NODE_DEFAULT_VERSION app settings.
You can retrieve the Node.js version inside any function by using that app's settings or reading the process.version property.
You can create your first function using the Azure Functions Core Tools in the same way you do for a C# function:
- The first step you must perform is to create the project that hosts your functions:
func init --worker-runtime node
The following screenshot shows the output of the preceding command:
The --worker-runtime parameter defines the worker runtime you want to use.
- In this case, you create the function project using the node as the worker runtime, so you can create your function both in JavaScript or TypeScript:
func new --name TimerFunction --language javascript --template TimerTrigger
The following screenshot shows the output of the preceding command:
At the time of writing this book (which uses version 2.6.1048 of the Azure Functions Tool), the templates available for the function written in JavaScript are as follows:
- Azure Blob storage trigger
- Azure Cosmos DB trigger
- Azure Event Grid trigger
- Azure Event Hub trigger
- HTTP trigger
- IoT Hub (Event Hub)
- Azure Queue storage trigger
- SendGrid
- Azure Service Bus Queue trigger
- Azure Service Bus Topic trigger
- Timer trigger
The project folder may have the following structure:
You have a folder for each function in your project, and you can create a SharedCode folder to contain the Node.js code you want to share in more functions.
The host.json file is used to configure the function app (for example, you can use it to define the runtime version or the logging configuration) in the same way that it is used in function apps written in C#.
For each function that you add to the function app (remember what we said in the first chapter: all the functions in the function app need to be written in JavaScript), you will find a folder with at least two files: index.js and function.json.
The index.js file contains the code that implements the function, and it is the file that the runtime is looking for when it searches for the functions to execute:
module.exports = async function (context, myTimer) {
var timeStamp = new Date().toISOString();
if (myTimer.IsPastDue)
{
context.log('JavaScript is running late!');
}
context.log('JavaScript timer trigger function ran!', timeStamp);
};
In this case, the function is very easy, but you will notice the following:
- JavaScript functions must be exported using the module.exports declaration.
- The function receives a number of arguments depending on the type of function, but every function receives, at least, the context argument. This argument contains the context of the single call and allows you to interact with the runtime (for example, to write a log trace). You can also use the context to interact with the function's binding (instead of using the argument bindings).
If you need to (or want to), you can configure the runtime to search for a different file instead of index.js.
The function.json file contains the triggers and bindings definition for the function, and it looks like the following:
{
"bindings": [
{
"name": "myTimer",
"type": "timerTrigger",
"direction": "in",
"schedule": "0 */5 * * * *"
}
]
}
Your function has one input binding of the timerTrigger type called myTimer with a specific schedule.
Every time you want your function to support other bindings, you must add their definition to this file.
You can define bindings in different ways based on whether their direction is inbound or outbound.
For the inbound bindings (direction === "in"), you can do the following:
- Use the arguments of the function (in the same order you declare in the function.json file). The name property of the JSON must not match the name of the argument in the function signature, but we suggest using the same name to avoid confusion.
- Use the members of the context object. Each binding becomes a property of the context object with the same name set in the JSON:
module.exports = async function (context) {
var timeStamp = new Date().toISOString();
if (context.myTimer.IsPastDue)
{
context.log('JavaScript is running late!');
}
context.log('JavaScript timer trigger function ran!', timeStamp);
};
- Use the objects that are present in the JavaScript argument array. This approach is similar to the use of the signature arguments, but allows you to access the binding dynamically:
module.exports = async function (context) {
var timeStamp = new Date().toISOString();
if (argument[1].IsPastDue)
{
context.log('JavaScript is running late!');
}
context.log('JavaScript timer trigger function ran!', timeStamp);
};
For the outbound bindings (direction === "out"), you can do the following:
- Return an object (both in the case of a single output and for multiple outputs). In the following example, we are returning an HTTP response and writing an item in a queue:
module.exports = async function(context) {
let retMsg = 'Hello, world!';
return {
httpResponse: {
body: retMsg
},
queueOutput: retMsg
};
};
- Use the context object to assign the return values to its binding properties:
module.exports = async function(context) {
let retMsg = 'Hello, world!';
context.bindings.httpResponse = {
body: retMsg
};
context.bindings.queueOutput = retMsg;
};
The dataType property in the JSON file allows you to specify what type of data the binding manages. You can use binary, stream, or string.
Once you have created the function app, you can run it in the same way you run a C# function app:
C:\MasteringServerless\MyFirstNodeFunction>func start
The following screenshot shows the output of the preceding command:
The runtime initializes the host reading the host.json file, then starts it and starts the language worker process to support Node.js and JavaScript, as you can see in the following screenshot:
Lastly, the runtime generates one job for every single function discovered, and your functions are alive.
More information about the language worker and language extensibility will be given in later sections.