One-to-One Relationships
Loading "Database Design"
Run locally for transcripts
π¨βπΌ We still need to deal with the Note images. And we're also going to add
images to the users as well. Before we do, Olivia the Owl would like to have a
word with you about data modeling...
π¦ One thing you want to think about when you're building out your data model
is... the future.
CAUTION: The future is impossible to predict. All you really can
reasonably predict about the future is that things will change. But you don't
know how. That said, it can be useful to build things in such a way that
optimizes for change provided it doesn't make things sub-optimal for the
present.
Consider, for example, that you are building a house. You're considering what to
do about the pool you want one day. You know you don't want it now, but you may
want to add one in the future. With that in mind, you may think about where the
pool should go, and you decide to avoid planting trees there. Depending on how
confident you are that you will get the pool you may even consider placing the
plumbing for the pool in the ground before you pour the foundation so you don't
have to pay the cost of digging things up later. You just need to weigh the
costs of doing that now vs. the cost of doing it later and the likelihood you'll
actually get the pool.
For our notes to have images, we just need a place to store the bytes for the
image, its content type, and the alt text. It's not much data. But if you think
about a note taking app, it's reasonable to assume folks will want to upload
more than just images to their notes. Maybe they'll want to upload a
csv
or a
pdf
or any number of other types of files.So we should think about modeling things in such a way that it makes supporting
these other file types possible without complicating our data model in the
present or having a difficult migration in the future.
π¨βπΌ Thanks for that Olivia.
π¦ Oh, one other thing... On the subject of images... We'll be storing our
images directly in the database. Turns out, in some cases,
serving files from SQLite can be 35% faster than the file system
(and can be more space efficient as well)! Even in situations where that's not
the case, it's certainly not much slower. So while you definitely can bring in
another service to manage storing the images for you, or store it to a
persistent volume, starting out with the images in the database is a perfectly
reasonable approach for many applications.
π¨βπΌ Ok, great. Thanks Olivia! So, with that said, instead of making a single
Image
model and using that for both the User and Note models, we're going to
have UserImage
and NoteImage
models that each hold the content type, file
bytes, and alt text.This may feel like we're duplicating code and schema, which we definitely are,
but if you consider the future possibilities, it will be much easier for us to
add a
NotePDF
model later without impacting other models than to try and get
all polymorphic with a single generic File
model (polymorphism and databases
don't mix well).Here's a visualized model of this:
And here's what we're going to do instead:
With these new models in place, we can connect them to the
Note
and User
models. So we'll have the following relationships:User
->UserImage
: one-to-oneNote
->NoteImage
: one-to-many
Don't forget that the Prisma VSCode extension, can automatically capitalize
the property names it adds which is annoying. Just double check that all
properties are lower-case.
The emoji should be in the schema file to help guide you through this.
π° Remember, if you get stuck on the syntax, remember you can check
the Diff Tab to check your work against the
solution.
Once you've got the model in a good place, then let's push that to the database:
npx prisma db push
And now let's check out the studio:
npx prisma studio
Now we want to create a new image/file. The tricky bit here is Prisma studio
represents files as base64 encoded strings which is fine, I even prepared this
base64 image for you:
data:image/jpeg;base64,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
Sadly, I couldn't find a way to create one-to-one required relationships in the
Prisma studio. So it's time to move to some code!
Go ahead and stick this in there:
import { PrismaClient } from '@prisma/client'
import fs from 'node:fs'
const prisma = new PrismaClient()
const firstNote = await prisma.note.findFirst()
if (!firstNote) {
throw new Error('You need to have a note in the database first')
}
await prisma.note.update({
where: { id: firstNote.id },
data: {
images: {
create: [
{
altText: 'an adorable koala cartoon illustration',
contentType: 'image/png',
blob: await fs.promises.readFile(
'./tests/fixtures/images/kody-notes/cute-koala.png',
),
},
{
altText: 'a cartoon illustration of a koala in a tree eating',
contentType: 'image/png',
blob: await fs.promises.readFile(
'./tests/fixtures/images/kody-notes/koala-eating.png',
),
},
],
},
},
})
We'll dive deeper into this later...
Next, let's run it. Because this is a TypeScript file, you'll need to use
tsx
instead of node
:npx tsx ./prisma/seed.ts
Great, with that done, now open up the studio:
npx prisma studio
You should have your note with the two images and two files in there now.
Huzzah! We'll get further into the seeding capability later, but this is a good
verification that our model is working.