Schema updates
Objectives
Section titled “Objectives”In this section, we will begin the process of adding articles to the app, associating them with users and displaying some raw article data on the front-end. This will involve:
- Updating the Prisma schema found at
prisma/schema.prisma
- Updating the seed data generated in
prisma/seed.ts
to populate our database with some dummy articles - Changing the file structure of the project to add ‘articles’ as well as the ‘notes’ which already exist
- Display some raw, unstyled article data in the
/news/$category.tsx
route file that we created in a previous lesson
🚀 Let’s get started! 🚀
The schema file
Section titled “The schema file”A Prisma schema is a file that serves as a blueprint for your database, defining the structure of your data.
Open the Prisma schema for the project which can be found at prisma/schema.prisma
.
In the code snippet below is a copy of the User
, Note
and NoteImage
models from the project.
Click on the collapsed lines to expand them one at a time, and look carefully at the links between them.
Can you see how they are related to each other? 🧐
model User {14 collapsed lines
id String @id @default(cuid()) email String @unique username String @unique name String?
createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
image UserImage? password Password? notes Note[] roles Role[] sessions Session[] connections Connection[]}
model Note {16 collapsed lines
id String @id @default(cuid()) title String content String
createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade) ownerId String
images NoteImage[]
// non-unique foreign key @@index([ownerId]) // This helps our order by in the user search a LOT @@index([ownerId, updatedAt])}
model NoteImage {13 collapsed lines
id String @id @default(cuid()) altText String? contentType String blob Bytes
createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade, onUpdate: Cascade) noteId String
// non-unique foreign key @@index([noteId])}
Let’s take each of the models in turn:
- A user has an
id
,email
,username
andname
field. Theid
,email
andusername
fields are required and unique. This means that they are all mandatory and must be unique for each user; no two users can share the same email address, for example. - A user can also have an
image
and apassword
. Note that theimage
andpassword
fields are optional. We can tell this because they are defined with a?
at the end of the type declaration. - A user can have many
notes
,roles
,sessions
andconnections
.
model User { id String @id @default(cuid()) email String @unique username String @unique name String?
createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
image UserImage? password Password? notes Note[] roles Role[] sessions Session[] connections Connection[]}
- A note must have an
id
,title
andcontent
field. Theid
field is the primary key and is generated automatically using thecuid()
function. - A note can have many
images
. - A note belongs to a
user
(theowner
field). This is known as a one-to-many relationship, as a user can have manynotes
, but anote
can only belong to oneuser
. - The
ownerId
field is a foreign key that references theid
field of theUser
model. - The
ownerId
field and theupdatedAt
field are indexed to improve performance. We can tell this because of the@@index
directive at the end of the model.
model Note { id String @id @default(cuid()) title String content String
createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade) ownerId String
images NoteImage[]
// non-unique foreign key @@index([ownerId]) // This helps our order by in the user search a LOT @@index([ownerId, updatedAt])}
NoteImage
Section titled “NoteImage”- A
noteImage
belongs to anote
. This is known as a one-to-many relationship, as anote
can have manyimages
, but animage
can only belong to onenote
. - The
noteId
field is a foreign key that references theid
field of theNote
model. - The
noteId
field is indexed to improve performance. We can tell this because of the@@index
directive at the end of the model.
model NoteImage { id String @id @default(cuid()) altText String? contentType String blob Bytes
createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade, onUpdate: Cascade) noteId String
// non-unique foreign key @@index([noteId])}
Creating an Article
model
Section titled “Creating an Article model”Our goal is to add articles to the app. To do this, we need to create:
- an
Article
model in the Prisma schema. - an
ArticleImage
model to store images associated with articles.
Many of the features that we need for articles are already present in the Note
model.
We will use this as a blueprint for the Article
model, and make the necessary changes to reflect the differences between notes and articles.
-
Add an
Section titled “Add an Article and ArticleImage model”Article
andArticleImage
modelPlace the code below directly below the
NoteImage
model in yourschema.prisma
file.prisma/schema.prisma model NoteImage {13 collapsed linesid String @id @default(cuid())altText String?contentType Stringblob BytescreatedAt DateTime @default(now())updatedAt DateTime @updatedAtnote Note @relation(fields: [noteId], references: [id], onDelete: Cascade, onUpdate: Cascade)noteId String// non-unique foreign key@@index([noteId])}model Article {id String @id @default(cuid())title Stringcontent StringcreatedAt DateTime @default(now())updatedAt DateTime @updatedAtowner User @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade)ownerId Stringimages NoteImage[]// non-unique foreign key@@index([ownerId])// This helps our order by in the user search a LOT@@index([ownerId, updatedAt])}model ArticleImage {id String @id @default(cuid())altText String?contentType Stringblob BytescreatedAt DateTime @default(now())updatedAt DateTime @updatedAtnote Note @relation(fields: [noteId], references: [id], onDelete: Cascade, onUpdate: Cascade)noteId String// non-unique foreign key@@index([noteId])} -
Update
Section titled “Update User and Article”User
andArticle
We now need to update the existing
User
article. You will find this around line 13.Add a relationship in the
User
model to theArticle
model like so:prisma/schema.prisma model User {7 collapsed linesid String @id @default(cuid())email String @uniqueusername String @uniquename String?createdAt DateTime @default(now())updatedAt DateTime @updatedAtimage UserImage?password Password?notes Note[]articles Article[]roles Role[]sessions Session[]connections Connection[]}Next, we will update the
Article
model, around line 66.Edit the code below to reference the
ArticleImage
model instead of theNoteImage
model.prisma/schema.prisma model Article {5 collapsed linesid String @id @default(cuid())title Stringcontent StringcreatedAt DateTime @default(now())updatedAt DateTime @updatedAtowner User @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade)ownerId Stringimages NoteImage[]images ArticleImage[]// non-unique foreign key@@index([ownerId])// This helps our order by in the user search a LOT@@index([ownerId, updatedAt])}Don’t worry that
images ArticleImage[]
is still underlined red - there’s one last change to make. -
Update the
Section titled “Update the ArticleImage model”ArticleImage
modelThe
ArticleImage
model is a little more complex, as it references theArticle
model as the parent.In other words, each
ArticleImage
belongs to a singleArticle
that it needs to reference via a foreign key.We therefore need to make several changes here:
prisma/schema.prisma model ArticleImage {id String @id @default(cuid())altText String?contentType Stringblob BytescreatedAt DateTime @default(now())updatedAt DateTime @updatedAtnote Note @relation(fields: [noteId], references: [id], onDelete: Cascade, onUpdate: Cascade)noteId Stringarticle Article @relation(fields: [articleId], references: [id], onDelete: Cascade, onUpdate: Cascade)articleId String// non-unique foreign key@@index([noteId])@@index([articleId])} -
Additional
Section titled “Additional Article fields”Article
fieldsFinally, we need to add some additional fields to the
Article
model to indicate whether an article is published or draft, and when it was published:prisma/schema.prisma model Article {id String @id @default(cuid())title Stringcontent StringisPublished Boolean @default(false)publishedAt DateTime?createdAt DateTime @default(now())updatedAt DateTime @updatedAt9 collapsed linesowner User @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade)ownerId Stringimages ArticleImage[]// non-unique foreign key@@index([ownerId])// This helps our order by in the user search a LOT@@index([ownerId, updatedAt])}
Default values and optional fields
Section titled “Default values and optional fields”Notice that the isPublished
field is a Boolean
type with a default value of false
. Similarly, the publishedAt
field is a DateTime
type that is optional.
Why do you think the publishedAt
field is optional, and isPublished
has an initial value of false
? 🤔
-
The
isPublished
field is aBoolean
type because it can only have two values:true
orfalse
.It has a default value of
false
because an article is usually a draft when it is first created. When the article is published, theisPublished
field will be set totrue
. -
The
publishedAt
field is optional because an article might be a draft and not yet published. In this case, thepublishedAt
field would benull
.
Add an ArticleCategory
model
Section titled “Add an ArticleCategory model”Finally, we want to arrange the news articles into categories, such as ‘Technology’, ‘Entertainment’ and ‘Business’.
To do this, we will create an ArticleCategory
model and link it to the Article
model.
-
Create an
ArticleCategory
model in the Prisma schema.This can be placed directly below the
ArticleImage
model in the Prisma schema.prisma/schema.prisma model ArticleImage {13 collapsed linesid String @id @default(cuid())altText String?contentType Stringblob BytescreatedAt DateTime @default(now())updatedAt DateTime @updatedAtarticle Article @relation(fields: [articleId], references: [id], onDelete: Cascade, onUpdate: Cascade)articleId String// non-unique foreign key@@index([articleId])}model ArticleCategory {id String @id @default(cuid())name Stringslug String @uniquecreatedAt DateTime @default(now())updatedAt DateTime @updatedAtarticles Article[]} -
Link the
Article
model to the newArticleCategory
model with the code below:prisma/schema.prisma model Article {id String @id @default(cuid())title Stringcontent StringisPublished Boolean @default(false)publishedAt DateTime?createdAt DateTime @default(now())updatedAt DateTime @updatedAtowner User @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade)ownerId Stringcategory ArticleCategory? @relation(fields: [categoryId], references: [id], onDelete: Cascade, onUpdate: Cascade)categoryId String?images ArticleImage[]// non-unique foreign key@@index([ownerId])@@index([categoryId])// This helps our order by in the user search a LOT@@index([ownerId, updatedAt])} -
Remember to save your changes to the Prisma schema file.
Migrate changes to database
Section titled “Migrate changes to database”Now that we’ve made all the necessary changes to the Prisma schema, we need to apply these changes to the database.
To do this, run the following command in your terminal:
npx prisma migrate dev --name "add-article-models"
Once the process has finished, you should see a a new migration file has appeared inside your prisma/migrations
folder:
Check database changes
Section titled “Check database changes”Let’s check the database to confirm the changes have been applied. Run the following command in your terminal:
npx prisma studio
This will open the Prisma Studio interface in your browser. You should see the Article
, ArticleImage
and ArticleCategory
models at the top of your models list:
Currently, these tables will be empty, as we haven’t added any seed data yet. We’ll do that in the next section.
Summary
Section titled “Summary”In this section, we have:
- Updated the Prisma schema to include an
Article
model and anArticleImage
model. - Updated the
User
model to include anarticles
field that references theArticle
model. - Updated the
Article
model to include anisPublished
,publishedAt
andcategory
field. - Updated the
ArticleImage
model to reference theArticle
model instead of theNote
model. - Migrated these changes to the database.
What’s next?
Section titled “What’s next?”In the next tutorial, we will make some adjustments to the project structure to reflect the changes we’ve made to the Prisma schema.