Functions Node.js JavaScript Runtime

Functions are blocks of code that run on demand without the need to manage any infrastructure. Develop on your local machine, test your code from the command line (using doctl), then deploy to a production namespace or App Platform — no servers required.


Functions supports Node.js 14 (nodejs:14) and Node.js 18 (nodejs:18). Specify the desired runtime and version with the runtime key in your project.yml file, or by using the Runtime dropdown when creating a function through the control panel.

The Functions Node.js runtime passes two parameters to your handler function, and expects either no return value or a properly formatted response object.

An Example JavaScript Function

Here is a JavaScript function that responds with “Hello world”:

export function main() {
  return { body: 'Hello world' }
}

This main() function takes no parameters, and returns a response object with a body key:

{ body: 'Hello world' }

The function is exported using the export keyword. By default, the runtime expects a function called main to be exported. This is the entry point or the handler. This handler function is the only function where the runtime will pass in data and receive responses.

Note

If your function uses any asynchronous, promise-based code, your handler function must return a promise to the runtime.

To do this, either return a promise explicitly, or use standard async/await syntax. See Asynchronous Functions for more details and examples.

You can set a different function name as the handler using the main key in project.yml.

Parameters

The handler function is always passed two parameters, event and context.

Note
Information about function parameters that is not language-specific can be found in the Parameters and Responses reference documentation.

The first parameter, event, is the input event that initiated the function. When this is an HTTP event it’s called a web event.

The second parameter, context, is data about the function’s execution environment such as the timeout deadline and memory allocations.

Both parameters are optional and you may ignore them if your function doesn’t require the information they provide. The parameter list for your handler function should look like one of the following:

  • (event, context): Accesses both parameters.
  • (event): Accesses only the event parameter.
  • (_, context): Accesses only the context parameter (_ is a common convention for unused parameters, though you may need to mark it as unused to satisfy your linter or compiler).
  • (): Accesses neither.

Here is a function that uses both parameters to return a personalized Hello world and the function’s version number:

export function main(event, context) {
  const name = event.name || 'stranger'
  const version = context.functionVersion

  return {
    body: `Hello ${name}! This is version ${version}.`
  }
}

You can call the function by pasting its URL in your browser and adding a name field in a query string at the end.

Note

You can get the URL for your function from the control panel interface, or by running the following command on the command line:

doctl serverless function get <function-name> --url
https://<your-function-url>?name=Sammy

Or use curl to send the input as form data in the body of the request:

curl -d 'name=Sammy' <your-function-url>

Either way, the response body is returned:

Hello Sammy! This is function version 0.0.2.

Event Parameter

The event parameter is an object. It is structured like the following example:

{
  "http": {
    "headers": {
      "accept": "*/*",
      "accept-encoding": "gzip",
      "user-agent": "curl/7.85.0",
      "x-forwarded-for": "203.0.113.11",
      "x-forwarded-proto": "https",
      "x-request-id": "5df6f6b0d00b58217439c8376fcae23a"
    },
    "method": "POST",
    "path": ""
  },
  "shark": "hammerhead"
}

This example event has had a shark: hammerhead input passed to it. This has been parsed and added as a top-level key to the object.

More details on the information contained in the event parameter is available under the Event Parameter section of the Parameters and Responses reference documentation.

Context Parameter

The context parameter is an object. It is structured like the following example:

{
  "activationId": "5f56b7e9fbd84b2f96b7e9fbd83b2f2e",
  "apiHost": "https://faas-nyc1-2ef2e6cc.doserverless.co",
  "apiKey": "",
  "deadline": 1675959023728,
  "functionName": "/fn-52ad03a2-8660-4f7c-55a5-81aa8bbe73b1/example",
  "functionVersion": "0.0.10",
  "namespace": "fn-52ad03a2-8660-4f7c-55a5-81aa8bbe73b1",
  "requestId": "452164dfeced0a7ad91ee675609024e7"
}

Additionally, it has one method, getRemainingTimeInMillis(), which returns the number of milliseconds remaining before the function times out.

More details on the information contained in the context parameter is available under the Context Parameter section of the Parameters and Responses reference documentation.

Returns

To send a response, your function must return a properly formatted response object. If your function does not need to send a response, you may return an empty object, nothing at all, or omit the return statement entirely.

Here is a response object that returns a string as the response body:

{ body: 'Hello world' }

If the body is a JavaScript object or list, it is automatically serialized as JSON and returned with a Content-Type: application/json header. This function returns the event and context parameters as JSON:

export function main(event, context) {
  return {
    body: {event, context}
  }
}

You may also specify the response’s status code and headers. The status code can be a number or a string:

export function main() {
  return  {
    body: 'Hello world',
    statusCode: 200,
    headers: {
      'Content-Type': 'text/plain'
    }
  }
}

More details on the response object can be found in the Returns section of the Parameters and Responses reference documentation.

Example Functions

Return JSON

If the body is a JavaScript object or list, it is automatically serialized as JSON and returned with a Content-Type: application/json header.

export function main() {
  return {
    body: [{type: 'hammerhead'}, {type: 'mako'}]
  }
}

Return an Image

To return an image or other media type, set the correct Content-Type header and return a base64-encoded body:

export function main() {
  // example 1x1 GIF
  const gif = 'R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
  return {
    headers: { 'Content-Type': 'image/gif' },
    statusCode: 200,
    body: gif
  }
}

Return an HTTP Redirect

A 302 status code and location header redirects an HTTP client to a new URL:

export function main() {
  return {
    headers: { location: 'https://example.com' },
    statusCode: 302
  }
}

Set cookies with the Set-Cookie header, and use the Content-Type: 'text/html' header to return HTML content:

export function main() {
  return {
    headers: {
      'Set-Cookie': 'UserID=Sammy; Max-Age=3600; Version=',
      'Content-Type': 'text/html'
    },
    statusCode: 200,
    body: '<html><body><h3>hello</h3></body></html>'
  }
}

More Example Functions

Some more complex example Node.js functions are available on GitHub:

github.com

Asynchronous Functions

If your handler function returns a promise, the runtime waits for it to resolve before responding and/or exiting. The async function main() handler below immediately returns a promise (as all async functions do), then waits for the sleep() function to resolve before returning the response body.

export async function main(event) {
  const time = event.time || 1000
  await sleep(time)
  return {body: `We slept for ${time} milliseconds`}
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

Call this function with a time parameter to adjust the sleep period, or use the default of 1000 milliseconds. The default timeout for functions is 3000 milliseconds, so either adjust your timeout or stick to time values under 3000.

You could write a similar function without using async/await:

export function main(event) {
  const time = event.time || 1000
  return new Promise((resolve) => {
    setTimeout(
     () => resolve({body: `We slept for ${time} milliseconds`}),
     time
    )
  })
}

This returns a promise which calls setTimeout to run the resolve statement after the specified amount of time.

Log to the Console

The full Node.js console object is available. See the Node.js console documentation for a list of the methods available.

Any text output to stdout and stderr from console.log() (or other console methods) are logged and can be accessed through the doctl command line tool. Use doctl serverless activations logs --follow to follow logs for all functions in the current namespace, or specify a single function with --function.

See the doctl serverless activations logs reference for more information or add the --help flag to the command for help output.

Use Modules and Dependencies

Functions that are made up of multiple requireed or imported files – whether npm modules or your own code – need to be built and deployed from the command line using doctl.

The only requirement for doctl to detect, build, and deploy your Node.js function is a properly formatted package.json file. Here is a minimal package.json:

{
  "main": "index.js",
  "dependencies" : {
    "slugify" : "1.6.5"
  }
}

The main key must point to the JavaScript file containing your handler function. The dependencies key lists all npm module dependencies.

For this example, index.js is the file containing our handler function. It looks like this:

const slugify = require('slugify')

function main(event) {
  const input = event.input || 'Hello world';
  return { body: slugify(input) }
}

exports.main = main

This uses the CommonJS format ( require and exports). To use ES modules (import and export), either add "type": "module" to your package.json file or change your JavaScript file extension to .mjs and update the main value in your package.json to the new filename.

Make sure the above files are in a properly formatted project directory then deploy with doctl serverless deploy <your-project-directory>.

Note
You can scaffold a new project directory and example JavaScript function using the command doctl serverless init --language js <project-name>.

The doctl serverless deploy command detects the presence of package.json and runs npm install --production automatically. If you don’t have npm installed locally, the entire build can be performed remotely by adding --remote-build to the command.

If you require different build steps create a build.sh file in the function directory. Read the Build Process reference for more details on the build process and build scripts.

Handle Native Dependencies

Node.js modules which compile and use native dependencies can be deployed to Functions using the --remote-build feature. Because native dependencies need to be compiled for the correct platform architecture used by Functions, local builds are not supported for native dependencies.

The full command to deploy with remote builds is:

doctl serverless deploy <your-project-path> --remote-build

You may also add the flag to the watch command:

doctl serverless watch <your-project-path> --remote-build

Use Bundlers to Package Source Files

JavaScript bundlers can transform a complex set of source files into a single compressed JavaScript file. This can lead to faster deployments and shorter cold starts, and may allow you to deploy larger applications where the un-optimized source files are larger than the function size limit.

Here are the instructions for how to use three popular module bundlers with the Node.js runtime. The previous slugify example is used as the source file for bundling along with the external library.

Rollup.js

Rollup is a popular module bundler with tree-shaking and many plugins. To use it, first re-write your index.js file to use ES Modules, rather than CommonJS module:

import slugify from 'slugify'

function handler(event) {
  const input = event.input || 'Hello world';
  return { body: slugify(input) }
}

export const main = handler
Note
Make sure you export the function using the const main = pattern. Using export {handler as main} does not work due to tree-shaking. See this blog post for details on why this is necessary.

Add "type": "module" to your package.json file.

Create a Rollup configuration file in rollup.config.js:

import commonjs from 'rollup-plugin-commonjs'
import resolve from 'rollup-plugin-node-resolve'

export default {
  input: 'index.js',
  output: {
    file: 'bundle.js',
    format: 'cjs'
  },
  plugins: [
    resolve(),
    commonjs()
  ]
}

Install the Rollup package and plugins as devDependencies using npm:

npm install rollup rollup-plugin-commonjs rollup-plugin-node-resolve --save-dev

Run Rollup now to test it:

npx rollup --config

Rollup outputs a created bundle.js in 78ms message and exit. Now add Rollup as a build script in your package.json:

  "scripts": {
    "build": "rollup --config"
  },

The Functions platform detects this build script automatically and runs npm install to pull in both dependencies and devDependencies, then runs npm run build to do the bundling.

Add a .include file to the function directory with the following line:

bundle.js

Functions only deploys the bundle.js file now. If you need to include other assets in the built function, add their filenames on subsequent lines in his file.

Deploy the function using doctl serverless deploy <your-project-path>. You may use the --remote-build flag to run the build remotely.

Webpack

Webpack is a powerful and modular JavaScript bundler. To use it, first change your index.js to export the handler function as a global reference:

const slugify = require('slugify')

function handler(event) {
  const input = event.input || 'Hello world';
  return { body: slugify(input) }
}

global.main = handler

This allows the bundle source to “break out” of the closures Webpack uses when defining the modules.

Next, create the Webpack configuration file in webpack.config.js:

module.exports = {
  entry: './index.js',
  target: 'node',
  output: {
    filename: 'bundle.js'
  }
}

This configures index.js as the entrypoint, and outputs the bundle at dist/bundle.js.

Install Webpack into devDependencies using npm:

npm install webpack-cli --save-dev

Do a test run of Webpack:

npx webpack --config webpack.config.js

The output is similar to the following:

webpack 5.75.0 compiled with 1 warning in 242 ms

The warning can be ignored and is due to our example configuration being as minimal as possible. Now add Webpack as a build script in your package.json:

  "scripts": {
    "build": "webpack --config webpack.config.js"
  },

The Functions platform detects this build script automatically and runs npm install to pull in both dependencies and devDependencies, then runs npm run build to do the bundling.

Add a .include file to the function directory with the following line:

dist/bundle.js

Functions only deploys the bundle.js file now. If you need to include other assets in the built function, add their filenames on subsequent lines in his file.

Deploy the function using doctl serverless deploy <your-project-path>. You may use the --remote-build flag to run the build remotely.

Parcel

Parcel aims to be a zero-configuration build tool for JavaScript and TypeScript libraries and websites. The following instructions install and configure Parcel 2 to bundle your code into bundle.js.

Change index.js to export the handler using as a global reference:

const slugify = require('slugify')

function handler(event) {
  const input = event.input || 'Hello world';
  return { body: slugify(input) }
}

global.main = handler

Note global.main = handler. This allows the bundle source to “break out” of the closures Parcel uses when defining the modules.

Install Parcel into devDependencies using npm:

npm install parcel --save-dev

Update package.json so that the main key points to the build target bundle.js, and add a new source key that points to your existing index.js:

  "main": "bundle.js",
  "source": "index.js",

Add a build script to package.json to run Parcel:

  "scripts": {
    "build": "parcel build"
  },

Finally, add a targets key to package.json, where we configure Parcel to bundle all modules up into a single file:

  "targets": {
    "main": {
      "includeNodeModules": true
    }
  },

Try out Parcel now:

npm run build

It prints out a successful build result:

✨ Built in 1.02s

bundle.js    9.59 KB    45ms

Add a .include file to the function directory with the following line:

bundle.js

Functions only deploys the bundle.js file now. If you need to include other assets in the built function, add their filenames on subsequent lines in his file.

Deploy the function using doctl serverless deploy <your-project-path>. You may use the --remote-build flag to run the build remotely.

Include Files in Built Function

To include arbitrary files with your deployed function (for example, config files and templates), place the files in your function directory. By default, all files in the function directory will be included in the deployed function. This can be customized using .ignore and .include files. See the Build Process reference for details.

Here is an example function that reads text content from a file:

Directory structure:

.
├── packages
│   └── <package-name>
│       └── <function-name>
│           ├── index.mjs
│           └── to_be_included.txt
└── project.yml
    
        
            
import fs from 'fs/promises';

export async function main() {
  const fileContents = await fs.readFile('to_be_included.txt');
  return {
    body: `File contents: "${fileContents}"`,
  };
}

        
    
    
        
            
Hello, World!

        
    

When invoked, the response is:

File contents: "Hello, World!"