Prisma
GraphQL’s SDL syntax requires an extra ! when a field is required. Remember: schema.prisma syntax requires an extra ? character when a field is not required.
Installation
Locally install the prisma cli:
npm i -D prisma
Install the prisma client
npm i @prisma/client
Initialize
npx prisma init
This also adds DATABASE_URL to the .env file.
Create the database and then run:
npx prisma generate
to generate the client inside the node_modules folder.
schema.prisma
Create the schema file and prisma folder:
Example:
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
binaryTargets = "native"
}
model Post {
id Int @id @default(autoincrement())
title String
body String
comments Comment[]
createdAt DateTime @default(now())
}
model Contact {
id Int @id @default(autoincrement())
name String
email String
message String
createdAt DateTime @default(now())
}
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
hashedPassword String
salt String
resetToken String?
resetTokenExpiresAt DateTime?
}
model Comment {
id Int @id @default(autoincrement())
name String
body String
post Post @relation(fields: [postId], references: [id])
postId Int
createdAt DateTime @default(now())
}
Relation
Comment has a relation to Post:
-
post which has a type of Post and a special @relation keyword that tells Prisma how to connect a Comment to a Post. In this case the field postId references the field id in Post
-
postId is just a regular Int column which contains the id of the Post that this comment is referencing
This gives us a classic database model:
┌───────────┐ ┌───────────┐
│ Post │ │ Comment │
├───────────┤ ├───────────┤
│ id │───┐ │ id │
│ title │ │ │ name │
│ body │ │ │ body │
│ createdAt │ └──<│ postId │
└───────────┘ │ createdAt │
└───────────┘
When you query for a Comment using Prisma you can get access to the attached Post using that name:
db.comment.findUnique({ where: { id: 1 }}).post()
Prisma also added a convenience comments field to Post which gives us the same capability in reverse:
db.post.findUnique({ where: { id: 1 }}).comments()
Prisma and the N+1 Problem
If you have any experience with database design and retrieval you may have noticed this method presents a less than ideal solution: for every post that’s found, you need to perform an additional query just to get the user data associated with that post, also known as the N+1 problem. This is just due to the nature of GraphQL queries: each resolver function really only knows about its own parent object, nothing about potential children.
There have been several attempts to work around this issue. A simple one that includes no extra dependencies is to remove this field resolver and simply include user data along with any post you retrieve from the database:
export const post = ({ id }) => {
return db.post.findUnique({
where: { id },
include: {
user: true
}
})
}
This may or may not work for you: you are incurring the overhead of always returning user data, even if that data wasn’t requested in the GraphQL query. In addition, this breaks further nesting of queries: what if you wanted to return the user for this post, and a list of all the other posts IDs that they created?
post {
id
title
body
createdAt
user {
name
posts {
id
}
}
}
This query would now fail because you only have post.user available, not post.user.posts.
findUnique() vs. findFirst()
-
findUnique() requires that any attributes in the where clause have unique indexes, which id does, but userId does not.
-
findFirst() allows you to put whatever you want in the where, which may return more than one record, but Prisma will only return the first of that set. In this case we know there’ll always only be one, because we’re selecting by id in addition to userId.