The Basics of CRUD
In this lesson, we'll walk through the basics of creating, reading, updating, and deleting (CRUD) data from our database using our Lucid Models.
- Author
- Tom Gobich
- Published
- Mar 02
- Duration
- 11m 56s
Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.
Adocasts
Burlington, KY
Transcript
The Basics of CRUD
-
[MUSIC]
-
So now with our models defined, we're ready to go ahead and
-
start using them.
-
However, we actually already have.
-
So if you recall back, we created our movie model,
-
defined the properties that we wanted on it, and
-
then started using it by mocking the all and
-
find method within that model.
-
Well, we have since removed those methods within our model,
-
so they no longer are defined directly inside of the model.
-
But if we jump up to our movie's controller,
-
we're still making use of them.
-
And you'll notice that there's no error indicated here by
-
red squiggly either, meaning that they are perfectly valid.
-
That base model that we're extending all of our models off
-
of attaches Lucid onto the model itself.
-
And Lucid's going to provide a number of static methods
-
available to us that we can use out of the box
-
to perform CRUD-based operations against that model.
-
CRUD meaning create, read, update, and delete.
-
Now remember, a model is representative of a single table
-
inside of our database.
-
So for example, whenever we import our movie model here,
-
we're working specifically with our movie's table inside of
-
our database.
-
So by calling movie.all,
-
we're querying all movies inside of our database.
-
By doing movie.find, we're finding a particular movie
-
by that movie's unique identifier.
-
Up till now, we've been using that movie's file name
-
inside of our application to represent its unique
-
identifier, which has served the purpose of the movie's
-
slug.
-
However, that responsibility has since been transferred
-
over to the database where we now have an ID that uniquely
-
identifies the movie itself.
-
We also have a slug, but this find method specifically
-
is going to be expecting our ID because the find method
-
is going to use the primary key to look up the movie
-
based off of the value that we provide in as the argument
-
to this method.
-
And if we jump over to our movie model,
-
our primary key is indeed our ID column.
-
So all that to say we are indeed already making use of
-
Lucid to query some information from our database.
-
The only thing is, is we'll need to switch our find
-
to either params.id to represent the movie's specific ID,
-
or we can keep it as slug and switch this find method
-
to now we have a find by available to us as well.
-
The only difference between find and find by is that
-
with find by, we can specify the specific column
-
as the first argument that we want to look the movie up by
-
with the value from the second argument.
-
So for example, we could provide slug in here to now find
-
our movie by the slug that we're providing in.
-
So if we pause here for a second, open up our terminal
-
and let's go ahead and boot up our server.
-
So npm run dev.
-
Okay, let's go ahead and jump into our browser now
-
and give our application a refresh.
-
It looks like it already did it,
-
we'll do one more for sanity sake.
-
And everything's working okay, we're not getting any errors,
-
but we're also not getting any movies listed out here either
-
and that's because we don't have any data
-
inside of our database at this point in time.
-
We do with the Adonis schema
-
and Adonis schema version table that Adonis has used
-
whenever we migrated our tables,
-
but for the actual usability of our application,
-
we don't have any data to find.
-
Additionally, if we were to try to jump into one
-
of our movies inside of our navigation,
-
we're gonna get cannot read properties of null reading title
-
because the find by method wasn't able to find a movie
-
with my awesome movie as the slug inside of our database,
-
meaning that our movie itself is going to be null.
-
Okay, let's go back a step there.
-
If we wanted our my awesome movie route to fail
-
immediately whenever it cannot find the record
-
inside of the database, instead of returning back null
-
and having it fail somewhere else down the line
-
whenever we actually try to use a property
-
off of the object that we're expecting to be populated,
-
we can instead, let's hide our browser back away.
-
Instead of using find by, there is an or variant as well.
-
So or fail.
-
So now we'll attempt to find a record
-
inside of our movie database via the slug column
-
with the slug that we're providing in.
-
And if it can't, it will fail immediately right here
-
with a 404 error.
-
This also exists for find.
-
So we can do find or fail as well,
-
if you just wanna look up by a primary key,
-
but we'll leave it as slug for right now.
-
So let's give that a save.
-
We can jump back into our browser
-
and let's give this page a refresh.
-
There we go.
-
So now you see that we're getting a 404 error
-
with row not found defined,
-
and our error is happening
-
right where that call was happening.
-
This difference is happening on the type
-
of the return of that method as well.
-
So now if we take a look at the type of our movie here,
-
you'll see that it's just movie.
-
But if we switch this back to just find by
-
instead of the or fail being on there,
-
and we hover over this to see the type now,
-
we'll see that it's movie or null.
-
So there's some type safety there as well.
-
Okay, so let's jump into pgAdmin real quick,
-
dive into our servers, dive into Postgres,
-
dive into our databases.
-
Let's go into our Adonis 6 database,
-
jump down to our schemas, go inside of public,
-
and go inside of our tables.
-
Let's right click on our roles table,
-
go into view or edit data,
-
and let's click on all rows.
-
Exactly as we expect, we're not gonna get back
-
any data.
-
In order for us to start populating our database
-
with information, just like we did with our migrations,
-
we're gonna have to start in a specific order
-
because in order for us to have users,
-
we're gonna need to have a role defined
-
so that we can attach a user a specific role.
-
The same with our movies, in order for us to have a movie,
-
we're gonna have to have a movie status defined
-
so that we can attach a movie status into the movie itself.
-
And we're also gonna have to have a cynist defined as well,
-
because our movies are directly dependent on a cynist ID
-
via the writer ID and director ID
-
inside of our movie table.
-
So inserting data with the start of our application
-
is going to be similar to how we defined our migrations.
-
They're gonna have a specific order
-
that we need to work with.
-
So let's start by getting some roles defined.
-
So let's hide pgAdmin back away,
-
and let's open up our terminal once more,
-
and we can stop our server.
-
So we'll go ahead and clear it out.
-
And let's open up a REPL session inside of our terminal.
-
REPL means read, evaluate, print, and loop.
-
It's going to allow us to directly work with our server
-
inside of our terminal so that we can work with our models
-
and actually create data inside of our database
-
without having to write the actual application code.
-
And it's also just gonna let us play around a little bit.
-
So let's go ahead and do node.acelist here.
-
And if we scroll up to the top section,
-
we're gonna see REPL right here,
-
and it allows us to start a new REPL session.
-
So we'll scroll back down,
-
and let's do node.acelist.repl.
-
You'll see in yellow here that we can type .ls
-
to see a list of available context methods and properties.
-
So let's go ahead and do that.
-
So .ls, oops, I had a brain fart there.
-
Let's do .ls.
-
There we go, that's better.
-
Let's hide our text editor away too.
-
Okay, so the first couple of arguments
-
are going to allow us to import modules.
-
We don't need to do that right now.
-
What we wanna do is work with models.
-
So if we scroll down a little bit to the load section here,
-
we'll start seeing some load options,
-
and right here is load models.
-
This is going to allow us to recursively load in
-
our Lucid models directly inside of our terminal
-
so that we can work directly with them here.
-
So let's go ahead and call load models, just like so.
-
Cool, we probably should have awaited that,
-
that return back a promise, but everything worked A-okay.
-
Our models are indeed loaded,
-
and we can access them by using models.
-
So if we type out models,
-
we're gonna see that we have an object.
-
First is our movie,
-
and then it's gonna go on with the type of the movie onward,
-
but we do have all of our models accessible.
-
So let's go back a little bit here,
-
and let's await models.
-
And let's do our role to access our role model.
-
And just like we're doing with our movie model
-
inside of our movie controller,
-
we can call .all here just to see if everything works.
-
So we'll go ahead and hit enter on that.
-
And you see that we get back an empty array,
-
indicating that we do indeed have no data
-
inside of the table.
-
So let's go ahead and try await models role,
-
and there's a create method on here as well
-
that will allow us to pass in an object of information
-
to define a specific row for our role table.
-
And since we're working with our Lucid model,
-
Lucid's going to automatically take care of assigning
-
and updating our created at and updated at as needed
-
whenever we're working directly with the model.
-
So all that we need to do is define a name for our role,
-
and we'll be good to go.
-
So let's hide that back away,
-
and let's go ahead and define a name.
-
For our first role,
-
let's just say they're a typical user
-
inside of our application.
-
So we'll go ahead and hit enter there.
-
And we're going to see that we get back an instance
-
of the model record for the specific row that we created.
-
And a lot of these columns that we see
-
are here for Lucid's sake,
-
so that they know the particular state
-
that the model is within,
-
whether or not any information has been mutated on it,
-
whether or not it's been persisted,
-
whether or not it's a part of a transaction,
-
things like that.
-
The information that we care about
-
is going to be within the attribute section,
-
where we can see that the name of the underlying record
-
that was created is user, just as we defined it.
-
The ID was assigned as one.
-
And the created at and updated at are date times,
-
specifically Lux and date times,
-
where the current time is 2024, February 25,
-
at about UTC 143.
-
Now, if you're wondering whether or not
-
to actually access the name of the newly created model
-
that we have here,
-
whether or not we're going to have to do
-
role.$attributes in order to read from the name.
-
The answer there is no.
-
The Lucid model will place accessors
-
so that we can access the attributes of the model
-
directly off of the model instance.
-
So for example,
-
so we didn't actually plop the return value here
-
instead of a variable.
-
So let's go ahead and create another role and do that.
-
So let's do const admin equals await models,
-
role create.
-
And for this one, we'll give it a name of admin.
-
And let's go ahead and hit enter on there.
-
Now you'll see that the role wasn't plopped out.
-
That's because we've plopped it onto a variable called admin.
-
So now if we type out admin,
-
there's our newly created admin model
-
with the attribute name of admin.
-
So while we could do admin.$attributes.name,
-
and that will work A-okay.
-
Instead, we can use the accessors
-
that Lucid will provide on the model itself
-
to access the name directly
-
and any attributes that we have on the model
-
directly as well.
-
So we can do just admin.name,
-
and we'll get admin there as well.
-
Or we can do admin.id, admin.createdAt,
-
so on and so forth.
-
And we're gonna get back the direct values
-
as they are within the $attributes object.
-
And since our createdAt is an instance
-
of a LuxonDateTime,
-
additionally, we could do admin.createdAt.
-
And we can work with that LuxonDateTime
-
directly off of here.
-
So we could do to format,
-
maybe month, month, month, day, day, comma,
-
year, year, year, year.
-
And that should print out, I believe,
-
F-E-B for Feb 25, 2024.
-
So if we go ahead and hit enter there,
-
indeed, there we go.
-
So Feb 25, 2024.
-
So you can work with the LuxonDateTime
-
there directly as well.
-
And as a reminder,
-
if we jump back into our text editor,
-
the reason why that's being converted
-
into a LuxonDateTime is because we've chained off
-
of our column decorator dateTime,
-
which informs Lucid that we want this converted
-
into a LuxonDateTime.
-
And it was auto-created
-
because we passed auto-created to that.
-
And if we were to update our record as well,
-
the updatedAt will auto-update
-
because we've assigned that here as well.
-
Cool, so we can hide this back away
-
and let's give that a try.
-
So let's go ahead and update our admin record.
-
Maybe we want it to be fully spelled out.
-
So since we already have an instance
-
of our admin role model defined
-
in a variable called admin,
-
all that we need to do to update this
-
is change a value on the model itself.
-
And Lucid will track that via the attributes
-
and the original objects that are on the model itself.
-
It's going to track that difference
-
and then we can persist it,
-
which will sync the difference into the database.
-
So if we go ahead and do admin.name equals,
-
and let's type out administrator.
-
There we go, hit enter there.
-
Let's go ahead and print out our admin one more time
-
and let's scroll up slightly.
-
So you'll see the underlying attributes
-
has reflected our change.
-
It has our name of administrator,
-
but the original attributes has a name of admin still.
-
The direct accessor that we have available
-
on the model itself is right down here.
-
And that too has been updated to administrator
-
as it's referencing the underlying attributes here.
-
Lucid will now be able to discern
-
whether or not any changes have been made
-
because there's a difference between the attributes
-
and the original values
-
whenever it pulled the value from the database.
-
So if we scroll back down,
-
we can now do await admin.save
-
to persist that change into the database.
-
So we'll go ahead and hit enter there.
-
And now if we look at the change that happened there,
-
if we scroll up once more,
-
we'll see that our attributes
-
and our original values match one to one,
-
representing that the change has been made
-
inside of the database.
-
Furthermore, if we open up pgadmin,
-
run this query once more,
-
hitting the play button right up here,
-
we're gonna see that we get back our user record
-
and our administrator record inside of our table.
-
Deleting information using our Lucid models
-
is fairly similar as well.
-
So let's go ahead and delete our admin value.
-
So let's await admin.
-
All that we need to do is call a delete method
-
on the model instance itself,
-
and that will delete it out of the database automatically.
-
So just to enter there.
-
Since admin's persistent inside of a variable,
-
we'll still have access to it.
-
So we can print it out once more, scroll up.
-
Everything still looks for the most part the same.
-
However, one thing has changed.
-
The is deleted flag has been changed from false to true,
-
meaning that it has indeed been deleted
-
inside of our database.
-
And we can confirm that by opening pgadmin,
-
hitting the play button once more to run that query,
-
and our administrator is now no longer in there.
-
So we can make use of these static model methods
-
to easily perform CRUD operations against our database
-
anytime that we need.
-
Introduction
-
Fundamentals
-
2.0Routes and How To Create Them5m 23s
-
2.1Rendering a View for a Route6m 29s
-
2.2Linking Between Routes7m 51s
-
2.3Loading A Movie Using Route Parameters9m 17s
-
2.4Validating Route Parameters6m 6s
-
2.5Vite and Our Assets6m 38s
-
2.6Setting Up Tailwind CSS9m 5s
-
2.7Reading and Supporting Markdown Content4m 32s
-
2.8Listing Movies from their Markdown Files8m 51s
-
2.9Extracting Reusable Code with Services7m 4s
-
2.10Cleaning Up Routes with Controllers4m 52s
-
2.11Defining A Structure for our Movie using Models9m 38s
-
2.12Singleton Services and the Idea of Caching6m 11s
-
2.13Environment Variables and their Validation4m 16s
-
2.14Improved Caching with Redis10m 44s
-
2.15Deleting Items and Flushing our Redis Cache6m 46s
-
2.16Quick Start Apps with Custom Starter Kits6m 28s
-
2.17Easy Imports with NodeJS Subpath Imports8m 40s
-
-
Building Views with EdgeJS
-
3.0EdgeJS Templating Basics8m 49s
-
3.1HTML Attribute and Class Utilities6m 9s
-
3.2Making A Reusable Movie Card Component10m 24s
-
3.3Component Tags, State, and Props4m 53s
-
3.4Use Slots To Make A Button Component6m 56s
-
3.5Extracting A Layout Component5m 13s
-
3.6State vs Share Data Flow2m 59s
-
3.7Share vs Global Data Flow6m 7s
-
3.8Form Basics and CSRF Protection6m 13s
-
3.9HTTP Method Spoofing HTML Forms3m 3s
-
3.10Easy SVG Icons with Edge Iconify7m 57s
-
-
Database and Lucid ORM Basics
-
4.0Configuring Lucid and our Database Connection4m 3s
-
4.1Understanding our Database Schema9m 35s
-
4.2Introducing and Defining Database Migrations18m 35s
-
4.3The Flow of Migrations8m 28s
-
4.4Introducing Lucid Models5m 43s
-
4.5Defining Our Models6m 49s
-
4.6The Basics of CRUD11m 56s
-
4.7Defining Required Data with Seeders11m 11s
-
4.8Stubbing Fake Data with Model Factories13m 48s
-
4.9Querying Our Movies with the Query Builder15m 30s
-
4.10Unmapped and Computed Model Properties3m 24s
-
4.11Altering Tables with Migrations7m 6s
-
4.12Adding A Profile Model, Migration, Factory, and Controller2m 57s
-
4.13SQL Parameters and Injection Protection9m 19s
-
4.14Reusable Query Statements with Model Query Scopes8m 11s
-
4.15Tapping into Model Factory States9m 15s
-
4.16Querying Recently Released and Coming Soon Movies4m 59s
-
4.17Generating A Unique Movie Slug With Model Hooks7m 59s
-
-
Lucid ORM Relationships
-
5.0Defining One to One Relationships Within Lucid Models5m 49s
-
5.1Model Factory Relationships2m 54s
-
5.2Querying Relationships and Eager Vs Lazy Loading5m 17s
-
5.3Cascading and Deleting Model Relationships5m 16s
-
5.4Defining One to Many Relationships with Lucid Models6m 56s
-
5.5Seeding Movies with One to Many Model Factory Relationships5m 24s
-
5.6Listing A Director's Movies with Relationship Existence Queries8m 41s
-
5.7Listing and Counting a Writer's Movies8m 41s
-
5.8Using Eager and Lazy Loading to Load A Movie's Writer and Director5m 18s
-
5.9Defining Many-To-Many Relationships and Pivot Columns9m 48s
-
5.10Many-To-Many Model Factory Relationships4m 50s
-
5.11A Deep Dive Into Relationship CRUD with Models18m 5s
-
5.12How To Create Factory Relationships from a Pool of Data13m 55s
-
5.13How To Query, Sort, and Filter by Pivot Table Data9m 47s
-
-
Working With Forms
-
6.0Accepting Form Data12m 15s
-
6.1Validating Form Data with VineJS9m 29s
-
6.2Displaying Validation Errors and Validating from our Request7m 16s
-
6.3Reusing Old Form Values After A Validation Error2m 3s
-
6.4Creating An EdgeJS Form Input Component5m 28s
-
6.5Creating A Login Form and Validator5m 1s
-
6.6How To Create A Custom VineJS Validation Rule9m 7s
-
-
Authentication & Middleware
-
The Flow of Middleware7m 49s
-
Authenticating A Newly Registered User4m 14s
-
Checking For and Populating an Authenticated User2m 10s
-
Logging Out An Authenticated User2m 24s
-
Logging In An Existing User6m 54s
-
Remembering A User's Authenticated Session6m 55s
-
Protecting Routes with Auth, Guest, and Admin Middleware5m 36s
-
-
Filtering and Paginating Queries
Join The Discussion! (0 Comments)
Please sign in or sign up for free to join in on the dicussion.
Be the first to Comment!