typescriptormprismaprisma2

Seeding with prisma nested create with one-to-one relation


Currently working on a project to learn a bit more about prisma with nestjs. But cant get the schema to work. The seed wont go through because the flat depends on the owner field, that can only be filled with the created user.

I wanted to create a simple database setup where a user can be an owner of a flat, but must have a flat.

A flat needs to have an owner, and can have multiple tenants.

Would be very thankful for a helpful hand here, otherwise its just another project that will never get over concepts.

Schema:

model User {
  userId        String    @id @default(uuid())
  firstName     String?
  lastName      String?
  nickname      String
  email         String    @unique
  password      String
  phoneNumber   String?
  ownerOf       Flat?     @relation("owner")
  ownerOfId     String?   @unique
  flatId        String
  flat          Flat      @relation(fields: [flatId], references: [flatId])
  paidFor    Expense[] @relation("paidBy")
  otherExpenses Expense[]
  updatedAt     DateTime? @updatedAt
  createdAt     DateTime? @default(now())

  @@map("users")
}

model Flat {
  flatId    String    @id @default(uuid())
  name      String
  owner     User?      @relation("owner", fields: [ownerId], references: [userId])
  ownerId   String?    @unique
  flatmates User[]
  expenses  Expense[]
  updatedAt DateTime? @updatedAt
  createdAt DateTime? @default(now())

  @@map("flats")
}

model Expense {
  expenseId   String    @id @default(uuid())
  flatId      String
  flat        Flat      @relation(fields: [flatId], references: [flatId])
  paidBy   User      @relation("paidBy", fields: [paidById], references: [userId])
  paidById String
  expenseFor  User[]
  amount      Float
  updatedAt   DateTime? @updatedAt
  createdAt   DateTime? @default(now())

  @@map("expenses")
}
const users = await prisma.user.create({
    data: {
      firstName: 'Flo',
      lastName: 'Test',
      nickname: 'flo',
      email: 'test@test.de',
      password: hash,
      flat: {
        create: {
          name: 'Test Flat',
          owner: {
            connect: {
              users,
            },
          },
        },
      },
    },
  });

Solution

  • I would start thinking in terms of relations.

    Additional requirements are:

    owner of flat remains optional

    You can model these relations like this in your schema.prisma (simplified):

    model User {
      userId    String @id @default(uuid())
      nickname  String
      flatId    String
      flat      Flat   @relation(fields: [flatId], references: [flatId], name: "tenantRelation")
      ownedFlat Flat?  @relation(name: "ownerRelation")
    
      @@map("users")
    }
    
    model Flat {
      flatId  String  @id @default(uuid())
      name    String
      ownerId String? @unique
      owner   User?   @relation(fields: [ownerId], references: [userId], name: "ownerRelation")
      tenants User[]  @relation(name: "tenantRelation")
    
      @@map("flats")
    }
    

    There is no need to introduce a redundant User.ownerOfId.

    That schema does not guarantee that each flat has an owner as ownerId is optional.

    If you accept this flaw, you can create user and flat like this:

    await prisma.user.create({
      data: { nickname: "flo", flat: { create: { name: "Test flat" } } },
    });
    

    and in a second step set the owner of the flat to the user...

    owner of flat should be mandatory

    If you do not accept this flaw, and make ownerId non-optional, seeding becomes indeed more difficult as you are having a circular dependency.

    In the schema, you just remove the two question marks:

    model Flat {
      ...
      ownerId String @unique
      owner   User   @relation(fields: [ownerId], references: [userId], name: "ownerRelation")
      ...
    

    You would then have to:

    import { v4 as uuidv4 } from "uuid";
    
    // ...
    
      const userId = uuidv4();
      const flatId = uuidv4();
    
      await prisma.$transaction(async ($tx) => {
        await $tx.$executeRaw`set constraints all deferred;`;
        await $tx.$executeRaw`INSERT INTO users ("userId", "nickname", "flatId") VALUES (${userId}, 'flo', ${flatId});`;
        await $tx.$executeRaw`INSERT INTO flats ("flatId", "name", "ownerId") VALUES (${flatId}, 'Test flat', ${userId});`;
      });
    

    which currently requires previewFeatures = ["interactiveTransactions"] in your schema.prisma.

    But before going down that route, I'd suggest to double check how your app would really use the database. Would user and flat always be created in the same step?