🕰️ Chapters
- Adding the Create Movie Button
- Defining All Resource Routes for Our Movies
- Planning Our Movie Create Fields
- Querying Select Options
- Defining Our Movie Create Page and Form
- Creating and Defining Our Movie Store Validator
- Creating An Exists Helper Validator Rule
- Finishing Our Movie Store Validator
- Validating Dates and Transforming to Luxon DateTime
- Validating and Storing our Movies
- Testing Our Movie Store Form
Allowing Admins to Create Movies
In this lesson, we'll allow our administrators to create movies via our admin panel. We'll walk through getting the form set up, validated, and our movies created.
- Author
- Tom Gobich
- Published
- Jun 12
- Duration
- 16m 39s
![Tom Gobich](/img/1/profile/avatar_1703713552864.jpg)
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
Allowing Admins to Create Movies
-
[MUSIC]
-
For our admin movie actions,
-
let's start by adding the ability to create a new movie inside of our application.
-
So we'll start by adding in a create movie button right over there.
-
So scroll on up to where we have our H1,
-
wrap this inside of a div class flex justify
-
between and add a gap of four there for safety sake.
-
For our H1, and then we'll do a div and add a button into it that goes to an href,
-
and we'll put a pound there for right now because we don't have the route created,
-
and our button, and we'll do create movie,
-
and we'll probably also want this to be items center there as well.
-
Next, let's go ahead and create the route for our create movie page.
-
So let's jump into our routes file,
-
and then let's do router.get/movies create admin movies controller,
-
and we have our create method there as movies create.
-
Now, let's pause here for a second.
-
So if you recall to when we created this controller,
-
we created it as a resourceful controller,
-
and there's also resource routes.
-
It's my personal preference to list out the routes individually,
-
but I know that may not be everybody's preference.
-
So an alternative that we could do to listing them out
-
individually is router.define this as a resource route group.
-
You define the resource name,
-
so that's our movies,
-
and then provide in the controller that handles the resource,
-
which is our admin movies controller,
-
and that takes care of the rest.
-
So we can remove those,
-
and now we have a route defined for each of our resource actions.
-
For example, if we stop our server,
-
clear that out, and do node ace list,
-
there is an actual list command called list routes.
-
I don't think we've taken a look at this quite yet.
-
Maybe we did early on, but let's clear this out.
-
Node ace list routes,
-
hit enter on that, and we're going to get a list of all of our defined application routes.
-
Right down here are all of the routes being defined via
-
that one line where we're doing router.resource movies.
-
It's prefixing all of the defined routes with movies.
-
For our create route, it's doing /create.
-
For our show, edit, update,
-
delete, and destroy routes,
-
it's appending on an ID route parameter,
-
and on edit, it's making it unique by doing /edit there as well.
-
Additionally, you can see each of these get a name too.
-
So we have our admin movies index,
-
the same as we had it before,
-
admin movies create, the same as we had just manually defined it.
-
We also have our store, show, edit,
-
update, and destroy names as well.
-
You can see put and patch get the same route name as they're discerned via their HTTP action.
-
So we can clear that out, boot our server back up,
-
so npm run dev.
-
So we should be done defining our routes for our movies resource actions.
-
Meaning that we can now jump into our movies controller.
-
For our admin section,
-
scroll down to where we have the create method,
-
grab the view, return view,
-
render, pages, admin, movies,
-
and we'll create a create page.
-
We're going to want to provide some things in.
-
So first, let's go ahead and take a look at our movies model
-
to remind ourselves what all we have on here.
-
So we're going to need to provide the status options as a select,
-
so that we can select the status to apply to a movie.
-
In a real-world application,
-
it would be great to just pull this writer and director in as an autocomplete.
-
So as you type out, it will fetch and provide options in.
-
That behavior is not quite manually supported by the browser,
-
and we would need to pull in a third-party package to support that on the browser side.
-
So we're going to bypass that approach here today,
-
and just provide our sinister as a select list,
-
so that we can select our writer and director IDs there.
-
Then we'll have a title, our slug will be auto-generated from the title,
-
summary, abstract, poster URL,
-
release that, and that looks like it.
-
So that we don't have to remember that,
-
what I'm going to do is drag this off to the right-hand side,
-
and we can nudge it in there a little bit to make it smaller,
-
just so that we have the property names here to
-
reference and remind ourselves what we need.
-
So we're going to need the statuses.
-
So const statuses equals await movie status.
-
We can import that model query,
-
order by, let's just order it by the movie status name.
-
Then we'll also want to get our sinist.
-
So sinist equals awaits,
-
import our sinist controller there,
-
query, and let's order them by,
-
we'll just do their last name.
-
That should be all that we need to provide in for our form.
-
So we have our statuses and our sinists.
-
So let's go create our movies create page.
-
So let's scroll on down to where we have our admin movies directories,
-
right-click that movie directory,
-
new file, add in create.edge@layoutadmin,
-
and that layout, do an h1,
-
create a new movie,
-
add a form in, method of post,
-
and an action reaching for a route admin movies store method,
-
and that form out.
-
We're going to want to add in our CSRF field,
-
and let's start with a form,
-
input label will be title,
-
name will be title,
-
the type will be text, which is our default input type.
-
So we'll leave that out.
-
So that should be all that we need for our title input.
-
Can actually go ahead and just collapse that down to a single line there too.
-
So then we have our form.input,
-
the type for this one is going to be select,
-
label will be status,
-
and the name we'll do as movie status ID,
-
matching how we have it defined on the model.
-
We'll end our input and add in our option slot.
-
So do at each status in statuses,
-
go ahead and end our each,
-
add in an option value,
-
status ID, and then the status.name as the display value.
-
Next, we have our writer and director.
-
So we can go ahead and give this select for our status copy,
-
paste it in once,
-
switch status to writer,
-
switch the name to writer ID,
-
and rather than looping over our statuses,
-
we're going to be looping over our synists with a synist there.
-
Give that a copy because it's hard to type and paste that in for the ID,
-
and rather the name that should be named there.
-
Once we have that applied, we can give that a copy once more,
-
paste it in there, and switch writer to our director.
-
So we'll have a director ID,
-
and then the options should remain the same for that.
-
Then we have our summary and abstract.
-
So we can do @forminput type,
-
and I believe we added support for a text area.
-
If not, we'll add that in here momentarily,
-
but I believe we did.
-
We'll have our label of summary and name of summary, just like so.
-
Then we can give that one there a copy as well,
-
paste it in, and switch summary to our abstract.
-
Now, an abstract is something that you would probably want to
-
add a WYSIWYG into your application for,
-
what you see is what you get editor.
-
Those can take time to actually add into an application.
-
We're going to cover how you can go about that in a separate series later on,
-
but for now, we're just going to leave this here as a text area.
-
Then it looks like we have our release that as the last field that we'll need.
-
Let's go and scroll up and put that underneath the title.
-
So we can use the native browser input for that.
-
So we can do @forminput type,
-
and it looks like we have this as a date time,
-
but let's just worry about the date in terms of actually setting up a value.
-
So name, give that a name of released at.
-
Again, we're just naming all of these fields to
-
match how we have them defined in the model,
-
just to simplify taking our form data,
-
validating it, and passing it directly into our movie model as the property values.
-
We need a label on this one here as well.
-
So label and we'll do release date there.
-
Let's scroll on down to the end and give ourselves a submit button.
-
So @button, type, submit,
-
end our button, and create movie there as the action.
-
We do also have that poster URL.
-
Take care of that here in the next lesson.
-
Let's first just see what we get.
-
So let's jump into our create movie page.
-
Oops, forgot to break up the route.
-
Let's go break that up real quick.
-
Jump into our movies index page right there,
-
route, admin, movies, create.
-
There we go. So now we should be able to jump into our browser and hit create movie.
-
There we go. Could use some spacing,
-
but we do indeed get our form here.
-
We have our date input right here.
-
For browsers, you can still click on the little calendar icon
-
and get a nice little date selector right there.
-
So that's nice.
-
For our statuses, we have casting, post-production,
-
production, released, and writing.
-
For our writers and directors,
-
we have a list of all of our cinests, just like so.
-
So everything there seems to be working A-okay.
-
Let's go ahead and minimize that back away.
-
Scroll up to our form.
-
And to add some spacing in here,
-
I think what we'll do is we'll just do class, flex, flex, call, and gap of form.
-
So we'll just use flex gap there to add spacing
-
in between each of our little form groups there.
-
Okay, cool.
-
So next we need to do our validator
-
and then actually create our movie.
-
So we do already have a movie validator file.
-
We could reuse this here if we wanted to,
-
or we could create a brand new file
-
specifically for our admin movie section.
-
What would probably make the most sense
-
is to rename this validator file to movie filter validator,
-
since this is specifically for our movie filters.
-
So I think what we'll do is we'll right-click this file,
-
rename, and append in filter to the end of that file name.
-
And Visual Studio Code's gonna ask us
-
whether or not we want to update the existing imports
-
for this file to match what we've renamed this file to.
-
Go ahead and hit yes for that
-
so that we don't have to worry about it.
-
The only thing it doesn't do that I wish it would do
-
is automatically save for us.
-
So you can see movie service right up here,
-
just switch to needing to be saved.
-
So we can jump into here.
-
This may be the only file where we were using it
-
since we normalized everything into this service.
-
So we can go ahead and give that a save, close that back out,
-
which should now give us room to do command control P,
-
make validator, and we'll create a brand new movie validator.
-
So now we have a validator for our movie filters
-
and a validator specifically for our movie itself.
-
So let's export const movie store validator
-
equals vine compile, and we'll do vine object.
-
We're gonna have a title of type vine string,
-
and let's just roll through and put the types in first,
-
and then we'll add in additional checks.
-
So let's scroll on up to make sure we get everything here.
-
We'll leave slug out because that's gonna be auto-generated
-
by our model, have our summary type vine string,
-
our abstract as type vine string,
-
our writer ID as type vine number,
-
director ID of type vine number,
-
and let's see, status ID vine number.
-
And let's see, here we have our poster URL,
-
which we'll take care of here in the next lesson,
-
and our released at.
-
So released at vine, and that will be a date.
-
Okay, so for each of these,
-
we're gonna have varying additional validations to do.
-
And for that, it would be great to reference
-
what we've defined on our database as requirements.
-
So we'll scroll down to our database, migrations,
-
and let's go find our movies table migration
-
and open that up here on our right-hand side.
-
Clips that back down, scroll back up there,
-
and minimize that back down too.
-
Okay, so for our status writer and director IDs,
-
these all reference their relationships,
-
and all three are not nullable,
-
so they're not gonna be optional.
-
So we're gonna wanna add exists checks to those three.
-
And if you recall, Mac, we created an is unique utility
-
so that we didn't actually have to write a unique query
-
over and over again for our validation.
-
So let's go ahead and do that for unique as well real quick.
-
So we'll scroll on down.
-
We had that within our start directory, rules.
-
We have this unique file here.
-
Rather than squeezing an additional rule
-
inside of this file, let's go ahead and create a new file.
-
So let's right-click our rules directory, create a new file,
-
and add in exists.ts,
-
and paste what we had for our unique
-
within here for our exists check.
-
For this one, I think we only care about numbers for it,
-
so we'll go ahead and remove strings.
-
And we're gonna want to update the name
-
from is unique to is exists,
-
or whatever you feel is applicable there.
-
My goal with the naming here is just to not overwrite
-
the currently existing unique and exist validator methods
-
in case we should ever need them.
-
Our query here remains the same,
-
but if we don't get a result,
-
that's when we wanna report an error.
-
So we'll flip our result check there
-
and switch our error to something like
-
value for field does not exist,
-
and switch the reported error name to is exists.
-
Scroll down a little bit.
-
And now we have our is exists rule,
-
and create that with our is exists method.
-
Scroll down a little bit further.
-
We're not gonna apply this to strings,
-
but rather just numbers, so we'll remove the bind string
-
and switch our bind number to is exists.
-
Scroll down a little bit further.
-
Again, we'll remove the string,
-
so we'll get rid of that macro,
-
and switch this macro to is exists,
-
and use our is exists rule.
-
Awesome, so we manually created this exists preload file,
-
so we also need to manually register it
-
inside of our javasrc.ts file.
-
So within here, we'll scroll down
-
to where we have our preloads,
-
and add in an additional item,
-
port import start rules exists.
-
Up till now, we've used ACE to create these files for us,
-
and it automatically registered it
-
within here for us as well.
-
So that's why we need to manually do it here,
-
because we manually created that file.
-
Awesome, so now we can jump back
-
into our movie validator, and do .is exists here,
-
provide in our options of our table.
-
This will be our Cineasts table,
-
and the column will be ID,
-
and give that a copy,
-
and paste it on our writer there as well.
-
And then for our status, this will be is exists table
-
movie_statuses, and column of ID.
-
All right, cool, so both of those should be good.
-
Then we come down to our title,
-
which is not nullable,
-
and has a max length of 100 characters.
-
So we're gonna want to validate against that as well.
-
So we'll do .max_length 100 there
-
to enforce that limitation.
-
Our slug is auto-generated, so we'll skip over that.
-
Our summary is not nullable,
-
but it does default to an empty string,
-
meaning that we can make it optional
-
here inside of our validator.
-
Our summary is also a type of string here,
-
so it's going to have a default max length
-
of whatever your database's max length for string is.
-
I believe in most cases,
-
a max length of 255 should be a safe bet there,
-
or our summary, and then our abstract is a type of text.
-
So we don't really need to worry
-
about adding a max length in there,
-
as it can go as long as needed.
-
We're omitting our poster URL for now.
-
We have that as not nullable
-
and defaulting to an empty string,
-
so that is technically optional as well.
-
Then we have our release_stat, which is nullable.
-
So we'll wanna make sure that that is optional as well.
-
And we're going to want to get this back
-
as a lux in the time.
-
So we'll also want to transform the value here.
-
This transformer, we can provide a callback to
-
that takes in our value as a JavaScript date,
-
which we can return date time,
-
dot from JS date, provide our value in, and there we go.
-
So now we'll have a lux and date time returned
-
after we validate our release_stat, if it has a value.
-
Now, since we're using a native date input,
-
the format should come up as year, year, year,
-
month, month, day, day, separated by hyphens,
-
which should be accepted by default from Vine here.
-
But if you have any issues,
-
you can specify additional formats into here as well.
-
You can specify that as an array as needed.
-
So you could do something like year, year, year,
-
month, month, day, day, which is I think supported by default
-
and I believe that's how our dates will be sent up.
-
So we should be able to omit that altogether.
-
Then we'll jump up into our movies controller
-
or our admin.
-
So right here, scroll down to our store method.
-
We'll do const data equals await request validate
-
using our movie store validator.
-
Then we'll do const movie equals await movie,
-
import that if we need to,
-
it looks like it's already imported, dot create
-
and provide our data in.
-
So with that in place,
-
we should be good to go ahead and just response
-
and return response redirect.
-
And let's go to route admin movies index.
-
And of course, since we're not using our movie,
-
we can go ahead and get rid of that variable there.
-
All right, let's go ahead and jump into our browser
-
and test it out.
-
So first and foremost,
-
let's just try to hit create without specifying anything.
-
And you can see, we get the title must be defined
-
and our abstract must be defined.
-
Apart from that though,
-
it looks like everything's working a-okay.
-
So let's go ahead and try my cool movie one.
-
Let's provide in a release date
-
to make sure that that's working okay.
-
Let's say maybe the 26th of June.
-
Let's say it is in post-production.
-
Let's say the writer is Leila Abernathy
-
and the director is Trudy Berner.
-
We'll leave our summary out,
-
but we will provide an abstract of,
-
this is a cool movie right here.
-
And let's go ahead and hit create.
-
And it looks like something failed,
-
but I'm not quite sure what.
-
We got redirected back here with our old values,
-
but no error was displayed.
-
All right, so let's jump back into our create page here
-
and let's see what's going wrong.
-
So let's add an inspect/messages.all
-
and let's try this all again.
-
So we'll do test movie,
-
do everything the exact same as we had it before,
-
not necessarily with the exact same values.
-
And let's hit create to see what's going wrong.
-
All right, so here's all of our old values
-
and then here's our error status ID.
-
The status ID must be the,
-
oh, in our validator, we called it status ID,
-
but on our model and here in our field,
-
it is movie status ID.
-
Okay, so let's hide that back away,
-
jump back into our movie validator.
-
Right here, it's status ID,
-
but we need to call this movie status ID.
-
Or did we call it just status ID on our model?
-
Oh, we sure did.
-
Okay, so let's leave that as status ID then.
-
And it's our form that needs to change.
-
So let's go down to our movie status select
-
and switch that to just status ID.
-
All right, now let's jump back into our browser.
-
My cool movie one, select the date here.
-
So we'll do the 26th again,
-
say that this is post-production,
-
Leila Abernathy, Trudy Bernard,
-
and this is a cool movie right here.
-
Create our movie and there we go.
-
So now we see the most recent movie
-
that we have inside of our movies list
-
is now my cool movie one,
-
it's in post-production,
-
the writer is Leila Abernathy
-
and the director is Trudy Bernard.
-
And we're not worrying about our cast or crew members
-
for this particular format at this point.
-
Introduction
-
Fundamentals
-
2.0Routes and How To Create Them5m 24s
-
2.1Rendering a View for a Route6m 30s
-
2.2Linking Between Routes7m 52s
-
2.3Loading A Movie Using Route Parameters9m 18s
-
2.4Validating Route Parameters6m 7s
-
2.5Vite and Our Assets6m 39s
-
2.6Setting Up Tailwind CSS7m 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 8s
-
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
-
7.0The Flow of Middleware7m 49s
-
7.1Authenticating A Newly Registered User4m 14s
-
7.2Checking For and Populating an Authenticated User2m 10s
-
7.3Logging Out An Authenticated User2m 24s
-
7.4Logging In An Existing User6m 54s
-
7.5Remembering A User's Authenticated Session6m 55s
-
7.6Protecting Routes with Auth, Guest, and Admin Middleware5m 36s
-
-
Filtering and Paginating Queries
-
8.0Creating A Movie List Page3m 43s
-
8.1Filtering A Query By Pattern Likeness7m 9s
-
8.2Filtering Our List by Movie Status5m 47s
-
8.3How To Apply A Dynamic Sort Filter To Your Query7m 12s
-
8.4Joining SQL Tables To Order By A Related Column4m 49s
-
8.5Validating Query String Filter Values7m 24s
-
8.6How To Paginate Filtered Query Results9m 15s
-
8.7Pagination First, Last, Next, and Previous Buttons4m 3s
-
-
User Watchlist
-
9.0An Alternative Approach to Many-To-Many Relationships4m 56s
-
9.1Toggling A Movie in an Authenticated User's Watchlist9m 56s
-
9.2Listing and Filtering User Watchlist Items7m 31s
-
9.3Allowing Users To Toggle A Movie As Watched4m 44s
-
9.4Filtering By User's Watched Status6m 7s
-
9.5Defining A Composite Unique Constraint4m 47s
-
9.6Persist Filters Easily with Lucid's Query String Method3m 58s
-
-
User Profiles
-
10.0How to Create and Fix Missing User Profiles in Your Application7m 37s
-
10.1Using Dependency Injection to Update A User's Profile9m 46s
-
10.2Saving All Or Nothing with Database Transactions5m 15s
-
10.3Uploading and Displaying User Avatars15m 29s
-
10.4Displaying A User's Profile6m 1s
-
10.5Filtering, Preloading, and Sorting By Relationship7m 6s
-
-
Admin Panel
-
11.0Creating An Admin Layout7m 14s
-
11.1Counting Stats for our Admin Dashboard5m 43s
-
11.2Paginated Admin Movie Table13m 2s
-
11.3Allowing Admins to Create Movies16m 39s
-
11.4Allowing Admins to Update Movies and Clear Values13m 27s
-
11.5How To Use One Form to Create or Edit Movies5m 32s
-
Uploading Movie Cover Images in our Create or Edit Form10m 29s
-
Using A Wildcard Route Param to Download Storage Images7m 57s
-
Posting Objects, Arrays, and an Array of Objects in HTML Forms26m 26s
-
Managed Transactions and Syncing Movie Cast Members15m 55s
-
Allowing Admins to Delete Movies and their Relationships7m 42s
-
Thank You for Watching!0m 31s
-
Join The Discussion! (0 Comments)
Please sign in or sign up for free to join in on the dicussion.
Be the first to Comment!