Schema updates
Objectives
Section titled “Objectives”In this section, we will look at how articles are configured in the app database and add some extra features to them. This will involve:
- Understanding the Prisma schema found at
prisma/schema.prisma - Adding some extra fields to the article table that will help us to publish and unpublish them on the site
- Display some raw, unstyled article data in the
/news/$category.tsxroute 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 a Prisma schema, you define your data models, each representing a table in the database. For each model, you specify the fields (which correspond to columns in the database table), their types, and any constraints or relations they might have.
In the code snippet below is a copy of the User, Article and ArticleImage 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? articles Article[] roles Role[] sessions Session[] connections Connection[]}
model Article {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 ArticleImage[]
// non-unique foreign key @@index([ownerId]) // This helps our order by in the user search a LOT @@index([ownerId, updatedAt])}
model ArticleImage {13 collapsed lines
id String @id @default(cuid()) altText String? contentType String blob Bytes
createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade, onUpdate: Cascade) articleId String
// non-unique foreign key @@index([articleId])}Let’s take each of the models in turn:
- A user has an
id,email,usernameandnamefield. Theid,emailandusernamefields 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
imageand apassword. Article that theimageandpasswordfields are optional. We can tell this because they are defined with a?at the end of the type declaration. - A user can have many
articles,roles,sessionsandconnections.
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? articles Article[] roles Role[] sessions Session[] connections Connection[]}Article
Section titled “Article”- An article must have an
id,titleandcontentfield. Theidfield is the primary key and is generated automatically using thecuid()function. - An article can have many
images. - An article belongs to a
user(theownerfield). This is known as a one-to-many relationship, as a user can have manyarticles, but anarticlecan only belong to oneuser. - The
ownerIdfield is a foreign key that references theidfield of theUsermodel. - The
ownerIdfield and theupdatedAtfield are indexed to improve performance. We can tell this because of the@@indexdirective at the end of the model.
model Article { 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 ArticleImage[]
// non-unique foreign key @@index([ownerId]) // This helps our order by in the user search a LOT @@index([ownerId, updatedAt])}ArticleImage
Section titled “ArticleImage”- An
articleImagebelongs to anarticle. This is known as a one-to-many relationship, as anarticlecan have manyimages, but animagecan only belong to onearticle. - The
articleIdfield is a foreign key that references theidfield of theArticlemodel. - The
articleIdfield is indexed to improve performance. We can tell this because of the@@indexdirective at the end of the model.
model ArticleImage { id String @id @default(cuid()) altText String? contentType String blob Bytes
createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade, onUpdate: Cascade) articleId String
// non-unique foreign key @@index([articleId])}Additional Article fields
Section titled “Additional Article fields”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.
1. Add isPublished and publishedAt fields
Section titled “1. Add isPublished and publishedAt fields”Carefully add the lines highlighted in green below to the Article model:
model Article { id String @id @default(cuid()) title String content String isPublished Boolean @default(false)
publishedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt
9 collapsed lines
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade, onUpdate: Cascade) ownerId String
images 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
isPublishedfield is aBooleantype because it can only have two values:trueorfalse.It has a default value of
falsebecause an article is usually a draft when it is first created. When the article is published, theisPublishedfield will be set totrue. -
The
publishedAtfield is optional because an article might be a draft and not yet published. In this case, thepublishedAtfield would benull.
2. Add an ArticleCategory model
Section titled “2. 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
ArticleCategorymodel in the Prisma schema.This can be placed directly below the
ArticleImagemodel 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
Articlemodel to the newArticleCategorymodel 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 studioThis 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
Articlemodel and anArticleImagemodel. - Updated the
Usermodel to include anarticlesfield that references theArticlemodel. - Updated the
Articlemodel to include anisPublished,publishedAtandcategoryfield. - Updated the
ArticleImagemodel to reference theArticlemodel instead of theArticlemodel. - Migrated these changes to the database.
Assignment
📄 Assignment documentation
Section titled “📄 Assignment documentation”We need to update your assignment to document this new feature. 🚀
- Explain how you updated the
Articlemodel to include anisPublished,publishedAtandcategoryfield. - Explain how you 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.