Create a unique ID generator with Directus Flows and Nanoid

Learn how to build a unique ID generator using Directus Flows and Nanoid with a custom Flow Operation, step by step.

Create a unique ID generator with Directus Flows and Nanoid

Introduction

Everyone needs at some point to generate unique and random IDs. But please, don't ever use Math.floor((Math.random() * 100) + 1);: it is not cryptographically secure and not even random.

Thankfully, many packages and tools exist that provide a secure and reliable way to generate unique IDs. For this tutorial, we will use the nanoid package.

The Flow Operation we will build is quite simple:

  1. Generate a unique ID based on two optional parameters; length and alphabet
  2. Return the ID

Before we begin, I strongly suggest you to read the awesome Directus Documentation and get familiar with Directus Extensions.

Create a new Flow Operation extension

Go to the root of your project and run the npx create-directus-extension@latest, choose the operation extension type, name it "nanoid" and hit enter.

This will scaffold a new Directus Flow Operation extension for you with the default template to help you get started.

We just need to tweak the extension's package.json a little bit to make sure that our extension's built files land into the right directory:

{
...,
"directus:extension": {
		"type": "operation",
		"path": {
			"app": "../extensions/operations/nanoid/app.js",
			"api": "../extensions/operations/nanoid/api.js"
		},
	},
...
}

Run npm run dev inside the package directory and you're all set.

Note: you don't need to install the nanoid package as it is already installed by default with Directus.

Building our unique ID generator

Let's begin with the API entrypoint (src/api.ts)

import { defineOperationApi } from "@directus/extensions-sdk";
import { customAlphabet, nanoid } from "nanoid";

type Options = {
  length: number;
  alphabet: string;
};

export default defineOperationApi({
  id: "operation-nanoid",
  handler: ({ length, alphabet }) => {
    const size = Number(length) || 21;
    return alphabet ? customAlphabet(alphabet, size)() : nanoid(size);
  },
});

What we did is dead simple:

  • Our handler function takes a length and an alphabet parameter that will be passed from the front-end
  • We make sur that the length is an actual Number and fallback to the default value if no value is passed (21)
  • If a custom alphabet is passed, we generate the unique ID with it, otherwise we use Nanoid's default alphabet

Let's finish with the app entrypoint (src/app.ts): 

import { defineOperationApp } from "@directus/extensions-sdk";

export default defineOperationApp({
  id: "operation-nanoid",
  name: "Nanoid",
  icon: "123",
  description: "Generate an unique string ID with Nanoid",
  overview: ({ length, alphabet }) => [
    {
      label: "Length",
      text: length,
    },
    {
      label: "Alphabet",
      text: alphabet,
    },
  ],
  options: [
    {
      field: "length",
      name: "Length",
      type: "integer",
      meta: {
        width: "half",
        interface: "input",
        options: {
          min: 1,
        },
      },
      schema: {
        default_value: 21,
      },
    },
    {
      field: "alphabet",
      name: "Custom alphabet",
      type: "string",
      meta: {
        width: "half",
        interface: "input",
        note: "Leave empty for default Nanoid alphabet.",
      },
    },
  ],
});

Et voilà! Your unique ID generator Flow Operation is now operational.

Just keep in mind that the more you reduce the length of your ID, the more you increase the probability of collisions. So take this in account, and use the Nanoid Collision Calculator before deciding of the size of your IDs.

Source code on GitHub.

You can install the package directly through npm by running npm i directus-extension-nanoid