How to Create Flexible JavaScript APIs with Functional Options
Tags: javascript, programming, node, webdev
Published: 2021-01-04 12:22:37

---
title: How to Create Flexible JavaScript APIs with Functional Options
canonical_url: https://primalskill.blog/how-to-create-flexible-javascript-apis-with-functional-options
published: true
description: 
tags: javascript, programming, nodejs, webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/vemw00r8qfnxq25y5uf0.png
---

-------------

*This article was originally published on [https://primalskill.blog/how-to-create-flexible-javascript-apis-with-functional-options](https://primalskill.blog/how-to-create-flexible-javascript-apis-with-functional-options)*

-------------


*The methods presented in this article were popularized by Dave Cheney, Rob Pike, and Márk Sági-Kazár. This article presents how to adapt these methods to JavaScript.*

Functional Options is a term used in the Go developer community and was created to explicitly describe and set an API's configuration options.

Go is a statically typed programming language, while pure JavaScript is not, therefore not every functional options method can be converted to JavaScript, nonetheless, it still offers a good way of defining an application API configurations.

## Traditional way of passing arguments

Let's look at the "traditional" way of setting up default configuration options for a method. Say we develop a conference meet application and we have the following function for creating a new meet.

```javascript
function CreateMeet(name, startDateTime) {
   console.log(name, startDateTime)
}
```

We initialize the function above like so.

```javascript
CreateMeet('Meeting', new Date())
```

From a developer perspective, it's not really obvious what arguments the function expects without looking at the function's signature. Also, this is a trivial example, but if the function has complex initialization arguments, not just JavaScript primitives, it falls short very quickly.

Not to mention that it makes our function inflexible for modification, adding a new argument would mean we need to modify all the `CreateMeet()` function calls in our code, or worse, we easily introduce backward-incompatible changes in our JavaScript module.

## Passing an object literal

Thinking about the problem differently, we could modify the function signature and use an `options` object literal to pass our options to the function.

```javascript
function CreateMeet(options) {
   console.log(options.name, options.startDateTime);
}
```

This fails horribly because if we pass an object other than what `CreateMeet` expects or if we’re not passing anything at all. Without proper validation, executing the function will throw an error. 

One fix we could do is to define some sensible defaults and merge our `options` with the default options.


```javascript
function CreateMeet(options) {
  const defaultOptions = {
    name: 'No Name',
    startDateTime: new Date()
  }

  options = {
    ...defaultOptions,
    ...options
  }
}
```

Again, without validating `options` we could merge a totally unrelated object literal with `defaultOptions`.

Nonetheless, it is a good way of making sure the passed `options` argument contains all the properties that the function might need and **this solution is enough most of the time**, but it's not the `CreateMeet` function's job to make sure the options are correct.

Another problem with the solution above is that it's not very reusable in a complex application, where the options are maybe defined in other parts of the code, consider how we would execute this function:

```javascript
CreateMeet({
  name: 'My Meet',
  startDateTime: new Date(2021,0,6,13,15,0,0)
})
```

This type of configuration initialization falls short if we have many configuration options that our function does not necessarily care about, and if we want to validate for correct values too; or if we want to define required options.

## Passing in variables and object literals

One could argue we could write something like this where the `name` is explicitly defined...

```javascript
function CreateMeet(name, options) {
  ...
}
```
...but then we circled back to our original problem where every function argument was explicitly defined making it inflexible for future modifications.

## Passing in variadic variables

An alternative solution we could implement is using variadic function arguments.

```javascript
function CreateMeet(...options) {
  console.log(options)
}
```

With this approach, `...options` becomes an array of JavaScript primitive types, but we would still need to validate each individual option item in the array to make sure the correct option is passed to our function.

## Passing in variadic functions

**Variadic function arguments to the rescue!** In this solution we could just pass in functions for `...options` and to make sure that we only accept functions as arguments.

```javascript
function CreateMeet(...options) {
  options.forEach((opt) => {
    if ( typeof opt !== 'function' ) { return }
    ...
  })
}
```

In the function above if the `...options` item is not of type function it will continue to iterate to the next item. 

Okay, but what's the purpose of this? Well, we could pass in our specific options literal to the option functions that are passed as arguments which in turn validate and modify our options literal, and removing this concern from our `CreateMeet` function.

Consider the following option function that would be passed to `CreateMeet`.

```javascript
function Name(value) {
  return (options) => {
    options.name = value
  }
}
```

So what's happening here? The `Name` is an "option function" which, in turn, returns a function accepting our options literal from `CreateMeet`. Let's modify `CreateMeet` to understand it more clearly.


```javascript
function CreateMeet(...options) {
  let config = {
    name: '',
    startDateTime: null
  }

  options.forEach((opt) => {
    if ( typeof opt !== 'function' ) { return }
    opt(config)   
  })
```

Executing `CreateMeet` would look like this.

```javascript
CreateMeet(
  Name('My Meet')
)
```

Passing in `Name` as an argument, which, remember, returns a function, and this returned function from `Name` would be executed in `CreateMeet` with `opt(config)` where `config` is our configuration object literal that we actually care about.

Let's define a `startDateTime` function option to better understand this method.

```javascript
function StartDateTime(year, month, date, hour, minute) {
  return (options) => {
    // We don't care about defining seconds and milliseconds so we pass 0 to new Date()
    // In JS month starts at 0, but we would like to define 1 - 12 (January through December), this is why we subtract 1.
    // Also, proper validation is in order, this is just a simple example
    month = (month - 1 <= 0) ? 0 : month - 1
    options.startDateTime = new Date(year, month, date, hour, minute, 0, 0)
  }
}
```

Passing in these function arguments to `CreateMeet` would look like this.

```javascript
CreateMeet(
  Name('My Meet'),
  StartDateTime(2021, 1, 6, 13, 15)
)
```

This makes our function much more readable to other developers, we instantly know that `CreateMeet` is executed by defining a `Name` and `StartDateTime`.

Furthermore, we could extract the initialization of the options altogether from `CreateMeet` into a separate function such as this, which not necessarily need to be exported.

```javascript
function setupConfig(...options) {
  let config = {
    name: '',
    startDateTime: null
  }

  options.forEach((opt) => {
    if ( typeof opt !== 'function' ) { return }
    opt(config)   
  })

  return config
}
```

Now, `CreateMeet` would only execute code that it cares about.

```javascript
function CreateMeet(...options) {
    const config = setupConfig(...options)
    
    // do something with config
    console.log(config)
}
```

## Extending CreateMeet

Extending our `CreateMeet` function becomes trivial with this approach.

Let's say we want to add another option to our function, but still want to ensure backward compatibility. We want to add the option of allowing only specific users, from a list, in the meet, thus executing `CreateMeet` will handle this scenario correctly.

Our `AllowedUsers` function option could look like this.

```javascript
function AllowedUsers(userList) {
  return (options) => {
    options.allowedUsers = userList
  }
}
```

Passing in this new option function is as easy as adding a new argument to `CreateMeet`

```javascript
CreateMeet(
  Name(‘My Meet’),
  StartDateTime(2021,1,6,13,15),
  AllowedUsers([‘john’, ‘jane’])
)
```

Keep in mind that the public API of our function hasn't changed, the previous examples work the same way with or without `AllowedUsers` being passed to `CreateMeet`.

We can go as far as to add different methods to manipulate the same option, in this example, `AllowedUsers` only accepts a user list and then overwrites the configuration with that list.

Let's say, down the road, in a future version of our application, we'll want to add a function that accepts a single user name only. In this case, we could write a new function like this.

```javascript
function AllowedUser(userName) {
  return (options) => {
    options.allowedUsers.push(userName)
  }
}
```

Executing `CreateMeet` works as expected, end users can use either `AllowedUsers` *(plural)* to pass in a user list or `AllowedUser` *(singular)* to append a user name to an existing list.


## Conclusion

We, as developers, should be very aware of how the public-facing API of our code is being consumed by other users.

This technique helps to keep this API flexible enough for future modifications and it's just another technique in the arsenal of a developer.

Should you use it every time? Probably not, in most cases passing a configuration object literal is enough, but if you have complex configuration setups, want greater flexibility, and also extracting the configuration setup from functions that don't care about it, then this approach is a good fit.

**I hope you enjoyed this article, please comment and consider sharing it.**

If you have any questions you can contact me here in the comments or on [Twitter](https://twitter.com/feketegy).

Below you'll find the full example presented in this article as well as a Codepen demo.

-------------

### Full Example

```javascript
function Name(value) {
  return (options) => {
    options.name = value
  }
}

function StartDateTime(year, month, date, hour, minute) {
  return (options) => {
    month = (month - 1 <= 0) ? 0 : month - 1
    options.startDateTime = new Date(year, month, date, hour, minute, 0, 0)
  }
}

function AllowedUsers(userList) {
  return (options) => {
    options.allowedUsers = userList
  }
}

function AllowedUser(userName) {
  return (options) => {
    options.allowedUsers.push(userName)
  }
}

function setupConfig(...options) {
  let config = {
    name: '',
    startDateTime: null,
    allowedUsers: []
  }

  options.forEach((opt) => {
    if ( typeof opt !== 'function' ) { return }
    opt(config)   
  })

  return config
}

function CreateMeet(...options) {
    const config = setupConfig(...options)
    
    // do something with config
    console.log(config)
}

CreateMeet(
  Name('My Meet'),
  StartDateTime(2021, 1, 6, 13, 15)
)

CreateMeet(
  Name('Private Meet'),
  StartDateTime(2020, 1, 6, 14, 0),
  AllowedUsers(['john', 'jane'])
)

CreateMeet(
  Name('One-on-one Meet'),
  StartDateTime(2021, 1, 6, 14, 30),
  AllowedUser('kevin')
)
```

### Codepen Example

{% codepen https://codepen.io/feketegy/pen/BaLrLBZ default-tab=js,console %}