Is it possible to establish a specific many-to-many relationship using Prisma?

In the Prisma documentation, it states that the set function can be used to override the value of a relation.

const user = await prisma.user.update({
  where: { email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="73121f1a10e0b231212242126850b2c1d">[email protected]</a>' },
  data: {
    posts: {
      set: [{ id: 32 }, { id: 42 }],
    },
  },
})

However, when attempting to use it with an explicit many-to-many relation, it does not function as expected.

model Product {
  // Model fields here...
}

model Tag {
  // Model fields here...
}

model ProductTag {
  // Model fields here...
}

Here is my code snippet for updating a Product:

// Code snippet here...

I am looking to correctly update the product's tag based on the product's information. How can I achieve this?

Answer №1

Preamble

As I was grappling with this issue myself, I stumbled upon this question. While you may have already found the solution by now, I thought it would be helpful to share the approach I devised in case others find themselves in a similar situation in the future.

I chanced upon your GitHub discussion, which led me to another GitHub issue that ultimately guided me towards what I believe is a viable solution.

Problem Explanation

The challenge lies in explicitly handling a many-to-many relationship, where relations are managed through the creation/deletion of records within a separate relation table. This concept is elaborated in the Prisma documentation available here. The set method does not generate or remove records; instead, it merely disconnects existing relationships and establishes connections between the specified models.

Solution

Since set does not handle record creation or deletion, we need to address both of these tasks separately. Fortunately, both operations can be executed within a single query. Below is a modified version of your code reflecting my implementation:

// BEFORE -- NOT WORKING
update(id: string, updateProductDto: UpdateProductDto) {
    const tags = updateProductDto.tags.map((tag) => ({
      productId_tagId: {
        productId: id,
        tagId: tag.id,
      },
    }));

    return this.prisma.product.update({
      where: { id: id },
      data: {
        ...updateProductDto,
        tags: {
          set: [...tags],
        },
      },
    });
  }

// AFTER -- WORKING
update(id: string, updateProductDto: UpdateProductDto) {
    return this.prisma.product.update({
      where: { id },
      data: {
        ...updateProductDto,
        tags: {
          deleteMany: {},
          create: updateProductDto.tags.map((t) => ({ tag: { connect: { t.id } } }))
        },
      },
    });
  }

Notes

There are a few considerations to keep in mind when employing this approach:

  • This method completely resets the relationships between the two models, even if the new set of tags includes tags that were previously associated. Therefore, if there are additional properties in your relation table records that you wish to retain (e.g., the createdAt property), you must ensure they are transferred accordingly.
  • I have only validated this approach using Prisma versions 4.1.0 and higher. I am uncertain about the version you were utilizing at that time or currently.

I trust that this information proves beneficial to anyone who comes across this post!

Answer №2

If you want to update the productTag records, one approach is to first delete the existing records and then create new ones using two separate queries. You can execute these queries as a transaction for better data consistency.

Here's an example of how you can achieve this:

// Perform other updates to Tags with values from updateProductDto.

const deleteOldTags = prisma.productTag.deleteMany({
    where: { productId: "__PRODUCT_ID__" },
});

const addNewTags = prisma.productTag.createMany({
    data: [
        {
            productId: "__PRODUCT_ID__",
            tagId: TAG_ID_VALUE
        },
        // Add more productID and tagId objects here.
    ]
})

let updateTags = await prisma.$transaction([deleteOldTags, addNewTags])

This workaround differs slightly from using set as it will create new records every time instead of updating existing ones.

Answer №3

When responding, please make sure to include the error messages and your ProductTagWhereUniqueInput from node_modules/.prisma/client/index.d.ts

update(id: string, updateProductDto: UpdateProductDto) {
    const tags = updateProductDto.tags.map((tag) => ({
        // It is not necessary to encapsulate 
        productId: id,
        tagId: tag.id,
    }));
     // To prevent the tags property from being updated in the product table, we will use set instead.
    delete updateProductDto.tags;

    console.log(JSON.stringify(tags));

    return this.prisma.product.update({
      where: { id: id },
      data: {
        ...updateProductDto,
        tags: {
          set: tags, // No need to reconstruct the array
        },
      },
    });
  }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transform a literal string type definition into a string value (similar to the typeof operator), or is it the other way around in TypeScript?

Is there a way to retrieve the string value of a string literal type without having to define it twice, similar to the typeof operator in C#? myStringLiteral: 'STRING TYPE'; myString: string = typeof(myStringLiteral); // I want myString to be e ...

Utilizing a nested interface in Typescript allows for creating more complex and

My current interface is structured like this: export interface Foo { data?: Foo; bar?: boolean; } Depending on the scenario, data is used as foo.data.bar or foo.bar. However, when implementing the above interface, I encounter the error message: Prope ...

When configuring the Redux logger, the type 'Middleware<{}, any, Dispatch<UnknownAction>>' is not compatible with type 'Middleware<{}, any, Dispatch<AnyAction>>'

In my React project, I have defined the redux logger with the package version "redux-logger": "^3.0.6" in the file store.ts as shown below: import { configureStore } from '@reduxjs/toolkit'; import rootReducer from '@/re ...

Struggling with TypeScript compilation in a Vue.js project? Encounter error code TS2352

Here is my code snippet from window.ts import Vue from 'vue' interface BrowserWindow extends Window { app: Vue } const browserWindow = window as BrowserWindow export default browserWindow Encountering a compilation error Error message: TS2 ...

Comparing the cost of memory and performance between using classes and interfaces

As I delve into writing TypeScript for my Angular project, one burning question arises — should I use an Interface or a Class to create my domain objects? My quest is to uncover solid data regarding the actual implications of opting for the Class route. ...

Please click twice in order to log in to Angular 16

Whenever I attempt to log in, I face the issue of having to click twice. The first click does not work, but the second one does. Additionally, an error message pops up: TypeError: Cannot read properties of undefined (reading 'name'). I am unsure ...

Creating a .d.ts file for a JavaScript file that exports a plain object - tips and best practices

I am attempting to include a .d.ts file for an existing js file. The type.js file looks like this: // type.js export default { NORMAL: '001', CHECK: '002', }; Now, I have added a type.d.ts file as follows: // type.d.ts decla ...

The async and await functions do not necessarily wait for one another

I am working with Typescript and have the following code: import sql = require("mssql"); const config: sql.config = {.... } const connect = async() => { return new Promise((resolve, reject) => { new sql.ConnectionPool(config).connect((e ...

Why am I encountering a 400 error with my mutation in Apollo Client, when I have no issues running it in Playground?

After successfully testing a mutation in the playground, I attempted to implement it in my Apollo client on React. However, I encountered an error message stating: Unhandled Rejection (Error): Network error: Response not successful: Received status code 40 ...

Obtain abbreviated names for the days of the week starting from Monday to Sunday using JavaScript

Is there a way to retrieve the abbreviated names of each day of the week in JavaScript, starting from Monday through Sunday? ...

Passing a parameter from a redirect function to an onClick in React using TypeScript

I am facing a challenge in passing a parameter to my redirectSingleLocker function. This function is intended to take me to the detailed page of a specific locker, identified by a guid. The lockerData.map method is used to display all the JSON data in a ta ...

Why does TypeScript trigger an ESLint error when using `extend` on a template string?

I am looking to create a TrimStart type in the following way: type TrimStart<T extends string> = T extends ` ${infer Rest}` ? TrimStart<Rest> : T; type TT = TrimStart<' Vue React Angular'>; // 'Vue React Angular' H ...

Acquired this as empty

I encountered a strange error message saying "this is null" and I can't figure out what the issue is. Here is my demo on Stackblitz.com with an example code for your reference. Component ngOnInit() { this.getCurrentLocation(); } getCurrentL ...

Encountered an unexpected token error in react-leaflet while attempting to render the component for a unit test scenario

Error in running test suite An unexpected token was encountered by Jest Jest failed to parse a file due to non-standard JavaScript syntax used in the code or its dependencies, or when Jest does not support such syntax configurations. SyntaxError: Unexpe ...

Mismatched data types caused by immutability

I'm having trouble with my object that has a middleware property and the types aren't working as expected. The error message is stating that the two middlewares are incompatible because one of them is set to readonly. Does anyone know how I can r ...

Ensuring strictNullChecks in Typescript is crucial when passing values between functions

When using the --strictNullChecks flag in TypeScript, there seems to be an issue with inferring that an optional property is not undefined when the check occurs in a separate function. (Please refer to the example provided, as articulating this clearly is ...

Tips for successfully passing function variables as parameters to Angular 2 HTTP subscribe callbacks

I attempted this.propositionService.addProposition(this.proposition) .subscribe(this.addSuccessCallback, this.addFailureCallback); The issue I am encountering is that both addSuccessCallback and addFailureCallback do not have acces ...

Is it possible to remove a complete row in Angular 2 using Material Design

JSON [ { position: 1, name: 'test', value: 1.0079, symbol: 'HHH' }, { position: 2, name: 'test2', value: 4.0026, symbol: 'BBB' }, { position: 3, name: 'test3', value: 6.941, symbol: 'BB' }, ...

"Troubleshooting: Why Angular2's ngOnChanges is not triggering with array input

I am currently using a ParentComponent to pass inputs to a ChildComponent. When the input is a number, the ngOnChanges hook successfully fires. However, when it's an array, the hook does not trigger. Could someone help me identify what I might be doi ...

Retrieving data from an API using VUEJS3 and Typescript

I am facing an issue with displaying data in my template. When I try to do so, the screen remains blank. I am using Vue.js 3 with TypeScript and I am fairly new to this technology. <template> <div> <img :src="datas[0].imag ...