DEV Community

Cover image for TypeID-JS: Type Safe, K-Sortable Unique IDs for Javascript
John Lago for Jetify

Posted on • Originally published at jetify.com

TypeID-JS: Type Safe, K-Sortable Unique IDs for Javascript

Since we first announced TypeID last year, we've seen significant adoption and interest from the community, with 23 different language clients contributed by the community and 90,000 weekly NPM downloads of our Typescript Implementation.

Last week, we released version 1.0 of our Typescript implementation, TypeID-JS. To celebrate this release, we wanted to share more about why we wrote TypeID, and how we use it to ensure type safety at Jetify.

Type Safety and Unique Identifiers

We developed the idea for TypeID while building Jetify Cloud, our solution for deploying and managing Devbox or Docker based projects across your team. Jetify Cloud's architecture has many different entities we need to manage: Orgs, Users, Deployments, Secrets, and Projects, all of which require unique identifiers to distinguish them.

Originally, we started by following best practices and assigning a UUID to each instance of an entity. Still, we quickly ran into a problem: UUIDv7 lacks type safety! Take the code below as an example:

export const getMember = async (
  memberId: UUID,
  orgId: UUID,
) => {
  const { member, organization } =
    await authClient.organizations.members.get({
      organization_id: orgId,
      member_id: memberId,
    });

  return { member, organization };
};
Enter fullscreen mode Exit fullscreen mode

This function uses two UUIDs to look up a member and organization. However, the function cannot ensure that memberID and orgID represent the correct entity! If a developer accidentally uses a memberID where we were expecting an orgID, we would only discover the issue at runtime.

As we were looking for solutions to this problem, we encountered Stripe's Object ID, which encodes type information into IDs using a prefix. This seemed like a great solution, but unfortunately, we couldn't find a well-defined standard for consistently implementing typed IDs across multiple languages.

TypeID: K-sortable, type-safe, globally unique identifiers

TypeID is our attempt to create such a consistent standard. TypeID is a type-safe, K-sortable, globally unique identifier inspired by Stripe's prefixed types. TypeID also provides a consistent standard for other languages to implement their clients and libraries.

TypeIDs encode unique identifiers as a lowercase string with three parts:

  1. A prefix that represents the ID’s type (63 chars, lowercase ASCII letters)
  2. An underscore (_) separator
  3. A 128-bit UUIDv7 encoded as a 26-character string using a modified base32 encoding.
  user_2x4y6z8a0b1c2d3e4f5g6h7j8k
  └──┘ └────────────────────────┘
  type    uuid suffix (base32)
Enter fullscreen mode Exit fullscreen mode

With this format, a TypeID-compatible client can encode and decode type information into your IDs and then run checks at a build or compile time to ensure you are using the right ID. For example, if we wanted to create a random TypeID for a user entity, we could do something like this:

With TypeID, we can also add type checks to our functions and catch errors at runtime. Rewriting the example above, we can now be sure that developers will use the proper IDs in the right place:

import { TypeID } from 'typeid-js';

export const getMember = async (
  memberId: TypeID<'member'>,
  orgId: TypeID<'org'>,
) => {
    ...
}
Enter fullscreen mode Exit fullscreen mode

In addition to type safety, this format has a few properties that make it friendly for developers to use:

  1. UUIDv7 compatible: You can easily convert a TypeID into a UUID by removing the prefix and decoding the suffix.
  2. K-sortable: You can use a TypeID as a sortable primary key in your database with good locality.
  3. URL safe: We use TypeIDs in our URLs to make them easy to copy, paste, and share. Including TypeIDs in the URL makes simplifies generating page state.
  4. Easily selectable: You can copy and paste it by double-clicking (Try it!)

New Features in TypeID-JS v1.0

Today, we're announcing version 1.0 of our TypeID-JS library. This update adds several new features to improve usability and type safety, including:

  1. Unboxed, function-based, streamable TypeIDs: This makes it possible to serialize TypeIDs without casting them to strings and lets you use TypeIDs as keys in Maps and Sets.
  2. Stricter Type and Runtime checking: TypeID now throws an error if you attempt to parse an empty string or a typeid with the wrong prefix.
  3. Compatibility with TypeID spec v3: JS TypeIDs can now use underscores in the prefix. For example: pro_subscription_2x4y6z8a0b1c2d3e4f5g6h7j8k is a valid TypeID with prefix pro_subscription.

How to Use TypeID with your project

You can add TypeID to your JS project using your NodeJS package manager of choice. Not using JS? TypeID has 26 different implementations, ranging from Go to OCaml to SQL. If you’re interested in writing your own implementation of TypeID, you can check our our formal specification.

Looking to Power-Up your Development Team?

Our team at Jetify builds powerful developer tools. Simplify your deployments and projects with Jetify Cloud, or automate onboarding + dev environments with Devbox. You can follow us on Twitter, or chat with our developers live on our Discord Server. We also welcome issues and pull requests on our Github Repo.

Top comments (0)