In this blog post I will introduce the concept of interfaces and how they can be useful even in dynamic languages. I will also use the library Implement.js to bring the concept to JavaScript, and show you how to get some extra utility out of interfaces.

What is an interface?

Google defines an interface as “a point where two systems, subjects, organisations, etc. meet and interact,” and this definition holds true for interfaces in programming. In software development, an interface is a structure that enforces specific properties on an object — in most languages this object is a class.

Here’s an example of an interface in Java:

In the above example, the Car interface describes a class that has two methods with no return type, both of which take a single integer argument. The details of the implementation of each function is left up to the class, this is why the methods both have no body. To ensure a class implements the Car interface, we use the implements keyword:

Simple

Interfaces in JavaScript

Interfaces are not a thing in JavaScript, not really anyway. JavaScript is a dynamic language, one where types are changed so often that the developer may not have even realised, because of this people argue there is no need for an interface to be added to the ECMAScript standard that JavaScript is based on.

However, JavaScript has grown massively as a backend language in the form of Node.js, and with that comes different requirements and a different crowd who may have a different opinion. To add to this, the language is quickly becoming the front-end tool; it has got to the point where many developers will write the vast majority of their HTML inside .js files in the form of JSX.

So as the language grows to take on more roles, it is helpful then to make sure one of our most crucial data structures is what we expected it to be. JavaScript may have the class keyword, but in truth this is just an uninstantiated constructor function, and once called it is simply an object. Objects are ubiquitous, so it is sometimes beneficial to ensure they match a specific shape.

Recently at work I found a case where a developer was expecting a property returned as part of an API response to be true but instead got "true" , causing a bug. An easy mistake, and one that could also have been avoided if we had an interface.

But wait, there’s more!

Interfaces, with a few minor modifications, could be used to reshape objects. Imagine implementing a “strict” interface, where no properties outside of the interface are permitted, we could delete or rename these properties, or even throw an error if we encounter them.

So now we have an interface that will tell us when we are missing certain properties, but also when we have unexpected properties, or if the properties’ types are not what we expect. This adds other possibilities, say for example refactoring a response from an API while adding that extra layer of safety on top of the interface’s standard behaviour. We could also use interfaces in unit tests if we have them throw errors.

Implement.js

Implement.js is a library that attempts to bring interfaces to JavaScript. The idea is simple: define an interface, define the types of it’s properties, and use it to ensure an object is what you expect it to be.

Setup

First install the package:

npm install implement-js

Next, create a .js file and import implement , Interface , and type :

Our first interface

To create an interface, simply call Interface and pass in a string as the name of your interface — it’s not recommended, but if you omit the name a unique ID will generated. This returns a function that accepts an object where the properties are all type objects, a second argument can also be passed with options to show warnings, throw errors, delete or rename properties, ensure only the properties of the interface are present, or to extend an existing Interface .

Here’s an interface that describes a car:

It has a seats property that should be of type number, a passengers array that contains objects which must themselves implement the Passenger interface, and it contains a beep property, which should be a function. The error and strict options have been set to true, meaning that errors will be thrown when a property of the interface is missing and also when a property not on the interface is found.

Implementing our interface

Now we want to implement our interface, in a simple example we will create an object using an object literal and see if we are able to implement our interface.

First, we create a Ford object, then we will attempt to implement it against our Car interface:

As we see from the comment above, this throws an error. Let’s look back at our Car interface:

We can see that while all the properties are present, strict mode is also true, meaning that the additional property fuelType causes an error to be thrown. Additionally, although we have a passengers property, it is not an array.

In order to correctly implement the interface, we remove fuelType and change the value of passengers so that it is an array containing objects that implement the Passenger interface:

“But JavaScript isn’t an Object-Oriented language!”

It’s true that while interfaces are typically associated with object-oriented languages, and JavaScript is a multi-paradigm language that uses prototypal inheritance, interfaces can still be highly useful.

For example, using implement-js we can easily refactor an API response while ensuring it has not deviated from what we expect. Here’s an example used in conjunction with redux-thunk :

First, we define the TwitterUser interface, which extends the User interface, as an object with twitterId and twitterUsername properties. trim is true meaning that we will discard any properties not described on the TwitterUser interface. Since our API returns properties in an unfriendly format, we have renamed the properties from twitter_username and twitter_id to camelcase versions of themselves.

Next, we define an async action with redux-thunk , the action triggers an API call, and we use our TwitterUser interface to discard properties we don’t want and to ensure it implements the properties we expect, with the correct types. If you prefer to keep your action creators pure(er) or don’t use redux-thunk , you may want to check the interface inside twitterService.getUser and return the result.

Note: when extending an interface options are not inherited

Unit tests are also a suitable place to use interfaces:

In summary