Sitemap
SwingDev Insights

SwingDev is part of a fast-growing InsurTech company from Silicon Valley — Hippo Insurance. Our technology-first approach is modernizing the otherwise stale home insurance industry, from IOT monitoring devices to our industry-leading software.

Adding Type Safety to Your JavaScript: An Overview to Better Code

7 min readMay 24, 2024

--

Press enter or click to view image in full size

JavaScript has long been a favorite among programmers and developers, and for good reason. Its versatility and manageable entry-level make it a top choice for a wide range of projects, including:

  • Backend (NodeJS),
  • Web apps (React, Angular, Vue),
  • Mobile applications (React Native),
  • Desktop applications (Electron),
  • Machine learning (TensorFlow.js, ml5.js).

In fact, JavaScript is even used in SpaceX’s Dragon vehicle as the language driving main user interfaces onboard.

But as great as JavaScript is, it does have its drawbacks. Being a weakly typed language, it can sometimes lead to unexpected bugs and issues. In this article, we’ll explore the benefits of adding type-checking to your JavaScript code and how to avoid the common pitfalls of weakly typed languages. From my own experience, I’ll share a story of a project plagued by bugs due to data discrepancies and a lack of type safety. I’ll also show you how you can use simple tools and techniques to make your code more resilient and reliable.

A quick note: I won’t be showing you how to configure the tools I’ll be mentioning in this article, and all examples were made using VS Code. Your experience may vary. Additionally, I won’t consider TypeScript in this article since it’s strictly about JavaScript projects.

Keep reading if you’re ready to take your JavaScript skills to the next level and avoid the common headaches of weakly typed languages.

Press enter or click to view image in full size
The headaches I talk about ;)

Types of type checking

Runtime

Runtime type checking occurs during the execution of the code. An example in React is PropTypes, which verifies, at runtime, whether the received props are of the correct type. However, this method is less helpful as it requires waiting for the app to reload after each change, navigating through a correct flow, and is prone to errors. It’s easy to miss a type-related bug or an edge case using this approach. Due to these limitations, no runtime tools will be included in this article.

Build-time

Build-time type checking takes place during the build phase of the code. Tools following this approach can be integrated into your build pipeline, preventing the build from succeeding if there are type errors. An example of this is Flow.

Static Analysis Time

Type checking during static analysis happens while writing code, without actually building or running it. This proactive approach allows developers to catch type-related issues early in the development process, minimizing the chances of errors slipping through undetected.

Enabling Type Checking in JavaScript

Well, you can’t. But you can. Sort of.

Flow

Citing after their official site: “Flow is a static type checker that allows developers to check for type errors while developing code.

Flow is designed to find type errors at compilation time, avoiding type errors at runtime. Flow works by using annotations in special JavaScript comments to indicate the expected type for an object or variable.”

Flow is an external library that hooks itself into your build pipeline to provide type safety. However, with a little bit of external tooling and configuration, you can hook up Flow to your IDE to provide real-time cues.

The actual notation is very-typescript-like. Let’s take a look.

// @flow
interface Person {
firstName: string;
lastName: string;
birthYear: number;
setBirthYear: (birthYear: number) => void;
middleName?: string;
}

function getFullName(firstName: string, lastName: string): string {
return `${firstName} ${lastName}`;
}

function getFullName(person: Person) {
return `${person.firstName} ${person.middleName} ${person.lastName}`;
}

Flow’s webpage nicely documents all the notation and features available.

When configured properly you can enjoy working code completion…

Press enter or click to view image in full size
Flow — code completion example

…and real-time static code analysis.

Press enter or click to view image in full size
Flow — static code analysis result example

Pros:

  • It can be integrated into your build, thus it can catch code with type errors.
  • Notation highly resembles TypeScript

Cons:

  • It can take a bit of effort to configure the tool correctly in both the IDE and your builds.
  • The integration with certain projects can be spotty.

JSDoc

What is JSDoc?

“JSDoc 3 is an API documentation generator for JavaScript, similar to Javadoc or phpDocumentor. […] JSDoc’s purpose is to document the API of your JavaScript application or library.”

However, it’ll do just fine with almost any type of application. I used JSDoc to document and apply type checking in the React application mentioned in the intro.

All major IDEs support JSDoc. The good part is it doesn’t require any additional tooling.

Let’s take a look at a code example:

/**
* Returns full name by combining first name and last name
* @param {string} firstName
* @param {string} lastName
* @return {string} Full name
*/
function getFullName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
/**
* A person
* @typedef {Object} Person
* @property {string} firstName - First name
* @property {string} lastName - Last name
* @property {number} birthYear - Birth year
* @property {(birthYear: number) => void} setBirthYear - Set person's birth year
* @property {string} [middleName] - Optional middle name
*/

Let’s take a look at the same function but that’s taking an object with structure defined as above.

/**
* Returns full name of a person
* @param {Person} person
* @return {string} Full name
*/
function getFullName(person) {
return `${person.firstName} ${person.middleName} ${person.lastName}`;
}

Don’t worry if you feel a little lost, here you can find a handy cheat sheet of JSDoc notation, and for a full reference please go here.

Without JSDoc, we’re unable to determine with full certainty what type of data the getFullName function is expecting. It’s not the worst-case scenario with the first example; it’s possible to infer that from reading the code. JavaScript is also a resilient language, so it would most likely handle an undefined value if passed.

However, with more complex code, it quickly becomes tricky and invites inconsistencies that will only grow over time. Take the second getFullName function: without JSDoc, we only get the ominous any type, as shown below.

Press enter or click to view image in full size
JavaScript — static code analysis result example

When the IDE can’t help us, we make ourselves prone to mistakes. With JSDoc, we would know that the person parameter is an object.

Press enter or click to view image in full size
JSDoc — code completion example

But it can get even better. By including the // @ts-check comment at the top of the file (or by enabling “js/ts.implicitProjectConfig.checkJs”: true in jsconfig.json if you want to enable it for the whole project), the TypeScript engine can also start looking for type errors and utilize full static code analysis.

Press enter or click to view image in full size
JSDoc — static code analysis result example

That’s useful and safe. Make sure you read till the end; I’ll show you how to supercharge JSDoc methods with a bit more of TypeScript.

Pros:

  • It provides type safety and code completion without the need for additional tooling.
  • It doesn’t interfere with project configuration and is project-type agnostic.
  • It doesn’t influence build times.

Cons:

  • JSDoc is comment-based. It won’t stop a build or publishing code with errors, thus allowing runtime errors.
  • Notation might get lengthy.

Bonus: Supercharge JSDoc with TypeScript interfaces

Did you know that JSDoc works nicely together with TypeScript? We can use that to provide type declarations in our JavaScript files. How? Let’s consider the following project structure:

my-app/
├─ Person.ts
├─ person.js

Person.ts holds an interface:

export interface Person {
firstName: string;
lastName: string;
birthYear: number;
setBirthYear: (birthYear: number) => void;
middleName?: string;
}

In person.js, we only import the interface inside JSDoc to get all the type safety goodies.

Press enter or click to view image in full size
JSDoc + TypeScript — code completion example
Press enter or click to view image in full size
JSDoc + TypeScript — static code analysis result example

Neat, huh?

Summary

We explored the benefits of integrating type-checking tools like Flow and JSDoc into JavaScript projects. These tools improve code reliability, catch errors early, and provide type safety.

Despite potential challenges, the advantages of improved code completion and project maintainability make these tools invaluable for developers seeking to improve their JavaScript projects.

Let me know in the comments if you use any of the tools mentioned in your projects ;)

--

--

SwingDev Insights
SwingDev Insights

Published in SwingDev Insights

SwingDev is part of a fast-growing InsurTech company from Silicon Valley — Hippo Insurance. Our technology-first approach is modernizing the otherwise stale home insurance industry, from IOT monitoring devices to our industry-leading software.

No responses yet