Prisma: how to delete a record where it may or may not have children - prisma

I am trying to figure out how to delete a record created in my graphql, apollo, prisma app, where the record I am deleting may have a has many relationship to another model.
I have a model called IssueGroups. IssueGroups have many issues.
I have a model with:
import * as Prisma from "#prisma/client"
import { Field, ObjectType } from "type-graphql"
import { BaseModel } from "../shared/base.model"
#ObjectType()
export class IssueGroup extends BaseModel implements Prisma.IssueGroup {
#Field()
title: string
#Field()
description: string
#Field(type => String)
issues: Prisma.Issue[];
}
A resolver with:
import { Arg, Mutation, Query, Resolver } from "type-graphql"
import { IssueGroup } from "./issueGroup.model"
import { IssueGroupService } from "./issueGroup.service"
import { IssueGroupInput } from "./inputs/create.input"
import { Inject, Service } from "typedi"
#Service()
#Resolver(() => IssueGroup)
export default class IssueGroupResolver {
#Inject(() => IssueGroupService)
issueGroupService: IssueGroupService
#Query(() => [IssueGroup])
async allIssueGroups() {
return await this.issueGroupService.getAllIssueGroups()
}
#Query(() => IssueGroup)
async issueGroup(#Arg("id") id: string) {
return await this.issueGroupService.getIssueGroup(id)
}
#Mutation(() => IssueGroup)
async createIssueGroup(#Arg("data") data: IssueGroupInput) {
return await this.issueGroupService.createIssueGroup(data)
}
// : Promise<IssueGroup[]> {
#Mutation(() => IssueGroup)
async updateIssueGroup(
#Arg("id") id: string,
#Arg("data") data: IssueGroupInput
) {
return await this.issueGroupService.updateIssueGroup(id, data)
}
#Mutation(() => IssueGroup)
async deleteIssueGroup(#Arg("id") id: string) {
return await this.issueGroupService.deleteIssueGroup(id)
}
}
// private readonly issueGroupService: IssueGroupService
// #Query(() => [IssueGroup])
// async issueGroups(): Promise<IssueGroup[]> {
// return this.issueGroupService.findAll()
// }
// #Query(() => IssueGroup)
// async issueGroup(#Arg("id") id: string): Promise<IssueGroup> {
// return this.issueGroupService.findOne(id)
// }
// #Mutation(() => IssueGroup)
// async createIssueGroup(
// #Arg("data") data: IssueGroupInput
// ): Promise<IssueGroup> {
// return this.issueGroupService.create(data)
// }
A service with:
import { prisma } from "../../lib/prisma"
import { Service } from "typedi"
import { IssueGroupInput } from "./inputs/create.input"
import { Resolver } from "type-graphql"
import { IssueGroup } from "./issueGroup.model"
#Service()
#Resolver(() => IssueGroup)
export class IssueGroupService {
async createIssueGroup(data: IssueGroupInput) {
return await prisma.issueGroup.create({
data,
})
}
async deleteIssueGroup(id: string) {
return await prisma.issueGroup.delete({ where: { id } })
}
async updateIssueGroup(id: string, data: IssueGroupInput) {
const issueGroup = await prisma.issueGroup.findUnique({ where: { id } })
if (!issueGroup) {
throw new Error("Issue not found")
}
return await prisma.issueGroup.update({ where: { id }, data })
}
async getAllIssueGroups() {
return (await prisma.issueGroup.findMany({orderBy: {title: 'asc'}}))
}
async getIssueGroup(id: string) {
return await prisma.issueGroup.findUnique({
where: {
id,
},
})
}
}
When I try to delete the issueGroup (which does not currently have any issues, I can see an error that points to issue.delete and says:
operation failed because it depends on one or more records that were
required but not found. Record to delete does not exist.
In my form I have:
import * as React from "react"
import { gql } from "#apollo/client"
import type { IssueGroupInput } from "lib/graphql"
import { QueryMode, Role, SortOrder, useAllIssueGroupsQuery, useDeleteIssueGroupMutation } from "lib/graphql"
const __ = gql`
query AllIssueGroups {
allIssueGroups {
id
title
description
}
}
mutation deleteIssueGroup($id: String!) {
deleteIssueGroup(id: $id) {
id
title
description
issues // I have tried both with and without issues in this list
}
}
`
export default function IssueGroups() {
const [deleteIssueGroup] = useDeleteIssueGroupMutation()
const { data, loading, refetch } = useAllIssueGroupsQuery({
fetchPolicy: "cache-and-network",
})
const allIssueGroups = data?.allIssueGroups
const onDeleteIssueGroup = (id: string) => {
return (
deleteIssueGroup({ variables: { id } }).then(() => refetch())
)
}
return (
<List>
{data?.allIssueGroups.map((issueGroup) => (
<ListItem key={issueGroup.id}>
{issueGroup.title}{issueGroup.description}
<Button onClick={() => onDeleteIssueGroup(issueGroup.id)}>Delete</Button>
</ListItem>
))}
</List>
)
}
I have seen this page of the prisma documentation and I think I have followed its advice for how to deal with an issue when the parent issueGroup is deleted.
model IssueGroup {
id String #id #default(dbgenerated("gen_random_uuid()")) #db.Uuid
title String
description String
issues Issue[]
createdAt DateTime #default(now()) #db.Timestamptz(6)
updatedAt DateTime #default(now()) #updatedAt #db.Timestamptz(6)
}
model Issue {
id String #id #default(dbgenerated("gen_random_uuid()")) #db.Uuid
title String
description String
issueGroup IssueGroup #relation(fields: [issueGroupId], references: [id], onDelete: SetNull, onUpdate: Cascade)
issueGroupId String #db.Uuid
subscribers UserIssue[]
createdAt DateTime #default(now()) #db.Timestamptz(6)
updatedAt DateTime #default(now()) #updatedAt #db.Timestamptz(6)
}
However, VS code thinks this is an error. It gives me an error message that says:
The onDelete referential action of a relation should not be set to
SetNull when a referenced field is required. We recommend either to
choose another referential action, or to make the referenced fields
optional.
My db is psql, which the prisma docs suggest, should allow SetNull
PostgreSQL PostgreSQL is the only database supported by Prisma that
allows you to define a SetNull referential action that refers to a
non-nullable field. However, this raises a foreign key constraint
error when the action is triggered at runtime.
For this reason, when you set postgres as the database provider in the
(default) foreignKeys relation mode, Prisma warns users to mark as
optional any fields that are included in a #relation attribute with a
SetNull referential action. For all other database providers, Prisma
rejects the schema with a validation error.
I can see from this page of the docs that lists cannot be optional, so I dont think I can follow the advice in the relational page of the docs and make the issueGroup issues[] as optional.
How can i delete the issue (if there were any) at the same time as I delete the issue group?

Just make issueGroup optional
model IssueGroup {
...
issues Issue[]
...
}
model Issue {
...
issueGroup IssueGroup? #relation(fields: [issueGroupId], references: [id], onDelete: SetNull, onUpdate: Cascade)
issueGroupId String? #db.Uuid
...
}
The actual message from prisma is saying that
Prisma warns users to mark as optional any fields that are included in
a #relation attribute with a SetNull referential action
Your relationship has onDelete: SetNull - so mark it as optional

Related

Referenced objects in DB returning null in GraphQL Query

I have the following database model:
Each Mediablock contains a reference to exactly one UTS object and one Media object.
Each UTS object contains rawText and normalisedText
Each Media object contains a url and a timestamp
My schema.prisma looks like this:
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Mediablock {
id String #id #default(auto()) #map("_id") #db.ObjectId // gen a new, unique id
UTS UTS #relation(fields: [utsId], references: [id])
utsId String #unique #map("uts_id") #db.ObjectId
Media Media #relation(fields: [mediaId], references: [id])
mediaId String #unique #map("media_id") #db.ObjectId
}
model UTS {
id String #id #default(auto()) #map("_id") #db.ObjectId
rawText String
normalisedText String
createdAt DateTime #default(now())
Mediablock Mediablock?
}
// // Mediablocks contain a Video object and connect back to the Mediablock.
// // mediablockId must have #db.ObjectId to match up with Mediablock's id type
model Media {
id String #id #default(auto()) #map("_id") #db.ObjectId
url String
createdAt DateTime #default(now())
Mediablock Mediablock?
}
My resolvers look like this:
const { PrismaClient } = require('#prisma/client');
const prisma = new PrismaClient();
//This resolver retrieves mediabooks from the "mediabooks" array above.
module.exports = {
Query: {
allMediablocks: () => prisma.mediablock.findMany(),
allMedia: () => prisma.media.findMany(),
allUTS: () => prisma.uts.findMany(),
},
};
And my typedefs looks like this:
module.exports = `
type Mediablock {
id: ID!
uts: UTS
media: Media # can be null when the text is generated first
}
type UTS {
id: ID!
rawText: String!
normalisedText: String!
}
type Media {
id: ID!
url: String!
createdAt: String!
}
# The "Query" type is special: it lists all of the available queries that
# clients can execute, along with the return type for each. In this
# case, the "allMediablocks" query returns an array of zero or more Mediablocks (defined above).
type Query {
allMediablocks: [Mediablock]
allMedia: [Media]
allUTS: [UTS]
}
`;
My seed file looks like this:
const { PrismaClient } = require('#prisma/client');
const prisma = new PrismaClient();
const mediaData = [
{
UTS: {
create: {
rawText: 'Welcome',
normalisedText: 'welcome',
},
},
Media: {
create: {
url: 'https://www.youtube.com/watch?v=kq9aShH2Kg4',
createdAt: '2022-09-29T12:00:00.000Z',
}
}
}
];
async function main() {
console.log(`Started seeding ...`);
for (const d of mediaData) {
const mediablock = await prisma.Mediablock.create({
data: d,
});
console.log(`Created Mediablock with id: ${mediablock.id}`);
}
console.log(`\nSeeding complete.`);
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
My problem is that when I attempt to query allMediablocks, I can't get any of the UTS or Media data.
query allMediaBlocks {
allMediablocks {
uts {
normalisedText
}
media {
url
}
}
}
// response
{
"data": {
"allMediablocks": [
{
"uts": null,
"media": null
}
]
}
}
I just get null values for both, when in fact, the database (MongoDB) contains references to both of these objects in other tables.
What am I doing wrong? Are my resolvers incorrect?
Is my schema structured incorrectly for MongoDB?
I've fixed it by changing my schema.prisma to be:
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Mediablock {
id String #id #default(auto()) #map("_id") #db.ObjectId // gen a new, unique id
utsId String #unique #map("uts_id") #db.ObjectId
uts UTS #relation(fields: [utsId], references: [id])
mediaId String #unique #map("media_id") #db.ObjectId
media Media #relation(fields: [mediaId], references: [id])
}
model UTS {
id String #id #default(auto()) #map("_id") #db.ObjectId
rawText String
normalisedText String
createdAt DateTime #default(now())
mediablock Mediablock?
}
// // Mediablocks contain a Video object and connect back to the Mediablock.
// // mediablockId must have #db.ObjectId to match up with Mediablock's id type
model Media {
id String #id #default(auto()) #map("_id") #db.ObjectId
url String
createdAt DateTime #default(now())
mediablock Mediablock?
}
My resolver to be:
const { PrismaClient } = require('#prisma/client');
const prisma = new PrismaClient();
//This resolver retrieves mediabooks from the "mediabooks" array above.
module.exports = {
Query: {
allMediablocks: () => prisma.mediablock.findMany({
include: {
media: true,
uts: true
}
}),
allMedia: () => prisma.media.findMany(),
allUTS: () => prisma.uts.findMany(),
},
};
And my typedefs to be:
module.exports = `
type Mediablock {
id: ID!
uts: UTS
media: Media # can be null when the text is generated first
}
type UTS {
id: ID!
rawText: String!
normalisedText: String!
}
type Media {
id: ID!
url: String!
createdAt: String!
}
# The "Query" type is special: it lists all of the available queries that
# clients can execute, along with the return type for each. In this
# case, the "allMediablocks" query returns an array of zero or more Mediablocks (defined above).
type Query {
allMediablocks: [Mediablock]
allMedia: [Media]
allUTS: [UTS]
}
`;
I also changed the way my seed function works:
const { PrismaClient } = require('#prisma/client');
const prisma = new PrismaClient();
const mediaData = [
{
uts: {
create: {
rawText: 'Welcome',
normalisedText: 'welcome',
createdAt: '2022-09-29T12:00:00.000Z',
},
},
media: {
create: {
url: 'https://www.youtube.com/watch?v=kq9aShH2Kg4',
createdAt: '2022-09-29T12:00:00.000Z',
}
}
}
];
async function main() {
console.log(`Started seeding ...`);
for (const d of mediaData) {
const mediablock = await prisma.Mediablock.create({
data: d,
});
console.log(mediablock);
}
console.log(`\nSeeding complete.`);
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
Now, using the same GraphQL query:
query allMediaBlocks {
allMediablocks {
uts {
normalisedText
}
media {
url
}
}
}
I get the following (desired) response:
{
"data": {
"allMediablocks": [
{
"uts": {
"normalisedText": "welcome"
},
"media": {
"url": "https://www.youtube.com/watch?v=kq9aShH2Kg4"
}
}
]
}
}

How to made a relation to user model using Prisma?

I'm trying to query comments and the user who made the comments. I'm using Prisma to create the schema and Planetscale as a database.
Below you find part of my schema
model User {
id String #id #default(cuid())
name String?
email String? #unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
Comment Comment[]
}
model Comment {
id String #id #default(cuid())
text String
contentId String
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
commenterId String?
commenter User? #relation(fields: [commenterId], references: [id])
}
This is my API route:
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "../../../lib/prisma";
export default async function handle(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "GET") {
return res.status(405).json({ msg: "Method not allowed" });
}
try {
const comments = await prisma.comment.findMany();
return res.status(200).json(comments);
} catch (err) {
console.error(err);
res.status(500).json({ msg: "Something went wrong." });
}
res.end();
}
The end goals is to query the comments and get an object with the commenter, so I can display name and image.
How should my #relation in the model Comment look like to make it possible to include the commenter in the query?
You need to tell prisma if you want to include a related model like this:
await prisma.comment.findmany({
include: {
commenter: true,
},
})
https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries

Relation type in Model is not defined

I am new to Prisma.
I am trying to use Prisma with Nextjs.
But I am facing problems with one to many relations.
I have a schema like below.
model Thread {
id Int #id #default(autoincrement()) #unique
title String
kakikoes Kakiko[]
}
model Kakiko {
id Int #id #default(autoincrement()) #unique
thread Thread #relation(fields: [threadId], references: [id])
threadId Int
}
// Kakiko means "Post" in Japanese.
There is "one to many Relation" which towards "kakiko" from Thread.
I can get datas from DB and I can send datas to browser, But My TS compiler says "TS2339: Property 'kakikoes' does not exist on type 'Thread'."
await prisma.thread.findUnique({
where: {
id: threadId
},
include: {
kakikoes: true
}
})
{thread.kakikoes.map(kakiko => <p>{kakiko.body}</p>)}
I checked type definition, and kakikoes not defined.
/**
* Model Thread
*
*/
export type Thread = {
id: number
itaId: number
title: string
}
How can I solve this?
Thank you.
Added (2022/04/03)
I use Intelij Idea Ultimate.
Type is defined like this.
My code is working correctly on My browser And Nextjs dev Tool doesnt say Any Errors nor Any Warnings.
The model is correct. I have tried it and this is the working solution.
schema.prisma file
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Thread {
id Int #id #unique #default(autoincrement())
title String
kakikoes Kakiko[]
}
model Kakiko {
id Int #id #unique #default(autoincrement())
thread Thread #relation(fields: [threadId], references: [id])
threadId Int
}
main.ts file
import { PrismaClient } from '#prisma/client';
const prisma = new PrismaClient();
async function main() {
// ... you will write your Prisma Client queries here
await prisma.kakiko.create({
data: {
thread: {
create: {
title: 'test',
},
},
},
});
const thread = await prisma.thread.findUnique({
where: {
id: 1,
},
include: {
kakikoes: true,
},
});
console.log('thread', thread);
}
main()
.catch((e) => {
throw e;
})
.finally(async () => {
await prisma.$disconnect();
});
Here is the response
thread { id: 1, title: 'test', kakikoes: [ { id: 1, threadId: 1 } ] }
Attaching the image as well:
I would also suggest restarting your vs code typescript compiler which could cause this issue.

Error retrieving data from DB using typeorm and type-graphql

I'm using type-graphql in conjunction with typeorm, apollo-server-express and postgreSQL. I have a User and a Customer entity in a 1:n relationship, meaning one user can have multiple customers.
I can create users and customers just fine, but when attempting to retrieve the user associated to a customer using Apollo Server playground, I get an error message stating "Cannot return null for non-nullable field Customer.user."
When I check the database, the associated user id on the customer table is definitely not null (see attached image).
query {
customers {
customerId
customerName
user {
userId
}
}
}
Does anyone know what I'm doing wrong?
User.ts
import { Field, ID, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { Customer } from "./Customer";
#ObjectType()
#Entity("users")
export class User extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn("uuid")
userId: string;
#Field()
#Column({ unique: true })
email: string;
#Column({ nullable: false })
password: string;
#Field(() => Customer)
#OneToMany(() => Customer, customer => customer.user)
customers: Customer[]
}
Customer.ts
import { Field, ID, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { User } from "./User";
#ObjectType()
#Entity("customers")
export class Customer extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn("uuid")
customerId: string;
#Field()
#Column()
customerName: string;
#Field(() => User)
#ManyToOne(() => User, user => user.customers)
user: User;
}
CustomerResolver.ts
export class CustomerResolver {
#Query(() => [Customer])
async customers():Promise<Customer[]> {
try {
return await Customer.find();
} catch (error) {
console.log(error);
return error;
}
}
....
Setup / Version
Node: v14.17.0
"apollo-server-express": "^2.24.0",
"type-graphql": "^1.1.1",
"typeorm": "0.2.32"
postgreSQL: 13.2
In your resolver change the find operation like below:
return Customer.find({
relations: ["user"]
});
You should write a #FieldResolver which will fetch customers based on root user data.
https://typegraphql.com/docs/resolvers.html#field-resolvers

Relationships GraphQL

The second week I try to link two collections in the apollo-server-express / MongoDB / Mongoose / GraphQL stack, but I do not understand how. I found a similar lesson with the REST API, what I need is called Relationships. I need this, but in GraphQL
watch video
How to add cars to the User?
I collected the test server, the code is here: https://github.com/gHashTag/test-graphql-server
Help
I have cloned your project and implemented some code and here what I changed to make relationship works. Note, I just did a basic code without validation or advance dataloader just to make sure non-complexity. Hope it can help.
src/graphql/resolvers/car-resolvers.js
import Car from '../../models/Car'
import User from '../../models/User'
export default {
getCar: (_, { _id }) => Car.findById(_id),
getCars: () => Car.find({}),
getCarsByUser: (user, {}) => Car.find({seller: user._id }), // for relationship
createCar: async (_, args) => {
// Create new car
return await Car.create(args)
}
}
src/graphql/resolvers/user-resolvers.js
import User from '../../models/User'
export default {
getUser: (_, { _id }) => User.findById(_id),
getUsers: () => User.find({}),
getUserByCar: (car, args) => User.findById(car.seller), // for relationship
createUser: (_, args) => {
return User.create(args)
}
}
src/graphql/resolvers/index.js
import UserResolvers from './user-resolvers'
import CarResolvers from './car-resolvers'
export default {
User:{
cars: CarResolvers.getCarsByUser // tricky part to link query relation ship between User and Car
},
Car:{
seller: UserResolvers.getUserByCar // tricky part to link query relation ship between User and Car
},
Query: {
getUser: UserResolvers.getUser,
getUsers: UserResolvers.getUsers,
getCar: CarResolvers.getCar,
getCars: CarResolvers.getCars
},
Mutation: {
createUser: UserResolvers.createUser,
createCar: CarResolvers.createCar,
}
}
src/graphql/schema.js
export default`
type Status {
message: String!
}
type User {
_id: ID!
firstName: String
lastName: String
email: String
cars: [Car]
}
type Car {
_id: ID
make: String
model: String
year: String
seller: User
}
type Query {
getUser(_id: ID!): User
getUsers: [User]
getCar(_id: ID!): Car
getCars: [Car]
}
type Mutation {
createUser(firstName: String, lastName: String, email: String): User
// change from _id to seller, due to base on logic _id conflict with CarId
createCar(seller: ID!, make: String, model: String, year: String): Car
}
schema {
query: Query
mutation: Mutation
}
`
src/middlewares.js
import bodyParser from 'body-parser'
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express'
import { makeExecutableSchema } from 'graphql-tools'
import typeDefs from '../graphql/schema'
import resolvers from '../graphql/resolvers'
import constants from './constants'
export const schema = makeExecutableSchema({
typeDefs,
resolvers
})
export default app => {
app.use('/graphiql', graphiqlExpress({
endpointURL: constants.GRAPHQL_PATH
}))
app.use(
constants.GRAPHQL_PATH,
bodyParser.json(),
graphqlExpress(req => ({
schema,
context: {
event: req.event
}
}))
)
}
try to make something like this in your car resolver
export default {
getCar: ({ _id: ownId }, { _id }) =>
Car.findById(ownId || _id);
// here is the rest of your code
You need to add a resolver for the cars field on the User type.
const resolvers = {
Query: {
getUsers: ...
getCars: ...
...
},
Mutation: {
...
},
User: {
cars: ...
}
}