- Lab
- Core Tech

Guided: Implementing Prisma in a Next.js Finance Application
In this lab, you will setup a Prisma database to persist data within an existing Next.js finance application. This will include seeding data, performing a migration, and writing queries to perform CRUD operations against the database. By the end of this lab, you will have a deeper understanding of Prisma Client, Prisma Schema, and Prisma's querying language.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome to the Guided: Implementing Prisma in a Next.js Finance Application lab.
Throughout this lab, you will become familiar with how to integrate a Prisma database to persist data for an existing finance application. This lab will cover how to set up a Prisma database, specify a schema, build migrations, seed tables, and use the database within the codebase. This lab will use a sqlite database because it is lightweight and easy to configure.
To start, you have been given a Next.js finance application. You can launch the application by entering
npm run dev
in the Terminal. The application has a Home page that displays all transactions in a table and highlights expense transactions in red and income transactions in green. There is also an Income page and an Expenses page where you can create, edit, and delete the corresponding transactions. Lastly there is a Plan page where you can create, edit, and delete budget plans. Currently the application reads data from JSON files in theapp/lib/
directory. The application does not have support for creating, updating, or deleting any data.You will be responsible for creating the
transaction
,plan
,cadence
, andtype
tables, seeding data for development, and using Prisma to perform database queries to complete the CRUD (create, read, update, and delete) functionality the application needs.You do not need any previous experience with Next.js or Prisma to complete this lab. This lab assumes you have basic experience with Javascript, JSX, React, and Database Architecture. Details on Next.js and Primsa will be given to provide context for your implementations in each step.
There is a
solution
directory with correspondingstep
directories that you can refer to if you are stuck or want to check your implementations at any time. Keep in mind that the solution provided in this directory is not the only solution, so it is acceptable for you to have a different solution so long as the application functions as intended. The solution provided will also be for the entire step.
Activity
Launch the application by entering
npm run dev
in the Terminal. The application can be seen in the Web Browser tab atlocalhost:3000
. Take a moment to become familiar with the different pages. The starting application should look like the picture below:Remember: throughout the lab, code changes within the
app
directory and to the database will be live on the website after refreshing if the application is running. If you do however want to stop and restart the application, you can do so by typingCtrl+C
in the Terminal and rerunningnpm run dev
. -
Challenge
Prisma Overview and Setup
Prisma Overview
Prisma is a modern database toolkit designed for TypeScript and JavaScript applications. It simplifies database access by providing a type-safe query builder, an Object-Relational Mapping (ORM) layer, and a schema definition language. Here are some of its main components and features:
Prisma Client
: An auto-generated and type-safe query builder for your database, allowing you to write database queries in TypeScript or JavaScript.Prisma Migrate
: A powerful and easy-to-use migration tool that helps manage your database schema and version control.Prisma Studio
: A GUI to view and manipulate data in your database.Prisma Schema
: A declarative language to define your database schema, models, and relationships.
Prisma supports multiple databases, including PostgreSQL, MySQL, SQLite, SQL Server, and MongoDB. It aims to improve developer productivity and reduce the complexity of database interactions.
Setting Up a Prisma Database
Prisma has already been installed using Node Package Manager,
npm
commands, and is ready to be used in the application. If you are usingnpm
in a project, simply runnpm install prisma
to get started. After Prisma is installed, you can initialize it with Node Package eXectute,npx
commands. In this lab, you will initialize Prisma with SQLite.
Activity
Run
npx prisma init --datasource-provider sqlite
to initialize Prisma in Terminal.This initialization generates a
prisma
directory in the project. Inside theprisma
directory will be a file titledschema.prisma
. YourFILETREE
should now look like the photo below.The
schema.prisma
file is where you will specify your schema definition in the next step.Take a moment to locate and examine the contents of
schema.prisma
. You will see that the schema initializes Prisma with sqlite, sets the database provider to sqlite, and the database url can be found in the.env
file.The initialization command will create a
.env
file (if it does not exist) and add the following line to the.env
file:DATABASE_URL=”file:./dev.db
. Runls -a
in the Terminal to verify that the.env
file was created and does exist. You can look at its contents by running thevim .env
command. Then, type:q
to exit thevim
editor. The contents of the file should look like the photo below as it was generated by thenpx
command you ran to initialize Prisma.Once the database is created you will see a
dev.db
file in yourprisma
directory that is being referred to as the database url. -
Challenge
Specify a Database Schema with Prisma
Specify a Database Schema With Prisma
Now that Prisma has been initialized using SQLite, you will work on specifying a database schema for the project. In
schema.prisma
you will build the schema using Prisma ORM’sPrisma Schema
. ThePrisma Schema
is the main method of configuration for your Prisma ORM setup. It consists of the following parts:- Data sources: These specify the details of the data sources Prisma ORM should connect to. The data sources in this application point to SQLite.
- Generators: These specify what clients should be generated based on the data model (e.g.
Primsa Client
). - Data Model Definition: These specify your application models (the shape of the data per data source) and their relations.
When
schema.prisma
was generated, it was created with correct data sources and generators. You will be responsible for adding models that match the following schema UML to the "Data Model Definition" section in theschema.prisma
file.
Defining Models
Models represent the entities, or tables, of your application domain. For the finance application you are working on in this lab, those entities are
cadence
,type
,plan
, andtransaction
.In Prisma, models are represented using model blocks that define fields within them. Fields are defined with a field name and a field type with optional type modifiers and attributes.
Prisma supports the following field types:
String
Boolean
Int
BigInt
Float
Decimal
DateTime
Json
Bytes
The following field type modifiers are available in Prisma:
[]
?
Prisma also supports the following field attributes:
@id
:Defines a single-field ID on the model@@id
: Defines a multi-field ID (composite ID) on the model. This is the same as aPRIMARY KEY
on the table.@default
: Defines a default value for a field.@unique
: Defines a unique constraint for this field.@@unique
: Defines a compound unique constraint for the specified fields.@index
: Defines an index in the database.@relation
: Defines meta information about the relation.
Model Relationships
A relation is a connection between two models in the Prisma schema, and a relation field's type is another model. In databases, there are three types of relations: one-to-many, one-to-one, and many-to-many. In this lab, you will only setup one-to-many relationships so if you would like to learn more about how to establish a one-to-one or many-to-many relationship, peruse Prisma's Relations Documentation.
Here is an example of how to establish a one-to-many relationship using Prisma:
model User { id String @id @default(uuid()) posts Post[] } model Post { id String @id @default(uuid()) author User @relation(fields: [authorId], references: [id]) authorId Int // relation scalar field (used in the `@relation` attribute above) }
Activity
Underneath the
datasource db
objectprisma/schema.prisma
define the models from the UML diagram.- Define the
Type
model from the UML above.- Define an
id
field of typeString
- The
id
field should have the@id
attribute on it - Using the
@default
attribute, set theid
field default value touuid()
- The
- Define a
name
field of typeString
.- The
name
field should have the@unique
attribute on it.
- The
- Define a
plans
field of typePlan[]
to create a many-to-one relationship with thePlan
model
- Define an
- Define the
Cadence
model from the UML above.- Define an
id
field of typeString
- The
id
field should have the@id
attribute on it - Using the
@default
attribute, set theid
field default value touuid()
- The
- Define a
name
field of typeString
.- The
name
field should have the@unique
attribute on it
- The
- Define a
plans
field of typePlan[]
to create a many-to-one relationship with thePlan
model
- Define an
- Define the
Plan
model from the UML above.- Define an
id
field of typeString
- The
id
field should have the@id
attribute on it - Using the
@default
attribute, set theid
field default value touuid()
- The
- Define a
name
field of typeString
- Define a
budgetAmount
field of typeDecimal
- Define a
description
field of typeString
- Default this field to an empty string
- Define a relation scalar field,
cadenceId
to use in the@relation
attribute - Define a field called
cadence
of typeCadence
that relates thePlan
model to theCadence
model - Define a relation scalar field,
typeId
to use in the@relation
attribute - Define a field called
type
of typeType
that relates thePlan
model to theType
model - Define a
transactions
field of typeTransaction[]
to create a many-to-one relationship with theTransaction
model - Enforce a compound unique constraint on
typeId
andname
fields- Hint Using the
@@unique
attribute with the array `[typeId, name])
- Hint Using the
- Define an
- Define the
Transaction
model from the UML above- Define an
id
field of typeString
- The
id
field should have the@id
attribute on it. - Using the
@default
attribute, set theid
field default value touuid()
- The
- Define a
date
field of typeDateTime
- Define a
amount
field of typeDecimal
- Define an optional
notes
field of typeString
- HInt: The
?
type modifier is added to make a field optional
- HInt: The
- Define a relation scalar field,
planId
to use in the@relation
attribute - Define a field called
plan
of typePlan
that relates thePlan
model to theTransaction
model
- Define an
Example Model Definition in schema.prisma
model Transaction { id String @id @default(uuid()) date DateTime amount Decimal notes String? planId String plan Plan @relation(fields: [planId], references: [id]) } model Plan { id String @id @default(uuid()) budgetAmount Decimal name String description String @default("") cadenceId String cadence Cadence @relation(fields: [cadenceId], references: [id]) typeId String type Type @relation(fields: [typeId], references: [id]) transactions Transaction[] @@unique([typeId, name]) } model Type { id String @id @default(uuid()) name String @unique plans Plan[] } model Cadence { id String @id @default(uuid()) name String @unique plans Plan[] }
-
Challenge
Create a Database and Migrations
Create a Database and Migrations
In the previous step, you created four models:
Type
,Cadence
,Transaction
, andPlan
with all the properties that are necessary for the finance application at hand. At this point, you have aPrisma Schema
, but you do not have a database.In this step, you will create the initial migration that will be responsible for creating the database and syncing it with the schema.
Activity
In Terminal run the following command:
npx prisma migrate dev --name init
This command has completed the following four things after execution:
- Creating the
dev
database which can be found in the/prisma
directory- It is stored in
dev.db
which is also what the.env
file stored as the database url in an earlier step
- It is stored in
- Creating the
Type
,Cadence
,Transaction
, andPlan
tables based on their corresponding Prisma model definitions inprisma/schema.prisma
- Creating a new SQL migration file for this migration
- Runs this SQL migration against the database
After running the above command, your database is now in sync with your
Prisma Schema
. You can locate the migration file that was created and ran in/prisma/migrations/{{migration_id}}_init/migration.sql
. The content of the migration file is the raw SQL used to make the database changes.
More Information on Migrations
If you were to make changes to the schema later down the road, you would need to create and execute new migrations to make the necessary changes to the database. Migrations are created using the command:
npx prisma migrate dev
.Lastly, if you wanted to only create the migration file without executing it, you would run
npx prisma migrate dev --create-only
.The migrations that are created after the initial migration are found under
/prisma/migrations/{{migration_id}}_{{migration_name}}/migration.sql
- Creating the
-
Challenge
Handle Next.js Hot Reloading Issue in Development Mode
Handle Next.js Hot Reloading Issue in Development Mode
In this step, you will be making changes to
app/db.ts
. The existingdb.ts
file assigns the variableprisma
to be a newPrismaClient
. This allows you to use the database throughout the project and create queries using thePrismaClient
. However, Next.js development mode does hot reloading which can cause issues with Prisma because it can attempt to constantly create new connections with thePrisma Client
.You will implement a solution Prisma provides to get around this issue in Next.js development mode.
Activity
Implement the following changes to
app/db.ts
to set a globalprisma
instance tonew PrismaClient()
if it has not previously been defined in development mode.- Wrap line 5 in an
if
statement for whenprocess.env.NODE_ENV === "production"
- Implement the else statement (when you are in development mode) as follows
- If
global.prisma
isnull
(meaning it has not been defined), assignglobal.prisma
to anew PrismaClient()
- Else,
global.prisma
has previously been defined so assignprisma
toglobal.prisma
- If
Example db.ts File Contents
import { PrismaClient } from "@prisma/client"; let prisma: PrismaClient; if (process.env.NODE_ENV === "production") { prisma = new PrismaClient(); } else { if (!global.prisma) { global.prisma = new PrismaClient(); } prisma = global.prisma; } export default prisma;
This code will only make a connection with the
PrismaClient
once despite Next.js hot reloading. This is because now theprisma
variable acts as a singleton. - Wrap line 5 in an
-
Challenge
Seed Application Data
Seed Application Data
Now that your database is up and running, you can seed the database with data relevant to the application. Seeding data can be helpful when you are developing new applications, so you do not have to create a bunch of data by hand. In this step, you will finish a script in
seedDatabase/seed.js
to seed the database with relevant data and add a script topackage.json
to run the aforementioned seeding script.
Activity 1
Follow the steps below to finish implementing the script in
seedDatabase/seed.js
that will be used to seed the database.- Use the
prisma
variable (which is already initialized to aPrisma Client
instance at the top ofseed.js
) to perform database queries to delete all the data in theTransaction
,Plan
,Cadence
, andType
tables in that order.- Hint: You can perform CRUD operations with your generated
Prisma Client
API. For example, you can calldeleteMany()
on yourtransaction
model to delete alltransaction
records. It is important to remember that all Prisma queries are asynchronous calls that need to be awaited. - Example of how to delete all transaction records below
await prisma.transaction.deleteMany();
- The order in which you delete records from the tables matters due to
Foreign Key
restraints since you cannot delete a record if another table is using that record'sPrimary Key
id
as aForeign Key
.
- Hint: You can perform CRUD operations with your generated
- Use the
prisma
variable to add the data fromseedDatabase/data.js
to its corresponding table in the following order:Type
,Cadence
,Plan
,Transaction
.- Hint: You can use
createMany()
to bulk insert data into a table. Under the hood, acreateMany()
call creates a singleINSERT INTO
statement which is more efficient that a separateINSERT
statement for each new row. - Example below of to add all transaction data to the
transaction
table below:
await prisma.transaction.createMany({ data: transactions, });
- Like above, the order in which data is added to the tables matters as
Foreign Keys
need to exist asPrimary Key
id
s in their personal tables before they can be added as aForeign Key
to a relating table.
- Hint: You can use
Example seed.js File Contents
import { PrismaClient } from "@prisma/client"; import { cadences, plans, transactions, types } from "./data.js"; const prisma = new PrismaClient(); const load = async () => { try { await prisma.transaction.deleteMany(); console.log("Deleted records in transaction table"); await prisma.plan.deleteMany(); console.log("Deleted records in plan table"); await prisma.cadence.deleteMany(); console.log("Deleted records in cadence table"); await prisma.type.deleteMany(); console.log("Deleted records in types table"); await prisma.type.createMany({ data: types, }); console.log("Seeded type table"); await prisma.cadence.createMany({ data: cadences, }); console.log("Seeded cadence table"); await prisma.plan.createMany({ data: plans, }); console.log("Seeded plan table"); await prisma.transaction.createMany({ data: transactions, }); console.log("Seeded transaction table"); } catch (e) { console.error(e); process.exit(1); } finally { await prisma.$disconnect(); } }; load();
Activity 2
Now that you have a script that can be ran to seed the database, you need to add to the
package.json
scripts
the command to run yourseedDatabase/seed.js
script.- In your
package.json
file, add another script to thescripts
section that reads"seed": "node seedDatabase/seed.js"
- Do not forgot to add a comma where is necessary to not corrupt the
JSON
file - With this line, you can run
npm run seed
and it will run theseedDatabase/seed.js
script and seed your database
- Do not forgot to add a comma where is necessary to not corrupt the
At this time run
npm run seed
to seed your database with the appropriate records.More Information
Seeding is possible above through a
prisma
property with a value of{ "seed": "node seedDatabase/seed.js" }
on the same level asscripts
inpackage.json
. If you were to include this in thepackage.json
, the seeding script will run whenever a migration is created and ran. Thesolution
has both implementations. - Use the
-
Challenge
Prisma findMany Read Query for Reading Data
Prisma findMany Query for Reading Data
In this step, you will finish implementing the methods that read and return several records at once from the database in
app/lib/actions.js
.Currently, data is retrieved for the application by reading from JSON files. You will change this and use Prisma to get data directly from the database.
For all Prisma queries, Prisma will convert these transactions into SQL statments and run them on the server to perform
INSERT
,SELECT
,DELETE
, andUPDATE
operations.
Activity
Import Prisma From db.ts
- In
app/lib/actions.js
, importprisma
fromdb.ts
- Remember
prisma
is the global instance of aPrisma Client
connectionimport prisma from "../db";
- Remember
Implement getCadences Next.js Server Action
- Remove the current assignment value to the
cadences
variable - Use the
findMany()
Prisma command on thecadence
table to return all records in the table- Hint: All Prisma queries happen asynchronously, so remember to
await
the return value
- Hint: All Prisma queries happen asynchronously, so remember to
Example getCadences Method
export async function getCadences() { let cadences = await prisma.cadence.findMany(); cadences = JSON.parse(JSON.stringify(cadences)); return cadences; }
Implement getTypes Next.js Server Action
- Remove the current assignment value to the
types
variable - Use the
findMany()
Prisma command on thetype
table to return all records in the table
Example getTypes Method
export async function getTypes() { let types = await prisma.type.findMany(); types = JSON.parse(JSON.stringify(types)); return types; }
Implement getPlans Next.js Server Action
- Remove the current assignment value to the
plans
variable - Use the
findMany()
Prisma command on theplan
table to return all records in the table of a specificplanType
- Hint:
findMany
accepts an object as a parameter. In this object you can specify options to filter the search and order, select, include, omit, and skip results. You want to use thewhere
option to filter the search by related record field values (becauseplanType
exists on the relatedtype
record). - Below is an example of how to filter by related record field values where the query returns
users
that have a publishedpost
const users = await prisma.user.findMany({ where: { posts: { published: true, }, }, });
- Hint:
- Use the
include
option in thefindMany
parameter object to eagerly load thecadence
,type
, andtransactions
relations on the return object- This is necessary because the frontend has pages that rely on this nested data - see
app/(ui)/components/planTable.jsx
line 25 for example - Below is an example of how to include relation data on the return value of
findMany
. The query below returns allusers
and theirpost
data.
const users = await prisma.user.findMany({ include: { posts: true, }, });
- This is necessary because the frontend has pages that rely on this nested data - see
- Order the returned plans list from
findMany
bycadence
name
in ascending order first andplan
name
in ascending order second with theorderBy
option- Example of how
orderBy
is used is below whereprofile
is a relation touser
const users = await prisma.user.findMany({ orderBy: [ { name: "asc" }, { profile: { nickname: "desc" } }, ], });
- Example of how
Example getPlans Method
export async function getPlans(planType) { let plans = let plans = await prisma.plan.findMany({ where: { type: { name: planType, }, }, include: { type: true, cadence: true, transactions: true, }, orderBy: [ { cadence: { name: "asc", }, }, { name: "asc", }, ], }); plans = JSON.parse(JSON.stringify(plans)); return plans; }
Implement getTransactions Next.js Server Action
- Remove the current assignment value to the
transactions
variable - Use the
findMany()
Prisma command on thetransaction
table to return all records in the table of a specifictransactionType
if it is defined otherwise return alltransactions
.- Hint: As in the
getPlans
method, use thewhere
option that can be passed tofindMany
to comparetransaction
'splan
'stype
's name totransactionType
. - Hint You can destruct an object and use a ternary to see if
transactionType
is defined and create an object withname
mapping totransactionType
if it is and an empty object if it is not.
- Hint: As in the
- Use the
include
option in thefindMany
parameter object to eagerly load theplan
andplan
'stype
relations on the return object- This is necessary because the frontend has pages that rely on this nested data - see
app/(ui)/components/transactionTable.jsx
line 32 for example
- This is necessary because the frontend has pages that rely on this nested data - see
- Order the returned plans list from
findMany
bytransaction
date
in descending order first andplan
type
name in ascending order second with theorderBy
option
Example getTransactions Method
export async function getTransactions(transactionType) { let transactions = await prisma.transaction.findMany({ where: { plan: { type: { ...(transactionType ? { name: transactionType } : {}), }, }, }, include: { plan: { include: { type: true }, }, }, orderBy: [ { date: "desc", }, { plan: { type: { name: "asc", }, }, }, ], }); transactions = JSON.parse(JSON.stringify(transactions)); return transactions; }
Implement getPlanCategories Next.js Server Action
- Remove the current assignment values to the
categories
variable - Use the
findMany()
Prisma command on theplan
table to return all records in the table of a specificplanType
- Hint: This will look exactly like the query in
getPlans
- Hint: This will look exactly like the query in
- Use the
select
option in thefindMany
parameter object to specify that onlyname
andid
are properties to include on the returned object- Below is an example of how to select data in a Prisma query
const users = await prisma.user.findMany({ select: { email: true, posts: { comments: true, }, }, });
- Order the returned plans list from
findMany
byplan
name
in ascending order- Hint: Since there is only one order by criteria, the value of the
orderBy
option can be an object instead of an array of objects.
- Hint: Since there is only one order by criteria, the value of the
Example getPlanCategories Method
* Below is an example of how to include relation data on the return value of `findMany`. The query below returns all `users` and their `post` data. ```js const users = await prisma.user.findMany({ include: { posts: true, }, });
- In
-
Challenge
Prisma findUnique Query For Reading Data
Prisma findUnique Query for Reading Data
Now that the application is reading several records at once from the database, you will implement more methods in
app/lib/actions.js
to get a single record from the database at a time.
Prisma findUnique Read Query
The
findUnique
query can only be performed with awhere
option on properties that areunique
.If you were filtering a query result on a property that was not
unqiue
, you would use thefindFirst
query.
Activity - Implement Methods
Implement getPlan Next.js Server Action
- In the
getPlan
method withinapp/lib/actions.js
remove theplans
variable - Remove the current assignment value to the
plan
variable - Use the
findUnique()
Prisma command on theplan
table to return the record withid
- Hint:
findUnique
has the same options available asfindMany
- so this is possible with awhere
option
- Hint:
- Use the
include
option to returnplan
'scadence
,type
, andtransactions
- Also
include
transactions
'splan
andplan
'stype
Example getPlan Method
export async function getPlan(id) { let plan = await prisma.plan.findUnique({ where: { id: id, }, include: { cadence: true, type: true, transactions: { include: { plan: { include: { type: true, }, }, }, }, }, }); plan = JSON.parse(JSON.stringify(plan)); return plan; }
Implement getTransaction Next.js Server Action
- Remove the
transactions
variable - Remove the current assignment value to the
transaction
variable - Use the
findUnique()
Prisma command on thetransaction
table to return the record withid
- Use the
include
option to returntransaction
'splan
andplan
'stype
objects
Example getTransaction Method
export async function getTransaction(id) { let transaction = await prisma.transaction.findUnique({ where: { id: id, }, include: { plan: { include: { type: true, }, }, }, }); transaction = JSON.parse(JSON.stringify(transaction)); return transaction; }
Implement getTypeByName Next.js Server Action
- Remove the
types
variable - Remove the current assignment value to the
type
variable - Use the
findUnique()
Prisma command on thetype
table to return the record where thename
property matchestypeName
Example getTypeByName Method
export async function getTypeByName(typeName) { const type = await prisma.type.findUnique({ where: { name: typeName, }, }); return type; }
Implement getCadenceByName Next.js Server Action
- Remove the
cadences
variable - Remove the current assignment value to the
cadence
variable - Use the
findUnique()
Prisma command on thecadence
table to return the record where thename
property matchescadenceName
Example getCadenceByName Method
export async function getCadenceByName(cadenceName) { const cadence = await prisma.cadence.findUnique({ where: { name: cadenceName, }, }); return cadence; }
Implement getPlanByTypeAndPlanName Next.js Server Action
- Remove the
plans
variable - Remove the current assignment value to the
plan
variable - Use the
findUnique()
Prisma command on theplan
table to return the record where thename
property matchesname
andtypeId
matchestypeId
- Hint: Since
typeId
andname
are a compound unique property in your schema, you can use thefindUnique
query with thetypeId_name
property within thewhere
option
- Hint: Since
Example getPlanByTypeAndPlanName Method
async function getPlanByTypeAndPlanName(planType, name) { const typeId = await getTypeByName(planType); const plan = await prisma.plan.findUnique({ where: { typeId_name: { name: name, typeId: typeId, }, }, }); return plan; }
Activity - Visit the UI
If you visit the Web Browser when the application is running, you will see that data is displaying on all of the pages (including the edit pages). You will also notice that the order the data is displayed in matches the order you specified in the previous step.
This is a great way to verify that your Prisma read queries are working.
- In the
-
Challenge
Prisma Create Query
Prisma Create Query
At this point, the application does not persist any data when a user attempts to create, update, or delete data in the UI. You can try this out yourself by attempting to create, update, or delete a transaction through the UI. You will see that the the transaction change is not persisted.
In this step, you will finish implementing some of the Next.js server actions in
app/lib/actions.js
that are responsible for persisting data creation.
Activity - Implement Methods
Implement createPlan Next.js Server Action
- In the
createPLan
method withinapp/lib/actions.js
remove the early return in thetry
block - Assign the Prisma
create
command on theplan
table return value to a variable calledplan
- Hint: The
create
model query acceptsdata
as an option.data
is a Javascript object that represents the new record. Adata
object has already been created for you at the top ofcreatePlan
. Thisdata
object is assigned to the variableplan
. Be sure to passplan
as thedata
option within thecreate
query
- Hint: The
Example createPlan Method
export async function createPlan(planType, formData) { const type = planType ? planType : formData.get("type"); let plan = { name: formData.get("name").toLowerCase(), budgetAmount: new Prisma.Decimal(formData.get("amount")), cadenceId: await getCadenceByName(formData.get("cadence")).id, typeId: await getTypeByName(type).id, description: formData.get("description"), }; try { // this is the line you will add plan = await prisma.plan.create({ data: plan }); console.log("New plan successfully added!"); } catch (error) { console.log(`Error adding new plan: ${error}`); throw new Error("Failed to add plan"); } revalidatePlanPaths(plan.id); plan = JSON.parse(JSON.stringify(plan)); return plan; }
Implement createTransaction Next.js Server Action
- Remove the early return in the
try
block - Assign the Prisma
create
command on thetransaction
table return value to a variable calledtransaction
- Hint: As before, the
data
object you should use is given to you above and assigned to thetransaction
variable
- Hint: As before, the
Example createTransaction Method
export async function createTransaction(transactionType, formData) { const type = transactionType ? transactionType : formData.get("type"); let transaction = { date: new Date(formData.get("date").replace("-", "/")), amount: new Prisma.Decimal(formData.get("amount")), planId: await getPlanByTypeAndPlanName(type, formData.get("category")), notes: formData.get("notes"), }; try { transaction = await prisma.transaction.create({ data: transaction }); console.log("New transaction successfully added!"); } catch (error) { console.log(`Error adding new transaction: ${error}`); throw new Error("Failed to add transaction"); } revalidateTransactionPaths(transactionType, transaction.id); transaction = JSON.parse(JSON.stringify(transaction)); return transaction; }
Activity - Visit the UI
Visit the application in Web Browser while it is running. Create an expense transaction, income transaction, and plan to verify the queries above work, the data is persisted, and the data is added to the corresponding table in the UI.
- In the
-
Challenge
Prisma Update Query
Prisma Update Query
In the previous step, you finished implementing the create methods. Now, you will be responsible for implementing the update methods using Prisma's
update
query inapp/lib/actions.js
.
Activity - Implement Methods
Implement updateTransaction Next.js Server Action
- In the
updateTransaction
method withinapp/lib/actions.js
remove the early return in thetry
block - Below the
TODO
comment in thetry
block, use Prisma'supdate
query on thetransaction
table to update thetransaction
record that has the correctid
. Save the return value from theupdate
query to the already existingtransaction
variable.- Hint: The
update
model query acceptsdata
andwhere
as options.data
is what represents the updated record properties as an object. Adata
object has already been created for you above theTODO
comment. Thisdata
object is assigned to the variablenewTransactionData
. Be sure to passnewTransactionData
as thedata
option within theupdate
query - Hint: As with the
findUnique
queries you wrote in a previous step, use thewhere
option withid
to update the correcttransaction
record
- Hint: The
Example updateTransaction Method
export async function updateTransaction(id, formData) { const inputData = { date: new Date(formData.get("date").replace("-", "/")), amount: formData.get("amount"), category: formData.get("category"), notes: formData.get("notes"), }; let transaction; try { transaction = await getTransaction(id); let planId = transaction.plan.id; if (inputData.category != transaction.plan.name) { const plan = await getPlanByTypeAndPlanName( transaction.plan.type.name, inputData.category ); planId = plan.id; } const newTransactionData = { date: inputData.date, amount: inputData.amount, planId: planId, notes: inputData.notes, }; // You will add this line transaction = await prisma.transaction.update({ where: { id: id, }, data: newTransactionData, }); console.log("Transaction successfully updated!"); } catch (error) { console.log(`Error saving transaction item ${error}`); throw new Error("Failed to update transaction item"); } revalidateTransactionPaths(transaction.type, id); transaction = JSON.parse(JSON.stringify(transaction)); return transaction; }
Implement updatePlan Next.js Server Action
- Remove the early return in the
try
block - Below the
TODO
comment in thetry
block, use Prisma'supdate
query on theplan
table to update theplan
record that has the correctid
. Save the return value from theupdate
query to the already existingplan
variable.- Hint: As above, use the
where
anddata
update
options. Adata
object has already been created for you above theTODO
comment and is stored in thenewPlanData
variable
- Hint: As above, use the
Example updatePlan Method
export async function updatePlan(id, formData) { const inputData = { name: formData.get("name").toLowerCase(), budgetAmount: formData.get("amount"), description: formData.get("description"), cadence: formData.get("cadence").toLowerCase(), type: formData.get("type").toLowerCase(), }; try { let plan = getPlan(id); let cadenceId = plan.cadence.id; let typeId = plan.type.id; if (inputData.cadence != plan.cadence.name) { const cadence = await getCadenceByName(plan.cadence.name); cadenceId = cadence.id; } if (inputData.type != plan.type.name) { const type = await getTypeByName(plan.type.name); typeId = type.id; } const newPlanData = { name: inputData.name, budgetAmount: inputData.budgetAmount, description: inputData.description, cadenceId: cadenceId, typeId: typeId, }; plan = await prisma.plan.update({ where: { id: id, }, data: newPlanData, }); console.log("Plan successfully updated!"); } catch (error) { console.log(`Error saving plan item ${error}`); throw new Error("Failed to update plan item"); } revalidatePlanPaths(id); plan = JSON.parse(JSON.stringify(plan)); return plan; }
Activity - Visit the UI
Update an expense transaction, income transaction, and plan in the UI to verify that the update queries are working correctly. When you update any item, the change should be reflected in the table (which is a representation of the current data stored in the database).
- In the
-
Challenge
Prisma Delete Query
Prisma Delete Query
Lastly, you need to finish implementing the server actions responsible for persisting the deletion of data in
app/lib/actions.js
.
Activity - Implement Methods
Implement deleteTransaction Next.js Server Action
- In the
deleteTransaction
method withinapp/lib/actions.js
remove the early return in thetry
block - Below the
TODO
comment in thetry
block, use Prisma'sdelete
query on thetransaction
table to delete thetransaction
record with theid
given and assign the return value to the variabledeleteTransaction
that has already been defined for you at the top of the method- Hint: The
delete
Prisma query acceptswhere
as an option as other queries have - be sure to use thewhere
option onid
- Hint: The
- Include
plan
andplan
'stype
on the return value from thedelete
query using theinclude
option- Hint: The
delete
query returns the record that was deleted and this will include thetransaction
's relational data on the return value
- Hint: The
- Assign the return value from the
delete
query to thedeletedTransaction
variable that has already been defined for you at the top of the method
Example deleteTransaction Method
export async function deleteTransaction(id) { let deletedTransaction; try { deletedTransaction = await prisma.transaction.delete({ where: { id: id, }, include: { plan: { include: { type: true, }, }, }, }); console.log(`Transaction with id ${id} successfully deleted`); } catch (error) { console.log(`Error deleting transaction: ${error}`); throw new Error("Failed to delete transaction"); } revalidateTransactionPaths(deletedTransaction.plan.type.name, id); }
Implement deletePlan Next.js Server Action
- Remove the early return in the
try
block - Below the
TODO
comment in thetry
block, use Prisma'sdelete
query on theplan
table to delete theplan
record with theid
given - Assign the return value to the variable
deletedPlan
that has already been defined for you at the top of the method
Example deleteTransaction Method
export async function deletePlan(id) { let deletedPlan; try { deletedPlan = await prisma.plan.delete({ where: { id: id, }, }); console.log(`Plan with id ${id} successfully deleted`); } catch (error) { console.log(`Error deleting plan: ${error}`); throw new Error("Failed to delete plan"); } revalidatePlanPaths(id); }
Activity - Visit the UI
Delete an expense transaction, income transaction, and plan in the UI to verify that the delete queries are working correctly. When you delete any item, the item should be removed from the UI's corresponding table to reflect the record has been removed from the database.
- In the
-
Challenge
Conclusion
Conclusion
In this lab, you became familiar with Prisma. You can now confidently setup a Prisma database, create a
Prisma Schema
, seed the database, perform migrations on the database, and use thePrisma Client
to perform CRUD Prisma queries against the datavse. The finance application used in this lab now performs CRUD operations against a database which is a huge feature.
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.