Type Safety in JavaScript

Updated Feb 18, 2021#javascript#type-checking

I used Java and Swift for many years and really enjoyed rich IDE experiences along with type-safe programming languages. Recently I love writing pure JavaScript with text editors and really enjoy its simple dynamic nature.

But I noticed a trend that many companies and open source projects are migrating into TypeScript. Is it worth to use static type checkers on personal projects? Or just use a data validator like Joi for the sake truly runtime safety?

The motivation

What’s wrong with JavaScript nowadays? Basically not much! JavaScript is getting seriously better after ES6 and recent updates, but there’s one missing piece that’ll never be fixed is a type system which will make JavaScript become a type-safe language.

Most type-safe languages include some form of dynamic type checking, even if they also have a static type checker. The reason for this is that many useful features or properties are difficult or impossible to verify statically

Dynamic type checking is the process of verifying the type safety of a program at runtime while static type checking is the process of verifying the type safety of a program based on analysis of a program’s source code.

People came from type-safe languages (C++, Java, or Swift) know how important type-safety is and they want that in JavaScript, big companies want that in JavaScript!

Currently we have some income solutions to write safer JavaScript by using JavaScript object validators to check at runtime or static type checkers which allow you to annotate types but get stripped at compile-time.

JavaScript data validators

JavaScript data validators are just small, simple, intuitive, and standalone libraries which allow you to describe what shape of data you want using pure JavaScript. These validators are extremely useful when writing JavaScript in servers to validate dynamic data from users at runtime.

The most popular is Joi, mostly used in NodeJS servers.

// https://joi.dev/api/?v=17.4.0#example
const Joi = require('joi')

const schema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),

  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),

  repeat_password: Joi.ref('password'),

  access_token: [Joi.string(), Joi.number()],

  birth_year: Joi.number().integer().min(1900).max(2013),

  email: Joi.string().email({
    minDomainSegments: 2,
    tlds: {allow: ['com', 'net']}
  })
})
  .with('username', 'birth_year')
  .xor('password', 'access_token')
  .with('password', 'repeat_password')

schema.validate({username: 'abc', birth_year: 1994})
// -> { value: { username: 'abc', birth_year: 1994 } }

React provides an internal mechanism for adding type checking to components. React components use a special property named propTypes to set up type checking.

import React from 'react'
import PropTypes from 'prop-types'

class Awesome extends React.Component {
  static propTypes = {
    anyProp: PropTypes.any,
    booleanProp: PropTypes.bool,
    numberProp: PropTypes.number,
    stringProp: PropTypes.string,
    functionProp: PropTypes.func
  }

  render() {
    return <div>awesome content</div>
  }
}

Joi and prop-types are currently the best, there are also tons of other similar libraries on GitHub, these solutions have almost zero learning curves, fast to start, easy to test, and most importantly they enforce type-safety in JavaScript at runtime.

JavaScript type checkers

Static type checker comes with a type system and provides the backbone needed for many useful IDE features such as error highlighting, autocomplete, and automated refactoring.

Static type checker helps developer write code with fewer bugs by adding types to your code, trying to catch type errors within your code, and then remove them during compile time.

There are two popular static type checkers for JavaScript are Flow and TypeScript.

Flow is a static type checker for JavaScript, written in OCaml, developed at Facebook, super easy to setup in an existing codebase. It supports type system, type annotations, library definitions, linting and many more.

Flow can automatically infer type information from existing code by using a technique they called flow analysis, and pick up any type errors by itself. Flow is incrementally adoptable and it can be easily added and removed from our code base without breaking anything, it good in case we only want to enable type checking for only one part of our project.

// @flow
function square(n: number): number {
  return n * n
}

square('2') // Error!

TypeScript is not actually a static type check but can be used as one. It is a programming language developed by Microsoft, is a typed superset of JavaScript, compiled to plain JavaScript in the end. Major text editors and IDEs have built-in support for TypeScript.

With the goal of strengthening the JavaScript language to make large-scale application development easier, TypeScript integrates many modern programming features such as module, class and interface into the language.

TypeScript has great support in all major text editors and IDEs, making your life easier with code completion and type inferences.

const add = (x: number, y: number) => {
  return x + y
}

class Drawer<ClothingType> {
  contents: ClothingType[] = []

  add(object: ClothingType) {
    this.contents.push(object)
  }

  remove() {
    return this.contents.pop()
  }
}

Using a static type checker for Javascript can greatly improve the safety of your code, but it does comes with drawbacks like big investment on learning, increasing the verbosity of your code, or slowing you down.

Conclusions

When TypeScript was first introduced in 2012, it had features like classes, still not available in JavaScript. But JavaScript had come a long way since then, and now TypeScript is struggling to keep up. If there is anything missing in JavaScript, there is a Babel plugin to do it.

If you’re working on a large project that’s built to last, using static type checker is almost certainly a good idea. On the other hand, if you’re just throwing together a small weekend project, it’s probably safe to skip static typing.

And finally, I think that both Flow and TypeScript have reached a stage where they are totally valuable to use. It’s a matter of preferences and what works for you might not work for me.

JavaScript static type checkers only give me fake impression of type-safety

IMHO, Flow and TypeScript only give me fake impression of type-safety, they can enforce very well coding standard and documentation in big companies but not for my personal projects. I hate the status of missing types from third-party libraries so I only use prop-types and joi and still happy with it.