Search code examples
reactjsdatabasepostgresqlnext.jsprisma

Prisma Query: How to Conditionally Fetch First Element from List?


I'm currently working on a Next.js project where I'm utilizing Prisma to fetch a list of equipment through an API route. The issue I'm facing is that each equipment object in the list contains a property called 'brand', which contains a list of images with a considerable amount of data. This has resulted in a heavier JSON payload, impacting the performance of my application.

To optimize this, I'm seeking guidance on how to fetch only the first image from the list. This would significantly reduce the payload size and improve the overall user experience.

Here's a simplified version of my Equipment model in Prisma:

  id            Int      @id @default(autoincrement())
  createdAt     DateTime @default(now()) @map("created_at")
  updatedAt     DateTime @updatedAt @map("updated_at")
  title         String   @db.VarChar(255)
  description   String?  @db.Text
  descriptionFr String?  @db.Text
  descriptionAr String?  @db.Text
  published     Boolean  @default(false)
  featured      Boolean  @default(false)
  brochure      String?  @db.VarChar(255)

  operatingWeight                                 Decimal? @map("operating_weight") @db.Decimal(10, 2)
  engineModel                                     String?  @map("engine_model")
  enginePower                                     String?  @map("engine_power")
 
 ...(skipped a very long list of caracteristics)
 
  keywordsAr                                      String?  @map("keywords_ar")

  salable   Boolean  @default(false)
  rentable  Boolean  @default(false)
  salePrice Decimal? @map("sale_price") @db.Decimal(10, 2)
  rentPrice Decimal? @map("rent_price") @db.Decimal(10, 2)
  year      Int?

  category      Category         @relation(fields: [categoryId], references: [id], onDelete: Cascade)
  categoryId    Int              @map("category_id")
  subcategory   Subcategory      @relation(fields: [subcategoryId], references: [id], onDelete: Cascade)
  subcategoryId Int              @map("subcategory_id")
  brand         Brand?           @relation(fields: [brandId], references: [id], onDelete: Cascade)
  brandId       Int?             @map("brand_id")
  images        EquipmentImage[]
  quotes        Quote[]

  @@map("equipments")
}

I am using this code to fetch from DB:

const equipments = await prisma.equipment.findMany({
    where: {
      published: true,
      category: {
        ...(slug && {
          slug: slug as string,
        }),
      },
    },
    include: {
      subcategory: {
        select: {
          id: true,
          slug: true,
          category: {
            select: {
              slug: true,
            },
          },
        },
      },
      images: true,
      brand: {
        select: {
          id: true,
          images: true,      (this is the part i desire to change)
        },
      },
    },
  });

This is an example of a json for 1 equipemnt:

{
  "id": 66,
  "title": "SY16C",
  "description": "\\u003ch3\\u003eHigh Performance\\u003c/h3\\u003e\\n\\u003cp\\u003eStrong power ensures reliability in tough working conditions.\\u003c/p\\u003e\\n\\u003ch3\\u003eHigh Reliability\\u003c/h3\\u003e\\n\\u003cp\\u003eLoad Sensitive Flow Distribution System: Senses micro changes of the load and adjusts flow and pressure in real time; optimized main valve core for perfect synergism between actuators and precise control.\\u003c/p\\u003e\\n\\u003ch3\\u003eStrong Adaptability to Various Situations\\u003c/h3\\u003e\\n\\u003cp\\u003eChangeable Steel Track/ Rubber Track. Cabin or canopy for your to choose.\\u003c/p\\u003e",
  "featured": null,
  "images": [
    {
      "id": 138,
      "createdAt": "2023-04-15T12:53:25.518Z",
      "updatedAt": "2023-04-15T12:53:25.518Z",
      "fileId": "4_zd299cdd293a1240180410413_f113aaccb5f06097c_d20230415_m125324_c003_v0312006_t0021_u01681563204430",
      "fileName": "SY16C-m355a7yy.jpeg",
      "intent": "CARD_IMAGE",
      "equipmentId": 66
    }
  ],
  "subcategory": {
    "id": 1,
    "slug": "mini_excavators",
    "category": {
      "slug": "excavators"
    }
  },
  "brand": {
    "id": 1,
    "images": [
      {
        "id": 16,
        "createdAt": "2022-10-28T23:31:25.004Z",
        "updatedAt": "2022-10-28T23:31:25.004Z",
        "fileId": "4_zd299cdd293a1240180410413_f1109c1df1ed4b5c8_d20221028_m233123_c003_v0312009_t0015_u01666999883556",
        "fileName": "SANY-4tu5s2li.png",
        "intent": "CARD_IMAGE",
        "brandId": 1
      },
      {
        "id": 29,
        "createdAt": "2022-11-22T14:01:11.369Z",
        "updatedAt": "2022-11-22T14:01:11.369Z",
        "fileId": "4_zd299cdd293a1240180410413_f118a48f913145fac_d20221122_m140109_c003_v0312016_t0008_u01669125669681",
        "fileName": "SANY-nwffhrfd.jpeg",
        "intent": "HEADER_IMAGE",
        "brandId": 1
      },
      {
        "id": 30,
        "createdAt": "2023-04-04T16:39:14.411Z",
        "updatedAt": "2023-04-04T16:39:14.411Z",
        "fileId": "4_zd299cdd293a1240180410413_f10107e14f6adfdea_d20230404_m163905_c003_v0312019_t0021_u01680626345708",
        "fileName": "SANY-qsewqprm.jpeg",
        "intent": "CARD_IMAGE",
        "brandId": 1
      },
      {
        "id": 31,
        "createdAt": "2023-04-04T16:39:14.411Z",
        "updatedAt": "2023-04-04T16:39:14.411Z",
        "fileId": "4_zd299cdd293a1240180410413_f1074d657564b40c3_d20230404_m163905_c003_v0312019_t0011_u01680626345626",
        "fileName": "SANY-tephlhc9.jpeg",
        "intent": "CARD_IMAGE",
        "brandId": 1
      },
      {
        "id": 32,
        "createdAt": "2023-04-04T16:39:14.411Z",
        "updatedAt": "2023-04-04T16:39:14.411Z",
        "fileId": "4_zd299cdd293a1240180410413_f112d3e8dc7929cd2_d20230404_m163906_c003_v0312006_t0024_u01680626346147",
        "fileName": "SANY-hk0n1xen.jpeg",
        "intent": "CARD_IMAGE",
        "brandId": 1
      },
      {
        "id": 33,
        "createdAt": "2023-04-04T16:39:14.411Z",
        "updatedAt": "2023-04-04T16:39:14.411Z",
        "fileId": "4_zd299cdd293a1240180410413_f1162e03509fae13b_d20230404_m163906_c003_v0312019_t0003_u01680626346268",
        "fileName": "SANY-d53y2yey.jpeg",
        "intent": "CARD_IMAGE",
        "brandId": 1
      },
      {
        "id": 34,
        "createdAt": "2023-04-04T16:39:14.411Z",
        "updatedAt": "2023-04-04T16:39:14.411Z",
        "fileId": "4_zd299cdd293a1240180410413_f11811a508d180ffd_d20230404_m163908_c003_v0312020_t0022_u01680626348476",
        "fileName": "SANY-51wwb69p.jpeg",
        "intent": "CARD_IMAGE",
        "brandId": 1
      }
    ]
  },
  "createdAt": "2022-11-07T12:05:10.553Z",
  "mainAttributes": null,
  "operatingWeight": "1.88",
  "engineModel": "Yanmar \\t3TNV70",
  "enginePower": "10.3",
  "engineDisplacement": "0.854",
  "radiator": "3.8",
  "fuelTank": "20",
  "hydraulicTank": "21",
  "bucketDiggingForce": "15.2",
  "armDiggingForce": "9.2",
  "carrierWheelOnEachSide": null,
  "thrustWheelOnEachSide": "3",
  "standardBoom": "1.81",
  "standardStick": "1.13",
  "bucketCapacity": "0.04"
}

My aim is to fetch the list of equipment with only the first image for each equipment item. I believe there might be a way to achieve this using Prisma's querying capabilities, but I'm unsure about the correct approach.

If anyone has experience with optimizing API fetches using Prisma and Next.js in a similar context, I would greatly appreciate your insights and any code examples you could provide.

Thank you for taking the time to help!


Solution

  • I managed to find a solution, thanks to CHATGPT (he had to fail many times in order to come up with a working solution)

    Adding 'take : 1' to my brand 'select', made the code work and return the desired results:

    const equipments = await prisma.equipment.findMany({
        where: {
          published: true,
          category: {
            ...(slug && {
              slug: slug as string,
            }),
          },
        },
        include: {
          subcategory: {
            select: {
              id: true,
              slug: true,
              category: {
                select: {
                  slug: true,
                },
              },
            },
          },
          images: {
            select: {
              id: true,
              fileId: true,
              fileName: true,
              equipmentId: true,
              intent: true,
            },
          },
          brand: {
            select: {
              id: true,
              images: {
                select: {
                  fileId: true,
                  fileName: true,
                },
                take: 1, // Take only the first image
              },
            },
          },
        },
      });