- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- 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.
Lab 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 devin 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, andtypetables, 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
solutiondirectory with correspondingstepdirectories 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 devin 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
appdirectory 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+Cin 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,
npmcommands, and is ready to be used in the application. If you are usingnpmin a project, simply runnpm install prismato get started. After Prisma is installed, you can initialize it with Node Package eXectute,npxcommands. In this lab, you will initialize Prisma with SQLite.
Activity
Run
npx prisma init --datasource-provider sqliteto initialize Prisma in Terminal.This initialization generates a
prismadirectory in the project. Inside theprismadirectory will be a file titledschema.prisma. YourFILETREEshould now look like the photo below.
The
schema.prismafile 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.envfile.The initialization command will create a
.envfile (if it does not exist) and add the following line to the.envfile:DATABASE_URL=”file:./dev.db. Runls -ain the Terminal to verify that the.envfile was created and does exist. You can look at its contents by running thevim .envcommand. Then, type:qto exit thevimeditor. The contents of the file should look like the photo below as it was generated by thenpxcommand you ran to initialize Prisma.
Once the database is created you will see a
dev.dbfile in yourprismadirectory 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.prismayou will build the schema using Prisma ORM’sPrisma Schema. ThePrisma Schemais 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.prismawas 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.prismafile.
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:
StringBooleanIntBigIntFloatDecimalDateTimeJsonBytes
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 KEYon 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 dbobjectprisma/schema.prismadefine the models from the UML diagram.- Define the
Typemodel from the UML above.- Define an
idfield of typeString- The
idfield should have the@idattribute on it - Using the
@defaultattribute, set theidfield default value touuid()
- The
- Define a
namefield of typeString.- The
namefield should have the@uniqueattribute on it.
- The
- Define a
plansfield of typePlan[]to create a many-to-one relationship with thePlanmodel
- Define an
- Define the
Cadencemodel from the UML above.- Define an
idfield of typeString- The
idfield should have the@idattribute on it - Using the
@defaultattribute, set theidfield default value touuid()
- The
- Define a
namefield of typeString.- The
namefield should have the@uniqueattribute on it
- The
- Define a
plansfield of typePlan[]to create a many-to-one relationship with thePlanmodel
- Define an
- Define the
Planmodel from the UML above.- Define an
idfield of typeString- The
idfield should have the@idattribute on it - Using the
@defaultattribute, set theidfield default value touuid()
- The
- Define a
namefield of typeString - Define a
budgetAmountfield of typeDecimal - Define a
descriptionfield of typeString- Default this field to an empty string
- Define a relation scalar field,
cadenceIdto use in the@relationattribute - Define a field called
cadenceof typeCadencethat relates thePlanmodel to theCadencemodel - Define a relation scalar field,
typeIdto use in the@relationattribute - Define a field called
typeof typeTypethat relates thePlanmodel to theTypemodel - Define a
transactionsfield of typeTransaction[]to create a many-to-one relationship with theTransactionmodel - Enforce a compound unique constraint on
typeIdandnamefields- Hint Using the
@@uniqueattribute with the array `[typeId, name])
- Hint Using the
- Define an
- Define the
Transactionmodel from the UML above- Define an
idfield of typeString- The
idfield should have the@idattribute on it. - Using the
@defaultattribute, set theidfield default value touuid()
- The
- Define a
datefield of typeDateTime - Define a
amountfield of typeDecimal - Define an optional
notesfield of typeString- HInt: The
?type modifier is added to make a field optional
- HInt: The
- Define a relation scalar field,
planIdto use in the@relationattribute - Define a field called
planof typePlanthat relates thePlanmodel to theTransactionmodel
- 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, andPlanwith 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 initThis command has completed the following four things after execution:
- Creating the
devdatabase which can be found in the/prismadirectory- It is stored in
dev.dbwhich is also what the.envfile stored as the database url in an earlier step
- It is stored in
- Creating the
Type,Cadence,Transaction, andPlantables 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.tsfile assigns the variableprismato 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.tsto set a globalprismainstance tonew PrismaClient()if it has not previously been defined in development mode.- Wrap line 5 in an
ifstatement for whenprocess.env.NODE_ENV === "production" - Implement the else statement (when you are in development mode) as follows
- If
global.prismaisnull(meaning it has not been defined), assignglobal.prismato anew PrismaClient() - Else,
global.prismahas previously been defined so assignprismatoglobal.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
PrismaClientonce despite Next.js hot reloading. This is because now theprismavariable 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.jsto seed the database with relevant data and add a script topackage.jsonto run the aforementioned seeding script.
Activity 1
Follow the steps below to finish implementing the script in
seedDatabase/seed.jsthat will be used to seed the database.- Use the
prismavariable (which is already initialized to aPrisma Clientinstance at the top ofseed.js) to perform database queries to delete all the data in theTransaction,Plan,Cadence, andTypetables in that order.- Hint: You can perform CRUD operations with your generated
Prisma ClientAPI. For example, you can calldeleteMany()on yourtransactionmodel to delete alltransactionrecords. 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 Keyrestraints since you cannot delete a record if another table is using that record'sPrimary Keyidas aForeign Key.
- Hint: You can perform CRUD operations with your generated
- Use the
prismavariable to add the data fromseedDatabase/data.jsto 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 INTOstatement which is more efficient that a separateINSERTstatement for each new row. - Example below of to add all transaction data to the
transactiontable below:
await prisma.transaction.createMany({ data: transactions, });- Like above, the order in which data is added to the tables matters as
Foreign Keysneed to exist asPrimary Keyids in their personal tables before they can be added as aForeign Keyto 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.jsonscriptsthe command to run yourseedDatabase/seed.jsscript.- In your
package.jsonfile, add another script to thescriptssection that reads"seed": "node seedDatabase/seed.js"- Do not forgot to add a comma where is necessary to not corrupt the
JSONfile - With this line, you can run
npm run seedand it will run theseedDatabase/seed.jsscript and seed your database
- Do not forgot to add a comma where is necessary to not corrupt the
At this time run
npm run seedto seed your database with the appropriate records.More Information
Seeding is possible above through a
prismaproperty with a value of{ "seed": "node seedDatabase/seed.js" }on the same level asscriptsinpackage.json. If you were to include this in thepackage.json, the seeding script will run whenever a migration is created and ran. Thesolutionhas 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, andUPDATEoperations.
Activity
Import Prisma From db.ts
- In
app/lib/actions.js, importprismafromdb.ts- Remember
prismais the global instance of aPrisma Clientconnectionimport prisma from "../db";
- Remember
Implement getCadences Next.js Server Action
- Remove the current assignment value to the
cadencesvariable - Use the
findMany()Prisma command on thecadencetable to return all records in the table- Hint: All Prisma queries happen asynchronously, so remember to
awaitthe 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
typesvariable - Use the
findMany()Prisma command on thetypetable 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
plansvariable - Use the
findMany()Prisma command on theplantable to return all records in the table of a specificplanType- Hint:
findManyaccepts 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 thewhereoption to filter the search by related record field values (becauseplanTypeexists on the relatedtyperecord). - Below is an example of how to filter by related record field values where the query returns
usersthat have a publishedpost
const users = await prisma.user.findMany({ where: { posts: { published: true, }, }, }); - Hint:
- Use the
includeoption in thefindManyparameter object to eagerly load thecadence,type, andtransactionsrelations on the return object- This is necessary because the frontend has pages that rely on this nested data - see
app/(ui)/components/planTable.jsxline 25 for example - Below is an example of how to include relation data on the return value of
findMany. The query below returns allusersand theirpostdata.
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
findManybycadencenamein ascending order first andplannamein ascending order second with theorderByoption- Example of how
orderByis used is below whereprofileis 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
transactionsvariable - Use the
findMany()Prisma command on thetransactiontable to return all records in the table of a specifictransactionTypeif it is defined otherwise return alltransactions.- Hint: As in the
getPlansmethod, use thewhereoption that can be passed tofindManyto comparetransaction'splan'stype's name totransactionType. - Hint You can destruct an object and use a ternary to see if
transactionTypeis defined and create an object withnamemapping totransactionTypeif it is and an empty object if it is not.
- Hint: As in the
- Use the
includeoption in thefindManyparameter object to eagerly load theplanandplan'styperelations on the return object- This is necessary because the frontend has pages that rely on this nested data - see
app/(ui)/components/transactionTable.jsxline 32 for example
- This is necessary because the frontend has pages that rely on this nested data - see
- Order the returned plans list from
findManybytransactiondatein descending order first andplantypename in ascending order second with theorderByoption
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
categoriesvariable - Use the
findMany()Prisma command on theplantable 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
selectoption in thefindManyparameter object to specify that onlynameandidare 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
findManybyplannamein ascending order- Hint: Since there is only one order by criteria, the value of the
orderByoption 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.jsto get a single record from the database at a time.
Prisma findUnique Read Query
The
findUniquequery can only be performed with awhereoption on properties that areunique.If you were filtering a query result on a property that was not
unqiue, you would use thefindFirstquery.
Activity - Implement Methods
Implement getPlan Next.js Server Action
- In the
getPlanmethod withinapp/lib/actions.jsremove theplansvariable - Remove the current assignment value to the
planvariable - Use the
findUnique()Prisma command on theplantable to return the record withid- Hint:
findUniquehas the same options available asfindMany- so this is possible with awhereoption
- Hint:
- Use the
includeoption to returnplan'scadence,type, andtransactions - Also
includetransactions'splanandplan'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
transactionsvariable - Remove the current assignment value to the
transactionvariable - Use the
findUnique()Prisma command on thetransactiontable to return the record withid - Use the
includeoption to returntransaction'splanandplan'stypeobjects
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
typesvariable - Remove the current assignment value to the
typevariable - Use the
findUnique()Prisma command on thetypetable to return the record where thenameproperty 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
cadencesvariable - Remove the current assignment value to the
cadencevariable - Use the
findUnique()Prisma command on thecadencetable to return the record where thenameproperty 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
plansvariable - Remove the current assignment value to the
planvariable - Use the
findUnique()Prisma command on theplantable to return the record where thenameproperty matchesnameandtypeIdmatchestypeId- Hint: Since
typeIdandnameare a compound unique property in your schema, you can use thefindUniquequery with thetypeId_nameproperty within thewhereoption
- 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.jsthat are responsible for persisting data creation.
Activity - Implement Methods
Implement createPlan Next.js Server Action
- In the
createPLanmethod withinapp/lib/actions.jsremove the early return in thetryblock - Assign the Prisma
createcommand on theplantable return value to a variable calledplan- Hint: The
createmodel query acceptsdataas an option.datais a Javascript object that represents the new record. Adataobject has already been created for you at the top ofcreatePlan. Thisdataobject is assigned to the variableplan. Be sure to passplanas thedataoption within thecreatequery
- 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
tryblock - Assign the Prisma
createcommand on thetransactiontable return value to a variable calledtransaction- Hint: As before, the
dataobject you should use is given to you above and assigned to thetransactionvariable
- 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
updatequery inapp/lib/actions.js.
Activity - Implement Methods
Implement updateTransaction Next.js Server Action
- In the
updateTransactionmethod withinapp/lib/actions.jsremove the early return in thetryblock - Below the
TODOcomment in thetryblock, use Prisma'supdatequery on thetransactiontable to update thetransactionrecord that has the correctid. Save the return value from theupdatequery to the already existingtransactionvariable.- Hint: The
updatemodel query acceptsdataandwhereas options.datais what represents the updated record properties as an object. Adataobject has already been created for you above theTODOcomment. Thisdataobject is assigned to the variablenewTransactionData. Be sure to passnewTransactionDataas thedataoption within theupdatequery - Hint: As with the
findUniquequeries you wrote in a previous step, use thewhereoption withidto update the correcttransactionrecord
- 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
tryblock - Below the
TODOcomment in thetryblock, use Prisma'supdatequery on theplantable to update theplanrecord that has the correctid. Save the return value from theupdatequery to the already existingplanvariable.- Hint: As above, use the
whereanddataupdateoptions. Adataobject has already been created for you above theTODOcomment and is stored in thenewPlanDatavariable
- 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
deleteTransactionmethod withinapp/lib/actions.jsremove the early return in thetryblock - Below the
TODOcomment in thetryblock, use Prisma'sdeletequery on thetransactiontable to delete thetransactionrecord with theidgiven and assign the return value to the variabledeleteTransactionthat has already been defined for you at the top of the method- Hint: The
deletePrisma query acceptswhereas an option as other queries have - be sure to use thewhereoption onid
- Hint: The
- Include
planandplan'stypeon the return value from thedeletequery using theincludeoption- Hint: The
deletequery 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
deletequery to thedeletedTransactionvariable 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
tryblock - Below the
TODOcomment in thetryblock, use Prisma'sdeletequery on theplantable to delete theplanrecord with theidgiven - Assign the return value to the variable
deletedPlanthat 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 Clientto 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.
About the author
Real skill practice before real-world application
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.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.