- Published on
Prisma ORM - A Condensed Guide to Mastering Database Management
- Authors
- Name
- K N Anantha nandanan
- @Ananthan2k
In the previous blog, I had discussed about TRPC, a new RPC framework for TypeScript. In this blog, I will be walking through the basics of Prisma ORM, everything you need to know to get started with it and use it in your projects.
What is Prisma?
Prisma is a modern database toolkit that makes database access easy with an auto-generated and type-safe query builder for Node.js and TypeScript. It is a database toolkit that consists of three main components:
- Prisma Client: Auto-generated and type-safe query builder for Node.js and TypeScript
- Prisma Migrate: Declarative data modeling and migrations
- Prisma Studio: GUI to view and edit data in your database
Getting Started
PlanetScale
Prisma supports a wide range of databases, but for this tutorial, we will be using PlanetScale, a MySQL compatible database. PlanetScale is a cloud database platform that provides a fully managed MySQL compatible database service.
To get started, you can sign up for a free account at PlanetScale. Once you have signed up, you can create a new database cluster. You can name the cluster anything you want, and choose the region closest to you.
Dependencies
- Create a project directory and initialize a new npm project.
mkdir prisma-tut
cd prisma-tut
npm init -y
- Install the Prisma CLI as a dev dependency. And other necessary dependencies.
npm i --save-dev prisma typescript ts-node @types/node nodemon
initialize Prisma
- Initialize Prisma in your project directory.
npx prisma init --datasource-provider mysql
As you can see, I provided the datasource as
mysql
. Since in planetscale I configured a MySQL database.
Note: For good intellesense support for prisma in vscode, use the prisma extension.
After that paste the Database URL from PlanetScale to the .env
file.
Prisma Schema
Prisma Schema is a declarative language that allows you to define your application models and their relations. It is used to define the data model of your application.
- So let's start by creating a simple "User" model in the
schema.prisma
file.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma"
}
model User {
id Int @id @default(autoincrement())
name String
}
- Now, we need the database to reflect the changes we made in the schema. So, we need to migrate the database.
npx prisma migrate dev --name init
Note: Funny thing is for PlanetScale, you can't use the migrate command. Because PlanetScale has certain system setup for migration file tracking in their workflow.
So instead use the below command:
npx prisma db push
- Now to be able to use the schema in our code, we need to install the Prisma Client.
npm i @prisma/client
- Now, we can use the Prisma Client to interact with the database.
To regenerate the Prisma Client, run the following command:
npx prisma generate
CRUD Operations
Create
- Let's create a new user in the database.
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
const user = await prisma.user.create({
data: {
name: 'John Doe',
},
})
console.log(users)
}
main()
.catch((e) => {
console.error(e.message)
})
.finally(async () => {
await prisma.$disconnect()
})
This will create a new user in the database with the name "John Doe".
Read
- Let's say we want to get all the users from the database.
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
const users = await prisma.user.findMany()
console.log(users)
}
This will return all the users from the database.
Model Fields
Field Attributes
There are a lot of different types of field attributes that you can use in your models. They are:
- @id: This field is used to define the primary key of the model.
- @default: This field is used to define the default value of the field.
- @unique: This field is used to define the field as unique.
- @map: This field is used to define the name of the field in the database.
- @relation: This field is used to define the relation between two models.
- @ignore: This field is used to ignore the field in the database.
- @updatedAt: This field is used to define the field as updated at.
- @uuid: This field is used to define the field as uuid.
- @@index: This field is used to define the index of the field in the database. This is a block-level attribute.
- @@unique: This field is used to define the field as unique in the database. This is a block-level attribute.
Field Type
There are a lot of different types of field types that you can use in your models. They are:
String
: This field is used to define a string field.Int
: This field is used to define an integer field.Float
: This field is used to define a float field.Boolean
: This field is used to define a boolean field.DateTime
: This field is used to define a date time field.Json
: This field is used to define a json field.Bytes
: This field is used to define a bytes field.Decimal
: This field is used to define a decimal field.BigInt
: This field is used to define a big integer field.Enum
: This field is used to define an enum field.Unsupported
: This field is used to define an unsupported field.
There are also different varients for each of these types based on the database you are using. You can check them out in the Prisma Docs.
Relations
One-to-Many
- Let's say we want to create a new model called "Post" which has a one-to-many relation with the "User" model.
model User {
id String @id @default(uuid())
name String
email String
isAdmin Boolean
preferences Json
writtenPosts Post[] @relation("writtenPosts")
favoritePosts Post[] @relation("favoritePosts")
}
model Post {
id String @id @default(uuid())
rating Float
createdAt DateTime @default(now())
updatedAt DateTime
author User @relation("writtenPosts", fields: [authorId], references: [id], onDelete: Cascade)
authorId String
favoritedBy User? @relation("favoritePosts", fields: [favoritedById], references: [id], onDelete: Cascade)
favoritedById String?
@@index([authorId], name: "authorId")
@@index([favoritedById], name: "favoritedById")
}
-
Okay, so a lot of things are going on here. Let's break it down.
-
First, we have defined a new model called "Post". It has a one-to-many relation with the "User" model. So, we have defined two fields in the "Post" model, "author" and "favoritedBy". Both of these fields have a relation with the "User" model. The "author" field has a relation with the "User" model with the name "writtenPosts", and the "favoritedBy" field has a relation with the "User" model with the name "favoritePosts".
Note: Since we are using two relations with the same model, we need to provide a name for each of the relations. That is why we have provided the
writtenPosts
andfavoritePosts
names for the relations to avoid ambiguity.
- So if you want to create a one-to-many relation from
User
toPost
, the format you want to follow is:
author User @relation("writtenPosts", fields: [authorId], references: [id], onDelete: Cascade)
authorId String
- And since we are using
relationMode = "prisma"
in thedatasource
block, we need to provide index for the fields that are used in the relation.
@@index([authorId], name: "authorId")
-
The
?
in thefavoritedBy
field means that the field is optional. And[]
in thewrittenPosts
field means that the field is an array. -
onDelete: Cascade
means that if the user is deleted, all the posts written by the user will also be deleted.
Many-to-Many
- Let's say we want to create a new model called "Category" which has a many-to-many relation with the "Post" model.
model Category {
id String @id @default(uuid())
name String
posts Post[]
}
model Post {
id String @id @default(uuid())
....
....
categories Category[]
}
- Constructing a many-to-many relation is pretty simple. You just need to add the model name in the field type. And Prisma will automatically create a many-to-many relation between the two models. That is by creating a join table between the two models.
One-to-One
- Let's say we want to create a new model called "userPreference" which has a one-to-one relation with the "User" model.
model User {
id String @id @default(uuid())
name String
email String
isAdmin Boolean
writtenPosts Post[] @relation("writtenPosts")
favoritePosts Post[] @relation("favoritePosts")
userPreference UserPreference? // one-to-one relation
}
model UserPreference {
id String @id @default(uuid())
emailUpdates Boolean
user User @relation(fields: [userId], references: [id]) // one-to-one relation
userId String @unique // one-to-one relation
}
- To create a one-to-one relation, you just need to provide the relation in
User
model andUserPreference
model. Such that inUser
unlike in one-to-many relation, you don't need to provide the field as an array. And inUserPreference
model, you need to provide theuserId
field asunique
.
Enum
Enum is a special type of datasource generator that allows you to define a set of named constants. Like let's say we know that the user can have only two roles, "ADMIN" and "USER". So, we can define an enum for the roles.
enum Role {
ADMIN
USER
}
model User {
id Int @id @default(autoincrement())
name String
role Role @default(USER)
}
Create Operations
To create a new record in the database, you can use the create
method.
const user = await prisma.user.create({
data: {
name: 'Ananthan',
email: 'ananthan@gmail.com',
age: 20,
isAdmin: true,
},
})
console.log(user)
- With prisma, you can also use the relations even though those models are not created yet. Prisma will automatically create the relations for you.
const user = await prisma.user.create({
data: {
name: 'Ananthan',
email: 'ananthan@gmail.com',
age: 20,
isAdmin: true,
userPreference: {
// one-to-one relation
create: {
emailUpdates: true,
},
},
},
})
Inlcude
- If you want to view returned relations, you need to use the
include
method.
const user = await prisma.user.create({
data: {
name: 'Ananthan',
email: 'ananthan@gmail.com',
age: 20,
isAdmin: true,
userPreference: {
create: {
emailUpdates: true,
},
},
},
include: {
userPreference: true,
},
})
The output would look something like this:
{
"id": "ce9f1204-04df-4094-b055-a7d308ce4fee",
"name": "Ananthan",
"age": 20,
"email": "ananthan@gmail.com",
"role": "BASIC",
"isAdmin": true,
"userPreference": {
"id": "8b8c1bea-99e8-4937-8278-8f4e65e9c24b",
"emailUpdates": true,
"userId": "ce9f1204-04df-4094-b055-a7d308ce4fee"
}
}
Select
- If you want to view only specific fields, you need to use the
select
method.
const user = await prisma.user.create({
data: {
name: 'Ananthan',
email: 'ananthan@gmail.com',
age: 20,
isAdmin: true,
userPreference: {
create: {
emailUpdates: true,
},
},
},
select: {
name: true,
userPreference: true,
},
})
console.log(user)
The output would look something like this:
{
"name": "Ananthan",
"userPreference": {
"id": "8b8c1bea-99e8-4937-8278-8f4e65e9c24b",
"emailUpdates": true,
"userId": "ce9f1204-04df-4094-b055-a7d308ce4fee"
}
}
NOTE: You can only use either
include
orselect
method. You can't use both at the same time.
CreateMany
- Let's suppose in our application, we want to create multiple users at once. We can use the
createMany
method.
const user = await prisma.user.createMany({
data: [
{
name: 'Ananthan',
email: 'ananthan@gmail.com',
age: 20,
isAdmin: true,
},
{
name: 'Raj',
email: 'rajive@gmail.com',
age: 20,
isAdmin: false,
},
],
})
console.log(user)
Read Operations
FindUnique
- To find that one specific record in the database, you can use the
findUnique
method.
const user = await prisma.user.findUnique({
where: {
id: 'ce9f1204-04df-4094-b055-a7d308ce4fee',
},
})
NOTE: If there is no matching record, it will return
null
.
- Now for our
User
mode, we defined the unique fields as a pair ofage
andname
. So, we can use that to find the record.
const user = await prisma.user.findUnique({
where: {
age_name: {
age: 20,
name: 'Ananthan',
},
},
})
FindFirst
- We use this method to find the first matching record in the database.
const user = await prisma.user.findFirst({
where: {
name: 'Ananthan',
},
})
FindMany
- We use this method to find all the matching records in the database.
const user = await prisma.user.findMany({
where: {
name: 'Ananthan',
},
})
- You can also use the
distinct
method along with findMany to get the distinct records.
const user = await prisma.user.findMany({
where: {
name: 'Ananthan',
},
distinct: ['name', 'age'],
})
console.log(user)
- What this will do is, it will return only the distinct records with the name and age fields.
Pagination
- You can also use pagination with the
findMany
method.
const user = await prisma.user.findMany({
where: {
name: 'Ananthan',
},
skip: 1,
take: 2,
})
console.log(user)
orderBy
We can also do sorting with the findMany
method.
const user = await prisma.user.findMany({
where: {
name: 'Ananthan',
},
orderBy: {
age: 'asc',
},
})
Advanced Queries
IN Query
- Let's say we want to find all the users with the name "Ananthan" or "Raj". We can use the
in
query.
const user = await prisma.user.findMany({
where: {
name: {
in: ['Ananthan', 'Raj'],
},
},
})
- To get the users not in the list, we can use the
notIn
query.
const user = await prisma.user.findMany({
where: {
name: {
notIn: ['Ananthan', 'Raj'],
},
},
})
Comparison Queries
- Similarly, we can also use
lt
,lte
,gt
,gte
queries. Assuming here we are using theage
field.
const user = await prisma.user.findMany({
where: {
age: {
lt: 20,
},
},
})
String Query
- Let's say we want to find all users that have the pattern of
@gmail.com
in their email. We can use thecontains
query.
const user = await prisma.user.findMany({
where: {
email: {
contains: '@gmail.com',
},
},
})
- Similarly, we can also use
startsWith
andendsWith
queries.
Logical Queries
AND Query
- Let's say we want to find all users with the name "Ananthan" and age less than 20. We can use the
AND
query.
const user = await prisma.user.findMany({
where: {
AND: [
{
name: 'Ananthan',
},
{
age: {
lt: 20,
},
},
],
},
})
OR Query
- Let's say we want to find all users with the name "Ananthan" or age less than 20. We can use the
OR
query.
const user = await prisma.user.findMany({
where: {
OR: [
{
name: 'Ananthan',
},
{
age: {
lt: 20,
},
},
],
},
})
NOT Query
- Let's say we want to find all users with the name "Ananthan" and age not less than 20. We can use the
NOT
query.
const user = await prisma.user.findMany({
where: {
NOT: [
{
name: 'Ananthan',
},
{
age: {
lt: 20,
},
},
],
},
})
Relationship Queries
- Let's say we want to query on our
User
model for the relationwrittenPosts
. In this case for example:
const user = await prisma.post.findMany({
where: {
writtenPosts: {
every: {
title: 'My First Post',
},
},
},
})
-
This will return all the users who have written a post with the title "My First Post".
-
Similarly, we can also use
some
andnone
queries.
Update Operations
- To update a record in the database, you can use the
update
method.
const user = await prisma.user.update({
where: {
email: 'ananthan@gmail.com',
},
data: {
email: 'henix@gmail.com',
},
})
console.log(user)
Here, we are updating the email of the user with the email ananthan@gmail.com
to henix@gmail.com
. And the update
method will return the updated
record.
-
You can also chain the different query methods I have mentioned above to update the required record. So go fancy with it. 😎
-
Some special methods in update are:
-
increment
: This method is used to increment the value of a field by a certain amount. -
decrement
: This method is used to decrement the value of a field by a certain amount. -
multiply
: This method is used to multiply the value of a field by a certain amount. -
divide
: This method is used to divide the value of a field by a certain amount.
Connect
- Let's assume that we already have a
Post
record in the database. And we want to connect that record to the user. We can use theconnect
method.
const post = await prisma.post.create({
data: {
title: 'Prisma makes databases easy',
averageRating: 4.0,
author: {
connectOrCreate: {
where: {
email: 'ronjacob@gmail.com',
},
create: {
email: 'ronjacob@gmail.com',
name: 'Ron Jacob',
age: 22,
isAdmin: true,
},
},
},
},
include: {
author: true,
},
})
- Here, we are connecting the
Post
record to the user with the emailronjacob@gmail.com
if the user exists. If the user doesn't exist, we are creating a new user with the emailronjacob@gmail.com
and connecting thePost
record to the newly created user.
NOTE: You can also use the
disconnect
method to disconnect the record during update.
Delete Operations
Delete
- To delete a record in the database, you can use the
delete
method.
const user = await prisma.user.delete({
where: {
email: 'ananthan@gmail.com',
},
})
In this example, we are deleting the user with the email ananthan@gmail.com
.
DeleteMany
- To delete multiple records in the database, you can use the
deleteMany
method.
const user = await prisma.user.deleteMany({
where: {
age: {
lt: 20,
},
},
})
- Here we are deleting all the users with the age less than 20.
Conclusion
So, that's it for this blog. I have covered all the basics of Prisma ORM after learning it for a week. I believe this blog will help you get started with Prisma ORM and use it in your projects. If you want to learn more about Prisma ORM, you can check out the Prisma Docs.