question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support custom user defined ID patterns (on model and global basis)

See original GitHub issue

Problem

I prefer to scope my ids with initials of the models. This helps developers and customer support reduce any confusion by knowing exactly what model an ID corresponds to. Stripe uses this heavily.

Eg:

usr_<id>
post_<id>
price_<id>

Suggested solution

It would be nice to allow developers to override the default ID generator (as an advanced feature).

This may be coupled with the idea to allow the user to define the ID generation. For example, using nanoid or shortid as the id generator.

import { nanoid } from 'nanoid';
prisma.generateId = () => nanoid();
prisma.user.generateId = () => `u_${nanoid()}`;

Alternatives

My preference though would be to do this as close to the schema definition as possible. So another preferable option could be to allow support for other id generators + prefixes in the schema definition.

eg:

model User {
    id  @id(type: "nanoid", prefix: "u_")
}

Additional context

Example of stripe id prefixes: https://gist.github.com/fnky/76f533366f75cf75802c8052b577e2a5

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:149
  • Comments:26

github_iconTop GitHub Comments

21reactions
appinteractivecommented, Oct 22, 2021

I Love ❤️ that approach as it would be totally flexible and usable on any field not just the id:

model User {
    id  @id(type: "nanoid", prefix: "u_")
}

Any progress on that one?

16reactions
smolakcommented, Jul 8, 2022

I saw there is a similar issue present here: https://github.com/prisma/prisma/issues/12125

After tweeting this: https://twitter.com/jacek_smolak/status/1544805438344122371 I was asked to write about my use-case, so here it is.

The problem

When I saw what the possible options for generating IDs (as a string) with Prisma are, I noticed there are cuid() and uuid(). After reading about both and discovering nanoid and learning it is better (I suggest Googling it, and of course this is not 100% true [it being better] for everyone), I wanted to make it possible to be used for Prisma’s schema.

The (mid) solution

I discovered that I could mutate the data for certain actions before it hits the DB. And in order to do that, one can use middleware. tl;dr - for create action you take the payload and overwrite the id property with whatever you want. Here’s what I did (not exact code, but you will get the idea):

export const generateModelId: Prisma.Middleware = async (params: Prisma.MiddlewareParams, next) => {
  if (params.action === "create") {
    switch (params.model) {
      case "User":
        params.args.data.id = generateUserId();
        break;

      // ...
    }
  }

  return await next(params);
};

And registering it:

prisma.$use(generateModelId);

I went this way to have separate ID generators for separate models so that I could go with prefixes. Why? I can’t tell you how great they are if all you see is some IDs here and there in logs or in payloads etc. and not knowing exactly where they originate from. See my previous comment.

So, the generateUserId() (and any other) has this implementation:

import { generateId } from "./generateId";

export const USER_ID_PREFIX = "usr_";

export const generateUserId = () => generateId(USER_ID_PREFIX);

And the underlying generateId function is this:

import { customAlphabet } from "nanoid";

// I went with custom alphabet to limit mistakes with I and l and alike (not all but some)
const ALPHABET = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ1234567890_";

export const generateId = (prefix = "") => `${prefix}${customAlphabet(ALPHABET)()}`;

Problems with that solutions

Going with middleware I saw that:

  1. Since schema.prisma has no @default(...) value set on @id type of column, I had to provide a default value in all of the code where given model was present. So e.g. if I have a User model and it looks like this:
User {
  id String @id
 ...
}

Then if I want to e.g. create a user in some parts of the code, I need to pass id with a value in any form, e.g.:

prisma.user.create({
  data: {
    id: 'I can put whatever here, or leave it as an empty string, as the middleware will overwrite it but I NEED to',
    email: 'john.rambo@gmail.com',
    name: 'John'
  },
});

That sux.

  1. Upserting data. If you want to do that, then the usage looks like this:
prisma.user.upsert({
  where: {},
  update: {},
  create: {
    id: 'same situation as before',
    email: 'john.rambo@gmail.com',
    name: 'John'
  },
});

And then the payload in the middleware changes, and so I need to create separate switch / case model handling. That sux again.

A better solution

Minimal one

The minimal one would be to have a ID generator registered / added, something like:

interface CustomIdGenerator {
  name: string;
  generateId: () => string; // or a Promise<string> as some generators out there are async
}

// Strict naming convention so that it can be used in schema
const name = 'customGenerator';
const generateId = () => { ... }

prisma.addIdGenerator({ name, generateId });

Usage in prisma.schema:

User {
  id String @id @default(customGenerator())
 ...
}

Perhaps also adding a setIdGenerator to Prisma’s interface - for overwriting a custom existing one, if need be. Just thinking out loud.

That would be the simplest version.

A slightly better one

This time model would be passed to the generator, so that if I wanted to have different prefixes set up for different models, I could do so.

interface CustomIdGenerator {
  name: string;
  generateId: ({ model }) => string; // or a Promise<string> as some generators out there are async
}

const name = 'customGenerator';
const generateId = ({ model }) => {
  switch(model) {
    case 'User':
      return 'usr_123';
      break;
  }
}

prisma.addIdGenerator({ name, generateId });

Usage in prisma.schema:

User {
  id String @id @default(customGenerator())
  ...
}

In both cases there’s no detecting what method is using it, as the generator would just be used as any other built in.

Hope this was clear ✌️

Read more comments on GitHub >

github_iconTop Results From Across the Web

Identity model customization in ASP.NET Core | Microsoft Learn
This article describes how to customize the underlying Entity Framework Core data model for ASP.NET Core Identity.
Read more >
Best practices for unique identifiers - Android Developers
In cases where an FID isn't practical, you can also use custom globally-unique IDs (GUIDs) to uniquely identify an app instance.
Read more >
Snippets in Visual Studio Code
Code snippets are templates that make it easier to enter repeating code patterns, ... Multi-language and global user-defined snippets are all defined in ......
Read more >
Values that you specify when you create or update a distribution
When you use the CloudFront console to create a new distribution or update an ... Custom SSL client support (Applies only when you...
Read more >
The Django admin site
In the preceding example, the ModelAdmin class doesn't define any custom values ... You can't use this decorator if you have to reference...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found