Build a YouTube Clone with Next.js 15: React, Tailwind, Drizzle, tRPC (2025)

117.34k views109479 WordsCopy TextShare
Code With Antonio
⭐️ Source Code: https://dub.sh/xp03EDA 🎬 PART 2: https://youtu.be/ig26iRcMavQ 🎨 Assets (Free): htt...
Video Transcript:
hey there my name is Antonio and in this course I'm going to show you how to build a fully functional YouTube clone using modern web Technologies our video player comes with all the essential interactions users can adjust volume switch quality settings and even toggle captions while watching users can like videos to show their appreciation they can also grow their feed by subscribing to channels they enjoy the comment section below is where the community Comes Alive users can share their thoughts by leaving comments these comments aren't just static users can like them to show support and
the conversation doesn't end there users can reply to comments creating engaging discussions throughout the community but what you've seen so far is just scratching the surface creators can access their Studio a dashboard where they manage all of their content the video goes through multiple processing stages within moments the status changes to ready as our infrastructure powered by Max generates video thumbnails optimizes the video quality and creates transcriptions we've built a versatile thumbnail management system where creators can upload custom thumbnails use AI generation or restore previous versions but here's where it gets even more exciting in
with just a click creators can leverage AI to generate optimized titles and descriptions based on the video transcriptions they can monitor these background processes in real time through the technical dashboard once everything is set and AI has worked its magic creators can review the generated content and publish their video with confidence besides the home feed we are also Al going to have trending and subscribed feed as well as custom user profiles where you can find specific videos from one channel another useful functionality we are going to develop are going to be playlists creating a new
playlist is as simple as clicking the plus icon and once created these playlists become powerful tools for Content organization users can browse to their feed and add any video to their playlist using the convenient drop down menu this makes it easy to build collections of tutorials and organize content exactly how they want users have full control over their playlists they can remove individual videos or delete entire playlists if they no longer need them besides all of that the platform also maintains automatic playlists like watch history and liked videos and of course the entire application is
going to be fully responsive you users can enjoy the same seamless experience whether they're watching videos managing playlists or even uploading content from their mobile devices this is my most comprehensive course to date spanning 38 chapters and over 24 hours of in-depth content throughout this journey we'll dive deep into advanced concepts you'll learn how to combine nextjs 15 and react 19 with trpc mastering servers side data prefetching and leveraging suspense in client components we'll Implement a module-based file structure that scales but where we'll really push the boundaries is database management using postgress with Drizzle or
you'll Master query building and advanced SQL Concepts you'll learn different types of joins how to leverage Common Table expressions and subqueries and most importantly how to write powerful combined queries instead of Chang multiple database calls this isn't just about a YouTube clone it's about mastering modern web development and now without further Ado let's get started if you wish to support my work consider becoming a premium member on code with antonio.com and you will gain access to all current and future source code as well as a growing library of over 180 hours of content so in
this tutorial I'm going to take a a bit of a different approach as opposed to my other tutorials and I'm going to be starting each of my chapters with a whiteboard summary of what our goal is for the certain chapter for example this is chapter one the setup in this chapter our goal is to configure the environment which means choose a runtime choose a package manager establish some basic bun commands and create the basic nextjs project and wrap it up with some useful vs code extensions I got a lot of feedback from all of you
that sometimes it's not exactly clear what or why we are doing something in my chapters so I hope that this kind of approach will make it easier to understand and follow along I will especially try and focus on some UI Elements which I've gotten feedback that it kind of seems a mystery to what it will be until it's finished so I will be using this whiteboard at the beginning of each chapter to add some screenshots here so that you can see what our final build for this specific chapter will be right so this first chapter
is quite simple our goal is to just get a project running as I've already said we first have to choose our runtime which will either be nodejs or bun and we also have to choose our package manager which can be npm pnpm yarn or bun as you've already noticed I have highlighted bun in these two places and in my little Legend here I've marked that as highly recommend it why do I recommend bun for this tutorial first of all I recommend it because you will get the same environment as I do basically I will be
using bun in this tutorial which means that I will be running bun commands so if you want to follow along exactly as I do it will be much easier for you to use bun as well the second reason is we're going to have to run some scripts which will have typescript inside of them and some es6 imports now I am aware that in the newest versions of nodejs uh we are capable of doing just this but I'm certain that we can do it with bun I'm not 100% sure if node.js can do it now I've
seen some updates around this but I am certain that we can do it with bun so for example we're going to add a little seed script to add some uh elements to our database and we can easily run that script using bun whereas with node at least in previous versions We would have to add a proper typescript configuration and we would have to change es6 Imports to the old require methods and all of those things just to run a simple script so for that reason again bun gets a point for me and the last reason
is we will have less issues in regards to dependency issues when it comes to react 19 so if you are not aware react 19 recently came out and while that is great not all packages that we will be using in this tutorial have upgraded their peer dependencies to uh use react 19 what happens then is that certain package managers will throw errors or Warnings when you try to install a package inside of your react 19 project which will be the case for this tutorial because we will be using react 19 for example npm will straight
up throw an error and prevent you from installing until you add a flag D- Legacy perer depths or D- Force this is something that I did in my previous tutorial Google Docs clone and it was just a hassle to write every single time yes you can store this Flags inside of a uh npmrc file but you can already see just how how much we're complicating just to install a simple package when it comes to yarn it will throw a warning but it will install the package but for bun it will simply work you won't even
notice that there are any dependency issues uh inside uh with react 19 and the package we are trying to install great so if you want to use bun you will have to visit bun bu. sh or simply Google bun runtime so yes bun is both a package manager and also a node compatible runtime so I'm going to of course show you uh everything I have installed in my system so you can follow along but go ahead and visit the website if you don't have bun if you have Linux and Mac OS you can use Curl
to install ban uh to install bun and if you use Windows go inside of your Powershell and go ahead and run this there are additional ways as you can install if you go into docs here uh I think maybe Windows users might have some questions especially if they don't use Powershell you can install it using npm as well also known as the last npm command you will ever need so go ahead and run this command if you are on a Windows and you don't use Powershell if you use Powershell you can just run this right
on Mac OS and Linux you can also do this you can also use Brew you can use docker or you can simply use uh curl right here once you've done that uh what you're going to do is just confirm that you have bun installed as you can see this is my current bun version I'm also going to show you my node version just in case if you don't have node um this is a bit of a conflicting advice I'm giving you now because I just told you we're going to use bun we're going to use
bun I would recommend you have node installed as well I think majority of people watching this tutorial will have node installed right but in case this is your first ever Venture with JavaScript you have chosen a pretty hard project to start with but I will support you through this journey go ahead and install node.js I think that installation is even simpler or as simple as bun just find the download latest long-term support So confirm you have node version confirm you have bun version you should not be getting errors for any of these next go ahead
and confirm you have npm just in casee right so when you install node you will also have npm installed uh and for bun uh well bun is both the environment I mean the runtime and the actual uh package manager so this confirmed that we have that right now let's go ahead and uh let's see so I'm just going to add some tiex here right so we selected our runtime here let me increase the opacity there we go uh we selected our package manager uh we learned why we're going to be using bun now let's establish
some basic bun commands and for this we can actually already start creating our nextjs project so here I am on the uh documentation page of nextjs and here we have some system requirements so first of all it says that we need to have node.js 18.18 or later so that's why I advise you to install node.js just in case um I usually don't don't tell this in my tutorials because I assume a lot of people already have this installed but just in case it's never wrong to have nodejs installed just make sure whatever version you see
here is not lower than whatever it's written on the documentation right here and you can see that the operating system supported our Mac OS windows and Linux right and we're going to be using this Command right here to install our nextjs project but before you go ahead and run this you will notice that it's using npx so this is uh no JS specific or npm specific command but I just told you we're going to be using bun in this tutorial so I will show you an alternative and we are also not going to be using
this at latest I will explain why in a second so let's go ahead and learn this whenever you see npx in bun world that's equal to bun X right and whenever you see npm install in bun world that's equal to bun add and then name of the package great so we just learned this basic uh bun commands right here so now let's say I want to see what is the latest version of this package because we want to install an explicit version here and not just use at latest because nextjs can introduce a lot of
breaking changes in a very short period so I don't want my tutorial to get outdated by the time you found it here so this is what we're going to do we're just going to copy this and instead of using uh npx we're going to be using bux and then we're just going to add create-- app at latest and then just D- version and there we go we now have the version so this is the version I will be using throughout this tutorial which means in order to run this command let's finally do it now npx
I'm sorry bun X create next app at 15.1.2 right here here so instead of latest we'll be using this specific version I'm going to call this project new tube like this I'm going to be using typescript I'm going to be using esent quick tip you can uh select this answer using arrow keys right so yes for typescript yes for slint yes for Tailwind yes for Source directory sometimes uh this will be defaulted as no so be careful move it to yes and then press enter make sure you select the app router this is absolutely crucial
because we will be using and leveraging server components very much in this tutorial uh I have not yet used turbo pack I've heard great things about it in development mode right so this is Turbo pack for development which uh is supposed to make your Dev environment much faster I would recommend selecting no for this tutorial purely because I will not be using it right so I cannot guarantee you how different of a project you're going to have if you select yes so keep it no for now I will not be customizing my import alas I
like the at sign if you don't know what an import Alias is I will show you that once we open up our project but just select no for now and there we go bun install will now be running and installing all of our dependencies there we go we now have installed our new project as you can see it's inside of my users and then whatever my folder was where I ran this Command right here B next create next app and inside of here we have new tube so I can now go inside of change directory
new tube like this and there we go you can see some uh files here and now we're of course going to open this up in Visual Studio code or whatever is your uh IDE of choice I would highly recommend going with VSS code simply so you can have all the same extensions as I will so I'm going to go ahead and click on open here and I will select new tube which is my newly created folder here if you get this prompt you can safely press yes I trust the authors here all right so now
let's take a quick peek at our structure here so outside of I mean in the root of our project we have all of these config files we will later revisit the Tailwind doc config.sys right so this configured tailwind and enabled Tailwind in this specific folders Pages components and app right but later on in the source folder we're going to add some new folders here which will not be covered by Tailwind by default so we're going to have to remember that and quickly go back here so we can learn how to enable that as well but
we're not going to concern ourselves with that right now I just want to bring your attention that uh even though we will mostly be working inside of the source folder here there are some useful things that we are going to Tinker with throughout this project right ensure that you have the Tailwind file ensure that you have the TS config uh basically everything that you see here you should have as well and of course we have our bun lock file because we opted to use bun inside of your Source folder you should have the app folder
which is basically our router so our app folder is a Reser resed folder in which there are certain uh reserved files like layout and page which are used to create routes right so if you go ahead right now and do Bun Run Dev or npm runev if you choose to use npm you can already visit this at Local Host 3000 so let's quickly go ahead here and visit this page I'm just going to zoom in a bit and there we go so I am currently uh past pm here so my entire project is in dark
mode your might be in light mode you might be seeing a white background here don't worry about that we're going to uh defold to one of those uh later uh great so we now have our basic project here but I don't want to end the chapter here purely because we added uh this add shatan UI so we learned how to use the exact version we got familiar with the structure and now we have to add shat ceni and uh just take a peek at some useful vs code extensions so let's go ahead and let's go
uh and for now I would recommend you shut down the app and instead go ahead and visit ui. shen.com or simply Google shaten UI and inside of here we're going to go inside of the docs here so what is shat ceni as you can see it is not a component Library it's a collection of reusable components that we can copy and paste into our apps and I honestly think this is my favorite way of building our own component library right so let's go ahead and head into installation let's select nextjs for the framework and now
we're going to go ahead and install this and as you can see here it will tell you something if you're using nextjs 15 see the nextjs 15 plus react 19 guide let's click on that just to see what's going on so you're going to see uh what's written here is exactly what I've told you in the beginning if you are using npm you can install shatan dependencies with a flag right so basically that's what I wanted to avoid the shaty and CLI will prompt you to select a flag when you run it but there are
no Flags required for pnpm bun or yarn right so what's happening react 19 at the time uh they were writing this was in release candidate now it's no longer in release candidate as far as I know it's completely out but this is still in progress basically all the other packages need to update their peer dependencies to include react 19 right so if you use npm this will happen you will get this kind of errors and then you have to choose either using force or Legacy peer deps so that's why I was highly recommending to use
bun because bun will not have any issues for this great we can now go back to the installation sorry for this hijacking I just wanted to explain once again so let's go ahead and select bun here and let's copy this command so bunx D- bun shatan at latest in it but before we run this this is what I will actually do I will just copy shatan at latest and I will do bonex shatan at latest D- version so I just want to see the version of the latest shaten and that is 2.1 1.8 great so
now we can go ahead uh and run this command here but instead of using at latest we will use at 2.1.8 so the reason I want you to do that is so you don't have any surprises right these things can change very fast and I want to ensure a certain level of longevity in my tutorials so no matter when uh you found this I want you to have the same experience so for the uh for this style you can select New York or the color as the base color you can select neutral you can uh
select yes for CSS variables right here and this should add some new components components. Json it should modify Tailwind config and it should update this global. CSS file and it should also create Source lib utils let's go ahead and quickly check that out now so as you can see I have six modified files here and all of those are exactly what we've written so we have the new components. Json which is basically a configuration file for shat CN we have modified package.json with all the necessary new uh files here we have modify Tailwind doc config.sys
if you go to the bottom here you will notice that there is an error here I've never had this error causing me any problems it did not cause problems with the build so I will leave it as it is I will not fix it or anything so let's see what else has changed global. CSS was modified as you can see and now what will happen What you're going to notice now is that you should no longer have a dark or light mode it should always be light mode so if you go to one run Dev
right now and if you revisit your project just make sure you refresh it there we go now it's in light mode if your was already in light mode you probably didn't notice any changes but for example for me I was in dark mode and now it is light mode great so you have officially and successfully installed shat CN as well and now let's wrap up the chapter by learning some useful vs code extensions here and just confirming whether you know Tailwind works and maybe learning how to add some components so uh let's go ahead and
do this let's go inside of source of our app folder and inside of page. DSX so what you see on this page is what's written inside of here what I'm going to do is I'm going to remove everything from my return method I'm going to remove the import and I will start from scratch with a little div here and a paragraph hello world like this so you should just see a small text hello world written here now let's try out some Tailwind so I'm going to give this a class name font bold and text Tex
rows 500 let's go ahead and try it out there we go hello world and text rows 500 like this now I'm going to go ahead and go inside of my extensions here and just go ahead and look at Tailwind CSS intelligence there we go so I would highly recommend that you have this uh extension I'm going to click install again like this and I will reload my window uh looks like something's happening yeah it it didn't exactly load the first time but now you can see that this extension tawin CSS intellisense which we have just
installed is very useful first of uh first of all you can already notice that it will tell you uh which a color this is in case you are not familiar with its variables but the second useful thing is that when you hover over something you can see the exact CSS so if you're not sure if you type a unknown uh unknown you know Stu you have no idea is this from Tailwind or not you can hover over it and if nothing pops up this is not Tailwind this is not a valid class name so that's
how you know if you have typos for example text roses you can see that this is obviously something wrong here because Tailwind is not telling you anything about it and you also have some you know Auto uh correct here like this great so Tailwind is confirmed to be working and now let's just wrap it up by installing uh the components we need from shat CN so in shaten UI here you can install each component individually right so you only use those that you actually need but it is actually much easier for us to install all
components at once simply because we're just going to do it now in this first chapter and we're not going to think about it for the rest of our tutorial so we don't have to you know remind ourselves oh what was the version that I was using right so this is what we're going to do then I'm going to shut down my project I'm going to do bux D- bun shaten at 2.1 uh 2.1.8 at-- all I believe this is the correct command and I think that this will add all the necessary uh components that uh
I will need there we go so you should now have all of these components added to your project It says 55 for me at least in this version right here it has also updated some config also updated some globals here so I'm going to do Bun Run Dev again I'm going to go inside of here and I will refresh my local host and I'm going to try in the root of my page here to import a home a button for example let's import button from components UI button so this is an alias this is what
I was talking about so instead of having to go uh dash dash components right obviously this isn't that all that horrible but if we were deeply nested in some folder we would have to do this this this right all the way to find it but with an alias we can just use this because Alias defaults to our source folder right so whenever you uh add the at sign in your import it will act as being in the source folder so you can then easily go inside of these components which was just created because of this
command we run and inside of here you can see the source code to every single component this is what I think is super cool with shaty Andi because the source code is not off fiscated inside of node modules as it is with other component libraries but instead you can make it your own so I'm just going to go ahead and remove the paragraph from here and I'm going to render this button and say click me like this and there we go we now have a button which says click me right here and I can go
ahead and give it a variant of destructive for example and now you can see it has a different variant I can also go directly inside of this button so what I did was I pressed command and pressed inside or if you're on Windows it's control and then uh go inside or you can go inside of source components UI and find the button and inside of here you can modify anything you want right for example inside of here we have the variants so if I were to add a new variant and give it a background of
purple purple 500 with a text of white that's it I can do that so now I'm going to go here and I'm going to change this to variant new and there we go that's my new variant right here that's how simple it is to build your own component library and you can do the same thing for the paddings the roundedness anything you can imagine the names of this if you don't like default destructive and want to call it you know some kind of different uh semantic way you can do that nothing is preventing you from
doing that and now you can remove this new style if you added it as I did and you can see that it will immediately throw an error here like hey you can't do that that does not exist great so that was a quick intro uh I believe we covered a lot about how we're going to be you know uh using all of this and we just added some useful vs code extensions here great I think this is a great start uh so what we're going to do in the next chapter is well you're going to
see in the Whiteboard which will come great great job in this chapter we're going to go ahead and build the basic layout for our app as you can see on this screenshot the basic layout will include our sidebar and our knv bar right here the full components will be the sidebar itself sidebar sections these and this and then sidebar items like home subscriptions and trending and history liked videos and all playlists then we're also going to develop a Novar with the search input and sign in component so this is the search input and this is
the sign in component in order to do that we're going to have to add this logo right here and I have prepared that right here in this Guist so you can download it using the link below or you can just use my source code and go inside of the public folder you're going to learn about the public folder in a minute so what we're going to do is let's start by adding this to our project right so I'm just going to go ahead uh and right click save image and download this and then what I'm
going to do or can I maybe just copy the image yeah I think I can also maybe copy it just go inside of your public folder and let's try I cannot paste it here so I'm just going to go ahead and drag it inside of my public folder there we go I'm going to rename this to just be logo.svg and there we go right so it looks like YouTube's logo but it is not right I just made it in uh in Photoshop uh great so we now have this logo right here and we can already
try it out on our homepage so I'm just going to go ahead and do BN run Dev and I'm going to refresh my page here to ensure that it is running and I'm going to use the image from next image right so we can remove the button import here and I'm going to give it a/ logo.svg with a height of 50 and a width of 50 and an out of logo like this so make sure logo. SVG is exactly what you have in your public folder here and you should be seeing the logo right here
great and then after that let's just for fun add NewTube text here and as you can see it doesn't exactly look uh like we are planning for it to look like which is a bolder font and a different font here so let's go ahead and learn how to change the font of our project so for that we're going to visit our source app folder layout. TSX this is also known as the root layout and inside of here they already specify uh this gased font so what we're going to do is we're going to remove this
we're not going to be using that font and we're going to import enter from next font Google so we're going to be using the inter font and we're going to remove these two variables and we're going to replace it with a single called iner we're going to use the iner class and we're going to execute it and let's give it subsets of Latin so now we are using Google fonts with xjs integration and we have that font inside of this constant so that we can do now is we can go inside of here and we
can simply remove everything inside and just just do enter the class name here like this so now this will use the new font right so what we can do already uh is we can go ahead inside of our page. DSX and give this NewTube a class name of text extra large and font semi bold and tracking of tight like this and there we go now it looks much better exactly as we're going to want it to be right here great so you've learned how to add an asset to your public folder you've learned how to
use an asset just make sure you have added the height and the width so this image component is uh requires the height and the width as you can see it's going to give you an error if you don't use it that's because the image component will automatically optimize images for you you can of course also use the normal uh image tag if that's what you want it's still going to work but it's not going to be as optimized so for this tutorial I will mostly use the image uh in some cases I'm going to use
the native uh image element but most of the time uh this is what we are going to use great so uh we just added our logo asset and now let's learn the basic app router folders right so I just want to check this let's get this out of the way great now let's learn basic uh app router folders okay uh so for example we are right now on Local Host 3000 this is our root page if I go instead of source app folder and if I create a new folder called feed for example let's imagine
that's how we're going to call our homepage right feed and if I create a new page. TSX inside it needs to be called page. TSX and it needs to be inside of the app folder like this and if I go ahead and simply export default page component and give it a div feed page like this what's GNA happen now so what's crucial here is that you do export default please don't forget to do this right save the file make sure it's saved and now go to Local Host 3000 feed and there we go you can
now see the feed page so that's how you create routes inside of nextjs 15 but there are a couple of more things that you can do for for example you can do a sub route and while we learn how to do a sub route let's also learn how to do a dynamic route for example let's do video ID inside of square brackets like this keep in mind that I have used the capital letter i for ID and inside is it the same thing page. TSX I'm going to go ahead and do another uh default export
here I'm just going to call it page it doesn't matter what the component it's called and I'm going to call this uh feed or let's call it video ID page so now as you can see this is not clearly defined what goes here all that we know is that it's a sub route of a feed so if I go to slash feed1 to3 it will load the video ID page if I go to 321 it will also load the video ID page if I go ABC it will do the same thing so it's Dynamic it
doesn't care what it's being past but here's what it's useful for so inside of page. TSX there is a way to extract that those pams and render it out here so we can easily do it by just by doing you know pams like this and then I can do video ID rams. video ID let's see if this will work there we go so you can see that it works but in case it doesn't work for you don't worry uh it probably means that nextjs has official deprecated this I say deprecated because this is not how
you should do it right it works and it's great because I can demonstrate how it works so if I go to slash feed one two 3 it will now show one two three right so this is basically a video IDE that's what it serves for it's going to give us something to query the database with right but first of all we have a problem if you go ahead and visit your server or your terminal your you're going to say this the route feed video ID used pam. video ID pams should be evaded before using its
properties and you can learn more here so Dynamic apis are now asynchronous this is a change that has happened in the nextjs 15 which means that whenever you're using a dynamic apis which are prams search prams cookies draft mode headers any of those things you need to await those things so this is what we have right now this works right perms perm. ID it's literally the exact thing we're doing p.v video ID but it will issue a warning it still works but this is not how you should do it so how you can fix it
is this is their solution if you are fixing this in a large code base so they added a code mod which goes throughout your entire project and fixes it for you we don't need that what we need to do is learn how to properly parse this so what we have to do is well first of all let's go an interface page props perams video ID let's mark this as string let's add page props here there we go no more errors right but still this doesn't fix our issue I just added the types what we have
to do is we have to mark this entire component as an asynchronous component and we have to give this a proper return type so this will be a promise which returns an object of video item and now we are getting the same error here that we are getting here now it makes more sense for us what we have to do so that's why you should always know what you're expecting so now let's do the folding let's extract the video ID from a weit rams and now let's use the video ID there we go what I'm
going to do now is I'm going to scroll all the way to the bottom and just leave a couple of empty spaces here I'm going to go to my local host and I'm going to refresh every still works fine and this time no errors at all so in order to use Dynamic apis like pams in this case the video ID right here you have to await them if this didn't immediately click for you don't worry because we will encounter this again and again and it's going to become more clearer the more we work with this
one thing that you're probably asking is how exactly can we async this right what exactly are is awaiting here right that may be confusing to you if you've never worked with xjs before well that's because this is actually a server component right and now I think you just have even more questions but you can try and do this server component if you go ahead and refresh this you're going to find that conso log right here in your terminal server component right so basically uh you can also you can also see that here in the inspect
element but you can also see that it says server here whereas if you were to mark this as use client first of all you're going to get a bunch of warnings here that's because client components cannot be asynchronous components client components cannot uh do this Dynamic API routes they can do it but they have to use a special hook for it right uh if you want to use client components in nextjs you have to mark your component as used client and I think a better way of explaining it is to uh go to another page
like feed right here which doesn't use the dynamic API endpoint uh I don't want to go too deep into this I don't want to scare you off or confuse you I promise you it is going to become a very simple once we start to leverage the server components so you can actually see what they're useful for I think that's the best way to learn right I can theoreti about what server components are all day long but until you actually see what they're used for I think only then is when it's going to click for you
so don't give up yet if you find this uh confusing or unusual right if this is your first time with xjs you're probably wondering what the hell is a server component so let's go back inside of feed page. DSX and let's change our URL to go to the feed component there we go uh my apologies this one feed page there we go and inside of here I'm going to do the same thing I'm going to do consol log where am I rendered and when I refresh here you can see that it's rendered on the server
by default right so server components will be able to do things like const you know data and you will literally be able to do database dot I don't know select from videos where equals videos ID one through three right and you would literally be able to render out data. name in here right imagine you want to do this you would be able to do this in a server component server components act like an API right imagine you're on the server that's what a server component is right we're are going to leverage this to prefetch our
data and then we're going to Leverage The Client component to enable the reactivity that we are used to right so I'm kind of introducing you into server components now I promise I'm going to go deeper into this in the actual chapter dedicated for this so let's go ahead and well of course we move this and just write uh feed page back so what I want to show you before we actually go and build our layout is that if I add use client here and then refresh this now you can see that it is first of
all rendered twice and second of all uh well no server and you cannot see it well you can still see it here let me just add some space here and then refresh uh oh looks like you can still see it here okay I did not expect that I I thought it was not going to be rendered when it's in Ed client here but looks like it is okay but you can clearly see that it no longer says server what can you do when it's in Ed client well you can do things like use effect right
use effect is something that you would not be able to do in a client component so I'm going to conso log this inside of here there we go it works just fine and now you can see that you cannot see it here uh in the terminal on the server because this use effect runs exclusively on the client right but if I remove use client what's going to happen is you're going to get an error you're importing a component that needs use effect this react hook only works in a client component right so I hope this
kind of give you a very very brief introduction into server components and client components client components which in nextjs you have to always explicitly Mark with use client are your usual components that you're most likely used to whereas server components are the new type of components which can fetch the database and we can leverage them for pre-rendering pre fetching basically making our app very very fast and once they actually click for you you're going to like them I promise you at first I was very you know confused about them I I didn't understand why they
were the default component here you'll probably notice that right they are the default component instead of the app folder everything you add here is a server components that's quite unusual you probably are not used to that but once you get a hang of it it will prove to be be quite useful great so we've learned the basics of that and now let's go ahead and learn about another you can remove actually keep the video ID and let's go inside of feed here and go ahead and add a layout. DSX file so layout is another reserved
file name and you also have to do a default export here so layout and inside of here let's add layout what's going to happen now is that just make sure you've added this inside of the feed folder what's going to happen now is that if you go inside of Slash feed it's just going to render layout if you go inside of SL feed 1 to three it will also just render layout that's quite unusual isn't it that's because layout functions work in a specific way let's create an interface to make this easier for us to
understand layout props will always have children like like this not node it's react node like this let's assign layout props and let's destructure our children and now let's render the children now everything is back to as it was feed 1 to three or any other ID renders that slash feed renders the feed page so what's the purpose of this layout file the purpose of this layout file is exactly as it stand for to create a reusable layout across components if I were to go ahead and and add a div here and say I am a
navbar like this let's give it a class name of padding four background rows 500 and the full width you can see that now this appears on my feed page and if I go to SL feed SL1 to3 it will also appear here so this layout file is exactly what we need that's why I wanted to explain it to you because this the nav bar and the sidebar will be inside of a layout file and what you're seeing right here this part this will be our page. TSX component and this will be our layout component let
me see can I draw on the picture I can great so this part will be our layout component right this part right here all of this is our layout and the rest is going to be our page component so this will be reusable right it no matter which page we are on this part will appear the same that's what we want that's what I wanted you to you know learn the basics of this and I think that we can uh now cross off uh this learn basic app route folders let's go ahead and Mark that
as learned great so this is what we are going to do we're going to learn about one more type of folder and then we're going to go ahead and build something I promise I know this uh this went longer than you probably expected but I think it's important to explain these things for you I'm going to remove the entire feed folder now and I'm going to show you one cool thing I'm going to create a new folder but inside of parentheses and I'm going to call it home folder like this now what I'm going to
do is the following I'm going to go ahead and remove this U sorry I'm going to close my terminal basically I don't want my app to be running just because I don't want to confuse my he reload then what I'm going to do is I'm going to copy page. DSX right so I recommend you copy it as well you can also move it but then you're going to cause some well let me show you if you move it right here click move you're going to get this update Imports for page. DSX you can press yes
for this if being asked and then you're going to have this weird unsaved file coming fromex types slapp so basically what's going on here this is just cache the cache is being confused that's why I recommend that that you shut down the app just so you don't have some funky errors that will confuse you but aren't actually real if this happens to you you will see one unsaved make sure it's the one inex folder SL types slapp you can click on it you can go inside and save the file and close it if you think
you've done something wrong don't worry at all so ensure that you have moved the page. TSX which was in the root of the app folder you should now have it moved inside of home which is written inside of parenthesis very important make sure it's written inside side of parenthesis and just for fun let's remove the next folder not any other folder just next remove it like this I want you to learn to not be afraid of that weird folder because that folder is just cash if you do Bun Run Dev again let's go ahead and
there we go it's back right here it just rebuilt the entire thing soex folder can sometimes get confused when you move around pages and layouts right if that happens if you think you've messed up just remove the next folder it will regenerate don't worry about it so now our slash feed does not exist so let's go back to Local Host 3000 and you should still see the page file even though it's inside of the home folder right if I change in YouTube 2 it's going to change here in the root of my application how is
that possible it's inside of a folder that's because nextjs has special rules for how you name your folders so the trick is not in the home keyword no the trick is in the parenthesis when you put your folder inside of parenthesis it tells nextjs router that this part this specific part is not going to be a part of the URL it will still be a part of the router but not a part of the URL this is useful when you want to categorize or structure something in the same folder but don't want to make an
entire new URL route out of it because if you just added home right and then another page. DSX inside like this you don't have to write this I'm just doing it for demonstration this one is only accessible when you literally go to slome but if you want to structure your root uh page and your root layout uh inside of a specific folder you can do that with the route group folder this is what that is called a route group and you can of course learn more about that here route groups right here so route groups
are nested folders so in the app directory nested folders are normally mapped to the URL pads that's what we just learned with our feed right however you can Mark a folder as a route group to prevent the folder from being included in the URL path they are useful for organizing routes into group like site section intent or team right in our case home is a site section it's a home feed right and the cool thing is you can still do the layout file inside of it so if I go ahead and create layout. DSX it's
only going to refer to my routes inside of this route group that's why I wanted uh to put it here keep in mind that this layout is a different thing this is a root layout you can not move that around this one includes the HTML and the body you can see that it's different than the one we've created because this is a completely different one you will never move this one you will modify it you can of course modify it as however you want but you should not move this file this is the root layout
but you can create another layout inside of here and this is where we are finally going to go ahead and create our home layout so let's go ahead and let's create an interface layout props give it a children react react node let's go ahead and create a con layout here let's assign the props and the structure the children inside of here let's just render the children and let's export default layout there we go nothing should change right now if I go to Local Host 3000 I should still see new tube too from this page right
here so what we're going to do now is we're going to build our home layout this is the decision that I will make now I'm not going to do any components inside of the app folder which are not related to routing so the only files that I will try to keep inside of the app folder will be routing files let me show you what I mean I'm now going to create another folder fer inside of my source folder which I'm going to call modules like this and inside of my modules I'm going to go ahead
and create another folder called home and inside of here another folder called UI you already see what I'm doing I'm going to have a modules folder where I'm going to have my sections my intents my teams basically whatever I need and I'm going to use these modules to store my components my hooks my a API my server my utils anything I need and then I'm going to use it as I need inside of the router here but the router itself is not going to have this I'm not going to have any Styles inside of page.
DSX I'm not going to have any Styles inside of layout the layout will simply serve to render the children so this is what we're going to do now inside of our modules home UI we're going to go ahead and create a new folder called layouts and inside of here we're going to create a home- layout. DSX let's go ahead and let's copy the interface from here we can actually copy the entire thing like this the only differen is is that our components like home layout are going to use named exports like this this needs to
be a default expert because nextjs expects it to be a default expert but for our own modules and components we don't have to do that and I prefer this one let's go ahead and be a bit more specific with our name here instead of the generic layout this will be a home layout like this but now we're going to run into a bit of an issue here I'm going to go ahead and attempt to do what I just did previously I'm going to create some kind of a Novar here right home navbar and if I
go ahead and refresh this my apologies not yet I have to go inside of my home route group layout here and I have to import home layout from modules home UI layouts home layout and I'll replace this with home layout like this you can see that it says home navbar now I'm going to attempt to show you something but I'm not exactly sure if it will fail like I expected to or not so I'm going to give this a padding uh of four a background of blue 500 and I don't know let's keep it like
that and there we go you can see that something is obviously wrong my padding works but this doesn't work how come it doesn't work well first of all how come this works the secret is in Tailwinds just in time compiler the padding Dash 4 has already been used so it was already let's say compiled I'm not sure what's the correct expression to use my apologies but it's already uh uh bundled in what styles Tailwind currently has for our project so when it sees padding four here it says cool I know what padding four is but
we have never used BG blue before at least I haven't right so it's confused it has no idea what that is why doesn't Tailwind know what this color is well let me remind you why what is our folder name our folder name is modules right instead of Source right here let's revisit our Tailwind doc [Music] config.sys component inside of modules will have Tailwind as well there we go we just added Tailwind I still have this little error at the bottom don't worry about it it will not affect anything now let's go ahead back inside of
our home layout so that is inside of modules layouts home layout right here and instead of using div as our main wrapper we're going to use sidebar Provider from components UI sidebar we have this because remember remember a few I mean a chapter ago uh we run bunx D- bun shcn 2.1.8 add-- all and that has added Source components UI sidebar right here in case you encounter a component that I have but you don't double check that you can visit it inside of source components UI sidebar if it really does not exist here you can
always addit man ually I would recommend again running the command that uh you know we just used previously which was this but you can also just add for sidebar you can also just add sidebar right in case that happens it should not happen to you great so make sure you have encapsulated your home layout with the sidebar provider now for this div right here we're going to give it a class name of full width like this and then inside of here we're actually going to render our home navbar component let's move the children inside of
this div and let's go ahead and add encapsulate our children in a div here so I'm just adding some sematics here we are going to populate this with some classes in a moment right now if you visit our app home nobar is not defined is the error we are seeing let's go ahead and build the home knvb bar so we're going to do that inside of the modules home UI and let's open a new folder called components and then let's open another folder called home navbar inside of home navbar let's add an index. TSX file
and I'm going to export con home navbar and I will return a div home navbar the reason I have opened a folder for this and an index. DSX and not just home navb bar. DSX is because it's going to have some sub components right so I just want to keep them all together that's the only reason why if this was a simple component I would just you know write the name of the component DSX in case you were wondering great now let's import the home Navar from do do components home Navar if you want to
you know you can you can use Alias but then remember you have to go to modules home UI right but since we are already inside of here I think it's just nicer to just use this right so now you should see home navbar again there we go now let's go ahead and let's properly style this home navbar instead of using a div it's going to use a nav element let's give it a class name of fixed top zero left zero so it's it has a fixed position right will also be zero height will be 16
background color will be white we're going to add Flex item Center padding on left and right will be two which comes to eight pixels padding on the right will be five because we need a bit more padding on that side because of our login button and the Z index will be 50 so it's above all other content so right now not much should really change except that it your content will be hidden right so my my knv bar right now is uh above my content so it's actually hidden basically what we did here is we
just created some basic rules for positioning our navbar and and we added some Flex items here so that we can properly you know uh use flex to align this on this side this in the middle and this here right so that's why we added those Flex elements great let's go ahead and continue developing inside of here I'm going to add a div with a class name Flex items Center Gap four so between each item that I'm going to add inside of this div it's going to be a gap of four or 16 pixels and the
full width now inside of here we're going to render menu and Logo the menu will be sidebar trigger from components UI sidebar like this uh like that and let's actually encapsulate this in another div because we will have two items here like this and let's give the div a class name Flex items Center and flex shrink zero so this never uh shrinks regard regardless of our resolution and now we're going to add a link component from next link which is basically going to be a click on our logo so let's give this an href to
go back to the roots page like this and inside we're going to do what we did previously which is render our logo. SVG using our uh next image like this let's give it an ALT of logo let's give it a width of 32 pixels and a height of 32 pixels as well and now let's add a paragraph new to you should already be seeing uh so we have this little button here and we have this new tube here so what we have to do now uh what we have to do now uh is the following
we have to wrap this inside of a div like this so that we can fix the current state which is that they are one beneath another so what I'm going to add this div is a last name padding four Flex item Center and GAP one there we go now they're nice next to each other like this and now let's go ahead and give this new tubes a class name text extra large font semi bold and tracking tight like this there we go now it looks much better right uh it looks way more similar to what
we have right here great one issue that you can probably see here is this little icon which is obviously different than from what I have here we're going to fix that in a moment I want to continue you know developing this so that we can actually see what this button is doing and then I'm going to show you how to change what this button looks like great so what we have to do now is we have to add our search bar right so find where this div ends right this div which is our menu and
logo and now what we're going to add is our search bar okay let me just add it here so we're going to have a div here with a class name Flex One Flex justify Center maximum width of 700 and 700 pixels so it doesn't expand more than that and MX outo so basically I want it to be pushed equally from this side side and this side so it's going to be rendered here and then inside of here let's add search input like this now we have to build the search input component we're going to do
that inside of our uh components right here and we can actually put it inside of Home Novar because that's the only place it's going to be used in so let's add the search input. DSX like this so I'm going to go ahead and do export con search input here for now this will not have any functionality just the UI so what I'm going to do is I'm going to add a form here with a class name Flex full width maximum width of 600 pixels I'm going to add a div with a class name relative with
a full width and then I'm going to use the native input so don't use the one from shaty nuui just the native one give this a type of text a placeholder of search and let's give it a class name and before we start uh writing the class name let's actually import the search input from do/ search input so that you can actually see what we're building right now you should just see a random input here so now let's go ahead and let's make it pretty by giving it full width padding left of four so we
live some space for the icon right because we are going to have uh the search icon here appear later like this pl4 py2 p12 rounded l pool border Focus outline none Focus border blue 500 like this there we go you can see that now it already looks pretty nice and you can see a clear cut off here that's because we're going to add a submit button in this corner right here so let's go ahead and let's do that outside of this div we're going to add a button we're going to give this a type of
submit because it's inside of a form and we're going to give this button a class name of px5 py 2.5 background color of gray 100 border border on the left side is going to be zero because it will directly connect to here as you can see so we don't want to have double borders on this side that's why it's zero on that side rounded on the right side will be full right so we want it to be fully rounded so it matches this part like that and then on a hover let's go ahead and give
it some background gray to 100 uh when it's disabled in case it's disabled opacity will be 50 and disabled will also say cursor uh not allow like this and inside of here we're going to use the search icon from Lucid react there we go make sure you have added this import uh if you're wondering where does this pack come from it comes from uh shaten right in case you don't have it you can do bun add Lucid react or npm install Lucid react and here's my version exact but you will most certainly have this installed
and let's give this a class name of size five there we go perfect so we have our search component right here but we are missing a couple of things and we're not not going to develop them now instead I'm just going to add to-dos so to-do add remove search button like this and to do add search functionality for now we are only worried about the UI great now let's go back inside of our home navbar index here and what we're going to do now is the out button so let's add a div here the class
name Flex shrink zero items Center flex and GAP four and let's render the out button component like this now I'm going to keep the out button inside of a new module hold out so let's create a new module like this let's create the UI folder and let's create the components folder like this and inside we're going to have the out button. DSX like this let's export const out button here and now what we're going to do is very simply we're going to add a button from components UI button meaning it's from shaten we're going to
render user Circle icon from Lucid react and a text assign in like this I will just align my imports like this and let's import this out button from modules out UI components out button so the reason I put it there is because uh it makes more sense semantically for it to be there right we are going to reuse that we we will be able to reuse that component in other places and I think it just makes more sense that it belongs to the AL module rather than it belonging to the home module you can make
of course a different decision for yourself if you want to great so we have a button here but you probably notice that it doesn't exactly look the same so let's just quickly go ahead and resolve that I'm going to go inside of the out button here and we're going to go ahead and first of all give our button variant of outline then we're going to give our class name PX4 py2 text small font medium text blue 600 on Hover text blue 500 so just a slight change border blue 500/20 rounded full shadow none and what
we're going to do is we're going to modify the size of this SVG right here you're probably thinking can't I just do this you cannot unfortunately that's because if you go deep inside of the button component here you will notice that it actually uh changes the SVG size like this right so if you want to change this to size five right what you have to do is you have to use that same Target get decorator SVG and then size five for example if you want to make it bigger but I think size four is actually
okay so you can just leave it like this there we go now our button is looking much much better great so that wraps up uh the UI for our navbar and now we have to do the same thing for the sidebar I will just add to do add different out States right because we currently well this out button is not really useful at the moment it's not really doing anything but that will change so we are now pretty much wrapped up with uh the UI when it comes to our home navbar right here and what
we can focus on now is go back inside of the home layout here and we should actually find a place to render our home sidebar let's go ahead and give this div which is encapsulating our children a class name Flex minimum height of screen and a padding top of four R just like this so now as you can see we have uh moved our body which was hidden behind the knv bar it's now being pushed right here great so now let's also render the home sidebar here and encapsulate our children inside of the main element
and let's give the main element a class name of Flex one and overflow y AO like this and now let's focus on developing the home sidebar so basically these are just some semantics to make this content behave properly when it's encapsulated uh in between the nov bar and the sidebar right those are this flexes and overfly Y autos and all of those things uh right here and the reason uh we are doing padding top for REM is because that translates to 64 pixels and if you go instead of home navbar and hover over here the
height of our Navar is 64 pixels so what we are doing is we are pushing the entire content below the home knv bar for the height of that knv bar so that's why our content is now visible right previously it was hidden behind let's go ahead inside of our components and let's create a new folder home sidebar and inside of Home sidebar let's create an index TSX component like this inside of here let's export con home sidebar and let's return a sidebar components from components UI sidebar make sure you don't accidentally import the icon from
Lucid react let's give the sidebar a class name of padding top 16 Z index of 40 and Border none like this and let's add a sidebar content here from components UI uh sidebar and let's give it a class name of background so BG background will simply default to whatever our background color is which in light mode is white uh and now instead of here we are going to add some sections but I think that we should already perhaps be seeing the sidebar so let's go ahead and import the home sidebar from components home sidebar the
same way we have imported the nav bar we can't see anything but we can clearly see that something has pushed our content right here and you can see that also when I click on this icon I can collapse our sidebar right so before we you know do any changes to any of this what I want to do is I actually want to go inside of the home sidebar and I want to add my first section which will be the main section like this and the main section will be another component which we're going to implement
inside of the home sidebar here create a new file main Das section. DSX like this and this will most certainly be a client component because we're going to be using some hooks so we need to turn this into a client component uh and now let's go ahead and let's prepare const items here it's going to be an array of items the first item will be a title which says home it's going to have a URL of a forward slash and then icon of home icon from Lucid react like this and that's how we're going to
build our uh items here the next one will be subscriptions which will lead to slash subscriptions actually slash feed slash subscriptions like this with an icon of play Square icon again from Lucid react so that's going to be our second one and we're also going to add a useful little Quirk here out true we're going to use this later it's going to be useful for us basically this page will only be rendered to authorized users because this will be used to load videos from your subscriptions if you're not logged in you don't have any subscriptions
right and this one will be trending so feed trending and this will have flame icon also from Lucid react and out is not going to be required for this one great so now let's export const main section here let's return sidebar group from components UI sidebar sidebar group content and sidebar menu so make sure you have all of this from sidebar sidebar group sidebar group content and menu but while we are here we can also add sidebar menu button and we can add sidebar menu item like this so make sure you have all of this
let me just collapse them so you can see them nicely there we go inside of the sidebar menu right here what we're going to do is we're going to iterate over our Items item. Map get the individual item and then inside of here render the sidebar menu item with a key of item. title because each title is unique so we can safely use it as a key and let's render the sidebar menu button component like this let's give this a tool tip of item. title let's give it an as child let's go ahead and give
it an is active to be explicitly false I'm going to change this in a moment and on click for now will just be an empty Arrow function then inside of here we're going to use our link from next link like this with an HRA going to item. URL and then inside we're going to render item. icon like this and a span item. tile let's give this some Styles so this will have a class name of flex items my apology Flex then items. Center Gap D4 and this span here will have a class name of 10
Tex small like this so that's our main section let's go ahead and import our main section from SL main section and now you should be able to see home subscriptions and trending right here which are all from our items as you can see here great so now what I'm going to do is the following I'm going to go ahead and add a little too change to look at current path name to do do something on click right so these are all the things that we are going to have to do later but for now we
are just focused on the UI great now that we have the main section we can easily build uh the other section which we need which is uh the personal section history liked videos and all playlists right so what I'm going to do is I'm going to go inside of my home sidebar and below the main section I'm going to add a separator component from components UI separator make sure you didn't accidentally import this from a radic because you're not going to have any shaty and styles let's give this separator well nothing it should just look
like this it should just be fine great and then below that we're going to have another section like this let's go ahead and let's copy the main section and paste it here and let's rename it to personal section like this meaning this will be the currently logged in users uh personal items but we are still going to render them regardless if we're logged in or not purely because the sidebar really looks empty when you're not logged in so we at least want to show the user what they could have once they're logged in so that's
my decision to show that let's modify our uh well first let's rename the this to personal section like this and let's go ahead and inside of sidebar group add sidebar group label you have to import this and call it you so sidebar group label is a new import here make sure you're doing this inside of the new copied personal section make sure you didn't alter the main section great now we're going to change the routes so the title of this one will be the history which will lead to SL playlists history playlist history and it
will use history icon from Lucid react and we're going to add our true next one will be like videos and this one will go to playlists liked oops and it's going to use the icon thumbs up icon from Lucid react and the last one will be all playlists and this will just go to slash playlists and it's going to use list video icon from Lucid react with Al true as well so make sure you have added history icon list video icon and thumbs app icon so now if you save this and go back instead of
your home sidebar and replace this duplicated main section with personal section like this you should now see another section right here great and we can now go ahead and go inside of source app folder my apologies inside of well yes inside of homepage. DSX and just change this to you know I will load videos in the future like this and remove the import and there we go now it makes more sense what this will be so this is our sidebar with home if you click on any of this it will lead to 404 because we
don't have any of those routes implemented yet but you can see our basic layout is definitely coming together let's go ahead and wrap it up uh by just modifying the icon of this sidebar right here you can do that by literally going inside of the sidebar component which we can do because we're using shat CN UI so just go to Source components UI and select the sidebar component let me just find it sidebar right here and you can see that we are using panel left from Lucid react let's add menu icon from Lucid react and
we can remove the panel left and now we you can just look for the error so just scroll down and you will see an error right here I believe there we go the component is called sidebar trigger and what you can do is just change this to be menu icon like this there we go and now it's exactly the same as mine is and another thing that you can do here is you can go inside of app folder my apologies inside of your modules home layouts actually home sidebar index and you can add collapsed I
believe is it collapse uh just a second I'm going to oh it is collapsible I believe not on the sidebar content on the sidebar collapsible and instead of here you can choose icon non or off canvas and if you choose icon you're going to enable the collapsed mode like this I think this is quite useful if you don't want to entirely hide the sidebar but you do want to expand your users View you right perfect so we now have this and I believe that already if you go inside of mobile mode let me go ahead
and go here for example there we go you can see that uh we it also works in Mobile mode it does throw some errors these are actually not our fault um because this component that's doing this has been built from uh you know we didn't build the code for this and this error that's being thrown comes from a newer version of radics which I guess uh the creators of shatan didn't exactly account for at the time so now it's throwing an error because the way they implemented this uh sheet component or this dialogue component uh
is throwing accessibility errors right but I think it's okay for a tutorial uh I will research a bit before I completely end the tutorial if that that has been resolved somehow but I think in this version is going to stay like that uh but those errors are not visible in production as far as I know uh great so one more thing I can show you how to do is the animation if you like the animation great I personally don't like the animation I think it makes the appears the app look slower so again let's go
back inside of our components UI side where I'm going to show you where you can change or remove the animation so basically if you want to remove the animation I don't think there's a prop for it but you can search for any of the transition classes right and if there are any transition classes you can remove them so for example I'm going to remove transition all from here you didn't have to do this but if you dislike transitions as much as I do you can just remove them so I'm going to remove the transitions here
right transition transform for example I will remove that uh transition withth height and padding I will remove that as well well transition transform remove that as well and transition with let's see anything else we do have this as well and I believe no more transitions so now let's see if I refresh oh it still shows some transitions I think I also have to change the duration so I'll remove all durations as well let me see perhaps that what uh was the problem again you don't have to do this I just really dislike them for some
reason there we go maybe I could have just removed durations but that this makes it the same way it works on actual YouTube so depending on what you prefer you can either you know um you can bring it back or you can change it as I did uh again you can just search for you know The Rao or you can search for uh transition like this and then you can just remove the necessary classes you don't have to do it if you like the animation and if you think you've messed it up uh what you
can do is well you can undo the file but remember you can always run bonex add sidebar and what this will do is uh let me just go ahead and you know save my sidebar component just in case I'm going to copy the entire thing you can do this for example and uh let's see oh yeah it will basically ask you the file sidebar. DSX already exists would you like to overwrite and you can just press yes and then it's going to overwrite it and it's going to bring it back to normal so I'm just
going to remove no no no no no the entire thing no okay great so let me just double check that nothing important was modified for me no great there we go so as I was saying we just finished the basic layout great so we built this red thing right here it's not exactly finished finish it's just UI but it is the layout that we need great amazing amazing job so this chapter is going to be a bit smaller than our last chapter which went on for an hour in this chapter our goal is to add
authentication to our project we're going to achieve that using clerk the authentication which we develop in this chapter is not exactly going to stop here but in order to implement the things that I have planned for authentication in the future we're missing a couple of modules like our database and trpc which is something we're going to add later so I decided to use this chapter simply to add basic authentication which means in this chapter we have to integrate clerk we have to add sign in and sign up screens we have to add user button right
which is this user button when someone is logged in we have to add this kind of login screen we need to add a middleware which will protect our routes and then we can finally use that uh out state if you remember personal section we have this out true right so we can finally use that to show a login model if an unauthorized user clicks on that and of course we have to learn how to protect routes using the middleware let's start all of that by going to clerk.com go ahead and create an account and once
you create an account you should be able to visit your dashboard I'm going to go ahead and create an application and I'm going to call it new tube like this and you can see it says sign in to new tube and now I'm only going to select Google as the signin provider and I'm going to leave email but you know you can choose what ever you prefer uh yeah I would actually um yeah I would like it to be Google the reason I want it to be Google is because we're going to rely on users's
first and last name to store in our database later on and if we just use email uh I'm not sure we can extract the first and last name from that but if we use Google Google should automatically have the users first and last name so let's just select Google sign in later on you know in your application uh if you want you can decide um to enable some more providers but you know if you want to follow the tutorial exactly as I do go ahead and just select Google like this there we go perfect so
now let's go ahead and let's add clerk nextjs instead of using npm we're going to be using bun let's do bun add but before we do that yes I want to be specific with my versions so I will do uh the following let's see uh I'm not sure how to check the latest versions so what I will do is just do npm Clerk nextjs and let's see what is the latest version so it's 610. 3 let's go ahead and do that bun add add clerk SL nextjs at 610. 3 at Clerk nextjs s at 6110.3
there we go that should add clerk to our project and now you should copy the environment variables right here so let's go ahead inside of our project and we have to create a new file do environment. local like this and it should automatically be hidden from git so instead of your git ignore you should have environment and then an Asterix which means basically all environment files are ignored and let's just add these two keys here you should never share your keys with anyone for the sake of this tutorial I'm using a demo account with no
credit card con connected or anything so I'm not at any danger of anyone stealing this but you should not share it with anyone you might forget that you added a card and someone can abuse these keys for you so make sure you only keep them safe for yourself so you should have the next public clerk publishable key and clerk secret key like this now let's go ahead and let's add the middleware here so update your middleware file or create one at the root of your project or inside of the source directory if you're using the
source directory structure so we are using a source so let's go inside of source and let's create middleware dods and let's paste this inside uh if you're wondering what this regex is you can see basically it says here uh this ensures that that it doesn't match the nextjs internals and all static files basically we don't want the middleware to run uh on loading our assets right but we do want it to run for API routes and it's very cool that they added trpc here because we will be using trpc in case your matcher looks different
which is possible if you're watching this into the future and their documentation has changed uh you I mean I hope you used this version clerk nexs 610. 3 you know you can you can use any version you want I'm not telling you that you have to use 6.1.3 right the same way I'm not telling you that you have to use the exact versions that I used for shaten and all these other things but I want to ensure a certain longevity to my tutorial so that's why I'm telling you the exact versions to install so if
your Masher looks different if you're watching this into the future I don't know a year from now and maybe something changed you can go Ahad head through documentation and try and figure if something changed on your own or you can just look at my source code and you know copy this from here but more or less this is very simple this shouldn't be anything special so now what we have to do is we have to add the clerk provider to our app so let's go ahead and go inside of our root layout so inside of
source app layout right here and we have to import clerk is it clerk provider it is it's clerk Provider from clerk nextjs like this and I'm just going to wrap my entire app inside of that clerk provider just like this and let me just indent this inside there we go like that and now uh this is what we're going to do we can do BN run Dev so we can start uh well seeing some changes I don't think anything will really change uh yet but let's go ahead and do the following what I want to
do now is I want to create my login screens right usually in in the past that was you know thought how to do here initially but they decided that it's simpler to just show you this part because some people don't want login screens but I believe they still have those there we go next steps is to utilize your own Pages for authentication right let's click continue to the nextjs guide and let's see what they have in store for us here so build a sign in or up page that's great I did not know that they
have uh okay yeah let's go ahead and do this so I'm going to go ahead and do app sign in and then sign in page. vsx let's do that let's go instead of source app folder and let's add how did they do it so sign in what we can do here is we can add them both inside of a route group called out right so they don't just randomly appear so we know that they're here but out is not going to be a part of the URL so we can utilize that so sign in right
and then we have to create another catch all route so this is a dynamic type of Route which will basically catch every single variable that's passed inside anything Dynamic so this can be literally anything and I guess that's useful for them because they want might want to redirect you from to somewhere after you sign in right so that's what they want this type of catch all route and then they want page. TSX inside and they want us to add the snippet inside there we go so now I believe that you should already be able to
visit this page but let's read what they have to say make the sign in or up page public by default clerk middleware makes all the routes public this step is specifically for applications that have configured the clerk middleware to make all routes protected okay so we don't have to do this right because our middleware does not protect any routes at the moment all routes are public uh by default so this step is for applications that have configured the clerk Middler to make the routes protected we don't have to do that right because we're only going
to have a few authorized routes you know YouTube as an application is mostly an open app so I think that's completely fine uh okay so now we should go be able to go to SL localhost 3000 sign in and uh let's see so sign in is not available for me how come Al sign dashin page. DSX let's see why is this page not available it's not available because I think there's a bug here uh inside of how I wrote this so I opened two parentheses but I only closed one there we go now it's proper
syntax so now if I refresh there we go we can see a nice login page here so what we're going to to do here uh is we can copy this entire thing and we can call this sign up like this and we can rename this to sign up as well and inside of the sign up we can import sign up there we go like this and then instead of out what we can do is we can add a layout. TSX like this and this layout can serve as a centering layout right so let's do this
let's do layout and what we're going to do here is just add an interface layout props and we're going to call this children react react node let's add layout props children and let's just render the children like this let me just fix my typo and now nothing should change but you should be able to visit both sign in and sign up so this one will say create your account and this one will say sign in welcome back right so the goal of this layout is to create a reusable layout which will be able to Center
this so items Center and justify Center like this let's see uh minimum height of screen with two e and there we go now it's centered all right this already looks better but we're going to be using these screens as fallbacks we will most of the time we will not show this kind of screen to the user this will only show if the user manually tries to use the URL to go to an authorized screen and then we're just going to redirect them to this screen that's what we're going to use that for let me just
show you something so this is step two but I believe there is another step uh right here which is the next public clerk sign in URL right so we also have to add that so clerk knows where to redirect in case something like that happens so let's add that exact route here next public clerk sign in url and we should also have the sign up URL let me just confirm inside of environment variables that both of them exist so clerk sign up URL and clerk sign in url the reason we need next public here is
because if you want to make an environment variable available to the client you need to prefix it with next public instead of nextjs that's why they don't have it here all right so we've ensured that and I think that's it for the documentation we don't have to focus on that anymore uh great and now uh I think there's also one more thing actually that I want to add in regards to the environment variables here so and it's those clerk signing fallback redirect URL the fallback URL to red irect after the user signin if there's no
redirect you roll to the path already right so this will this is useful if the user doesn't have any predefined redirect URL we're going to create a global fallback so it always goes to our root page even though it says it defaults to the slash uh I found that when using the clerk model which is something that I'm going to be doing that doesn't happen so we're going to go ahead and add it either way so let's do next public clerk signning fullback URL go to the root page and sign up fullback URL go to
the root page as well uh yeah okay even though it says it defaults to here um that wasn't exactly my experience but with one specific model which I'm going to show you uh in a moment once I add that uh okay so we now have this ready great so now we should be able uh to log in but uh before we do that how about we go to our Local Host 3000 here like this and actually make this sign in or out the button react to whether we are logged in or not so we can
do that by going inside of modules out UI components out button let's mark this as use client component this is very important we need it to be used client and now let's import user button from clerk nextjs sign in button signed in and signed out so all of these things from CL connect JS and now we can remove the to-do here because we're actually going to do it so I'm going to wrap the entire thing in a fragment like this and then what I'm going to do is first I'm going to add if we are
signed out if we are signed out that's when we're going to render this right and then I'm going to wrap our button in a sign in button like this there we go so now let me just see I have some errors so that's unused files that's fine if I click on sign in now it will redirect me to this sign in page right so that's cool if you like it that way that's perfectly fine but here's a cool thing you can direct the signin button to use the mode model and I like this better when
you click on it there we go it shows up this Sleek model right what I've experienced is that when you use the model this doesn't really work so sometimes it will take you like to your account dashboard in clerk but if you explicitly set this routes then it will always uh lead you back to the root page here all right so we now have that and now let's create a state when we are signed in so sign in we'll use the user button component like this and the user button well doesn't really need anything I
think right so let's go ahead and try out I'm going to click sign in continue with Google I'm going to use my account here I'm going to click continue and now after this loads I should be redirected and there we go we have the user button and we can update our profile we can remove or change our profile picture we can connect or disconnect our accounts all of those things and inside of your dashboard you should see your first user right here and instead of here you can also you know impersonate that user ban the
user delete the user or simply look at their profile and change any information you have about them so quite useful uh things you can find here so now what I want to do is the following I want to go ahead uh and add a little to here add menu items for studio and user profile so right now we don't have either of those but clerks makes it very easy to add multiple items to this uh user dropdown even though they are going to internally uh redirect somewhere so they're not going to use any anything clerk
related so that's really cool uh great so we're going to have that and now let's go ahead and let's let's log out so that works as well perfect and one thing that I actually want to do is I want to go inside of my layout right here and I want to give the clerk provider an explicit after sign out URL to always go to the root page I like to be explicit even though I think this is the default uh I like to be explicit so I know exactly that's what's happening right great so now
what I want to do is I want to go uh inside of my main section for example and let's go ahead and actually do this to do something on click let's actually make it open the clerk model so what we can do is we can import use clerk from clerk nextjs like this so cons clerk will be use Clerk and we can also add user ID from use out there are many hooks that you can use from clerk for example you have used user and things like that but I think use Al is uh it's
very simply just loads the clerk token right uh it doesn't have to load anything it will wait for clerk to load but the the hook itself is not really loading anything right so uh let me just escape this so instead of extracting user ID you can also use is signed in for example like that and then what you can do here is on click if uh we are not signed in and if this item has the out feature marked we're going to prevent default let's get the event so prevent default on here and let's return
clerk open sign in and then here uh we're going to well nothing actually right it's just going to override the link feature so by default uh is signed in right so by default when someone clicks on the sidebar menu button it will lead them to the URL but in case some this is our main section so for example uh subscriptions should be unclickable for a person that's not logged in so home button works if I click on trending I get a 404 so that technically works but if I click on subscriptions there we go I
get uh a model right let me just go ahead and try and log in and if I click on subscriptions now I get a 404 exactly what I wanted perfect so now let's go ahead and let's do the same thing uh inside of the personal section so personal section right here and we can do the same thing so let me just add the Imports here from clerk nextjs like this let me go ahead and copy this two add them to the personal section like that and then I can just copy the on click and we
can remove the to-do comment here and I think that the same thing should now work so instead of here there we go perfect so that works now uh what I want to do now is just learn how to protect the routes using the middleware so right now our middleware our middleware is allowing us to use any route right it's not explicitly protecting anything so how about we create a new route uh let's go inside of home here let's call this protected and I'm going to uh create a new file Insite called page. TSX page they
only authorized users should see this or only logged in users right so if I now go to Local Host 3000 protected even though I am logged out I can see this page how can we fix that well very simply if we go instead of source middleware dots we can learn how to use it so we're going to add create route matcher from here and Define const is protected route to use the create route matcher and open an array inside and inside we can simply pass slash protected like this and then we can modify the clerk
middleware to be a bit more explicit so let's get the out and the request here and if is protected route passing the request in that case await out. protect as simple as that so now if I refresh this I am redirected to my signin page so that's why we need the signin Pages after all and you can see inside of your url you should have the redirect URL to lead you back to the protected so if I continue with Google now I am redirected back to the last page that I tried to access right so
that's how that works and in case you're wondering well what if I have a whole sub route that I want to protect right inside of here I should have something right and if I go inside of page. DSX let me just copy this see this sub rout for example and if I go and log out and if I go to slash protected slash uh sub R is that's what we called it we called it something so something I can still see this right because my middleware explicitly only protects this route it doesn't protect SL protected
slash something so what you can do in that case is give it a little parenthesis and then an asteris and now if you refresh all routes under that route will be protected great so I hope uh you've learned how to use clerk now it's super super simple uh but but there is you know one let's call it a problem here the problem is that at the moment we are not synchronizing this users to our database that's what we have to do next because we're going to need to keep uh keep track of which user uploaded
which video because this videos will be displayed here right and then if I go ahead and you know change my profile picture we need a way for that profile picture to update to every video card that my my user uploaded we can only do that by synchronizing our clerk users to our database and we can do that with web hooks so this is what we're going to use next web hooks right here we can establish web Hooks and then every time a new user is created updated or deleted it will contact our app and it
will tell uh the database what to do right update that user delete that user or create create that user in the database but before we can do that we're going to be we're going to have to well establish our schema and our database in the first place so I believe that's what's going to happen in our next chapter great great job let's just mark this as completed there we go let's do that we integrated clerk we added sign-in screens we added the user button we added the middleware we used the outstate on the sidebar sections
and we learned how to protect midders great great job in this chapter we're going to set up our database we're going to be using postra SQL which we are going to obtain through neon database after we establish our database and have a proper connection string we're going to integrate drizzle Oram after that we're going to create our first database schema which will be the schema for our users we are then going to learn how to migrate those changes to our database on neon and we're also going to learn how to use drizzle kit which is
very useful because it has a drizzle Studio which will uh allow you to locally browse your database so before we move on why drizzle omm so in previous tutorials of mine I've used Prisma because I really really like its querying right so this picture right here is not Prisma this is all also drizzle but I wanted to show you this because this is a very nice type of querying it reminds me of mongoose which is very simple right you database query users find many and include posts right very simple but there are a couple of
problems with that first of all why drizzle orm it is the only omm with both relational which is this and SQL like query apis this is its SQL like querying it is also serverless by default which is something Prisma is not and this is what I really like about drizzle especially when it comes to making tutorials it is forcing us to understand our queries what do I mean by that so if you want to you can use drizzle like this they have both relational API which is this one so this is U you know an
API similar to prismas and it's great but to be honest when you write this you have no idea what you're doing right you have no idea is this a subquery is this a Common Table expression or is this a join if it's a join is it an inner join a left joint a right join you have no idea but if you opt to use SQL like quering you're going to have to force yourself to understand what you're doing so just by using this left join here I feel like we've gained more understanding of our database
and our query then we would with a thousand queries of Prisma so that's why I'm going to be using drizzle orm and I'm I'm going to be using exclusively SQL like query apis which means all of our queries are going to be like this almost as if we were writing seq so let's start by creating and obtaining our connection URL from neon.pdf name and I'm just going to click uh create project I'm not going to modify any order scaling here I will just click confirm and then what I'm going to do here is I'm just
going to copy this right here right I see they have some options for nextjs but we are not going to be using this right so you can just copy the normal postgress connection string so you can click copy snippet like this and now let's go ahead inside of our Environ M.L here and I will add a database URL and I will just paste that here this is probably the most crucial uh environment variable key you will have so please do not share it with anyone as always this is my test account I don't care about
these keys I'm going to remove this account afterwards uh so that's why I'm showing them to you but please do not share this with anyone keep it for yourself great and we're pretty much done with the neon dashboard but I do want keep it here just you know uh so we can see once we actually push our users we can also explore the tables here so I just want to teach you how to do that but for now let's go ahead and let's focus on adding drizzle omm so you can Google drizzle orm to get
through its documentation page here and instead of the get started here make sure you've selected the new database guide and go ahead and select the Neon right here great here you can also see the basic file structure so this is our project rout and we're going to have a database folder in which we're going to have our schema and we're also going to need to have a drizzle config file as well as the index of the actual drizzle you're going to see all of that in a second but let's start by installing the necessary packages
here so I'm going to select bun here just so we have the proper Bond commands we're going to have to add drizzle orm neon database server list and do environment and then in our Dev dependencies we're going to add drizzle kit and TSX technically since we're using bun I'm pretty sure we don't need a TSX but I'm going to follow the guide just in case we mess it up right so I have prepared all of these things uh inside of npm so we can see the versions right one thing I want to bring to your
attention is that you might see this in the top here you can see that version 1.0 is currently 75% done what does that mean so they have a public Road mapap in which they are going to publish drizzle kit version one which is you know a very major version I have contacted the authors be behind drizzle uh you know to see how can I ensure longevity for this tutorial you know how can I ensure that uh the moment I upload this video there won't be any new Breaking changes there is one thing uh that's happening
at the moment which is the relational queries version to API and I asked the author whether I should wait for this to be released or should I use uh the current version and it turns out that this is only going to be out uh in a couple of weeks after I publish this video uh so I got a green light to tell you to install this version 039.0 right so in this case I can't really teach you the new uh relational queries version to API but I'm pretty sure this version will be supported for long
enough so yes it is a bit important that you install this version if you want to follow the tutorial exactly as I do or if you want to you know explore the latest version yourself if in the future you see this being 1.0 or something a major change you can explore it yourself if you want to but if you're watching this a bit closer I prefer you to install the same version as I do let's go ahead and install this one by one I'm going to go ahead and shut down my app so let's do
bun ad drizzle orm at 0 39.0 as you can see that was published 12 hours ago and let's go ahead and see what else do we need to install so we need neon database serverless so let's add that and let me see the version for that is 0.1.4 there we go and then we need the dot environment so let's add that the latest version for that is 16.4.2 great so let's just ensure we have all the necessary ones so those three great let's add them perfect so we have those three and now let's do bun
add Dash uh development and we're going to add drizzle kit and TSX so the drizzle kit version is 0.3.3 also published 13 hours ago and we also need TSX which I'm not exactly sure if we need or not but let's go ahead and install it so TSX at 4.9.2 great let me go ahead and install this inside of my Dev dependencies here and I think that should be enough so let me go ahead and do bun runev now and now let's go ahead and follow the guide but so we have already set up our database
URL right right here so we don't need to do this step now what we have to do is we have to use that environment variable to create our database util right so let's go ahead and do that I'm going to go instead of source and I'm going to create a new folder called database and inside an index.ts let's import drizzle from drizzle orm neon HTTP and let's export cons database drizzle process environment database URL and put an exclamation point at the end to ensure well to kind of override the fact that typescript doesn't know what
this is we know that is right here uh great and of course you can see that in if you need a synchronous connection you can use our additional connection API where you can specify a driver connection and pass it to the drizzle instance right so that's if you want to use serverless specifically you can do that as well in our case neon HTTP will do just fine great now what we have to do is we have to create a table and a schema that we can push in our case that's going to be users let's
go ahead and do that I'm going to go inside of database and I'm going to create schema. TS like this I will export cons users to be PG table from dzel or/ PG core meaning postgress right and let's define the users so I will always Define my table names in multiple entity right so not User it's going to be users like this let's go ahead and let's add ID I'm going to use the uu ID from PG core uh I know there's a lot of opinion around uu IDs uh to its speed uh to its
well long length right but um I kind of find them to always be a good standard of course if you know better um you can use Nano ID you can use CU ID uh I believe there are some other newer versions of uu ID uh the thing is I've always built my projects with uu IDs without any problems and I like to only teach you things that I'm certain that work right we can experiment with with Nano ID some other time if you want to you can do that but I'm certain that relations and everything
that I have imagined for this project work perfectly with uu IDs so that's my only choice for that no other reason so it's pure preference for me if you know uh any other if you have any other preference feel free to use that type of ID uh great so let's go ahead and actually build our user ID here so we are going to have our primary key right but we are almost well we are certainly never going to create this via any registration form this user model will only be created through a web hook which
clerk will fire because of that we are always going to have a clerk ID so this clerk ID will be what we have here for the user so for example I have one user here and inside of here somewhere there we go I have this user ID so that's what's going to be here right that's what we need so we can connect the two so I'm going to use the textt option from drizzle ormg core make sure you don't accidentally import it from my SQL or something else I'm going to call this clerk ID it's
always going to be unique and it's always going to be required then we're going to have a name which is also going to be required and then we're going to have Banner URL uh actually how about this you don't know what the banner is at the moment so I'm not going to add it yet I think it will be a good exercise for us to learn how to modify the schema later on how about we just add a to-do add Banner Fields let's continue let's add image URL this is something that each user will get
when they create an account with clerk so that is going to be text image URL not now as well meaning required and then we're going to have our handy created app and updated at timestamps so import the timestamp from PG core created at default it to now and set it as required and we're going to do the same thing as updated ad just modify this to be updated at like this there we go so one thing I want to do now is I want to add an index for the clerk ID because we are often
going to search for the user in our database by the current clerk ID that we have because we we will be uh able to use Clerk's handy server utils and then we're going to have to query the database to check okay this logged in user WEA clerk what user in the database does this entity belong to so we're going to do a lot of querying by clerk ID but clerk ID at the moment is not our primary key so technically yeah maybe we could have put the clerk ID to be our primary key but I
don't know I'm not really sure about that so this this is what I'm going to do I'm going to go ahead and create an index on that using unique index uh from drizzle rmpg core and I'm going to call it clerk ID idx a short for index do ont. clerk ID like this there we go so now I have created an index on this clerk ID so I can query faster like that great and I believe that we are ready to push this now but before we can do that let's continue learning about uh setting
up drizzle so we have created a table that's done right as you can see you can also use like bar charts if you want to specify the exact length of something I use text which is uh I'm not sure what is the correct definition you'll have to forgive me for that but I use it whenever I'm not sure what I should limit a field do for example I have no idea what how how uh long this image URL is going to be so I don't have the confidence to set a length to it I don't
want my insertions to start failing all of a sudden if you are more experienced in this part of the project feel free to change this to ourchart and put the exact length that you are uh more familiar with great now we have to set up our drizzle config file so create a drizzle. config.sys config do DS like this and you can go ahead and copy this entire thing and add it here so make sure you have installed environment as I have in the packet Json right here and one thing we're going to do is we're
going to specify which environment file we are using because by default I don't think it knows that we are using environment. loal it's probably searching for do environment so what we can do is environment. config my apologies we have to do this we have to import environment from environment like this and then environment. config path. environment. loal so now it will be able to read this and find the database URL here what's important here is that the out is. drizzle that the schema is correct path so/ Source database schema let's see/ Source database schema schema
right here perfect the dialect is postgress SQL that's correct and this is my database URL perfect that is it that's all we need for our drizzle config.sys you can migrate your changes or apply your changes to the database this is the most convenient method for fast iteration for solo developers or for tutorials if you're more familiar with SQL you might know that there are things called migration files right migration schemas that is also possible with Drizzle you can go ahead and read some tips here for example alternatively you can generate migrations using drizzle kit generate
command and then you can apply them using drizzle kit migrate command and you can learn more about that by clicking here so now you're going to see how all of that works in this tutorial we're going to use is the most convenient method drizzle kit push basically anytime we make a change to our schema we're just going to do drizzle kit push let's go ahead and do that now I'm going to go ahead and do bonex drizzle Das kit push like this and let's see if we get any errors looks like everything went fine pulling
schema from the database and changes applied it means that it correctly found our schema and it correctly pushed to this database URL which we specified right here so if I now go ahead inside of my neon console you can see that I have zero tables in my public schema but if I refresh I now have my users and you can see all the fields which we created previously the ID the clerk ID name image URL and the time stamps right here perfect so that works just fine one cool thing about drizzle if you don't know
is that they have very powerful branching feature right if you go ahead with this project and create proper you know staging environments development environments preview environments and production environments this will come in very handy to you because especially because you will be able to replicate data from the production database now I know that's a bit controversial but in fast iteration at least in my case that has proven to be insanely useful to have real data that you can test on without actually modifying the production database right so I just want to bring that to your
attention I think that's super cool they also have the restore feature they have very cool monitoring so you can see if something is going on with your queries if you need to have uh some focus on a slow running query they have Integrations they have this authorizations a lot of things that you can explore here great so let's go ahead and see what else do we have here they are teaching us how to seed and query the database here we are going to learn how to do that in a moment but here's what I want
to do what I want to do is learn how to use the drizzle kit studio so let's go ahead and do this bonex drizzle kit studio right here it's going to say that drizzle studio is up and running on htps local. drizzle. studio so if you go here you should be able to see your studio right here and your users and if you go ahead you can go ahead and you know add a record so this is not something that we're going to do now but if you want to know bootstrap if you want to
quickly add something you can do that from here great so that's what I wanted us to learn let's go ahead and see uh did we cover everything we need here we created our postgress database we've set up dzel orm we have created the user schema we've learned how to migrate the changes and we've learned how to use drizzle kit and we explained why we're using rusal orm and later on you're going to see why I really want to use this instead of this right here uh great so I think that wraps up wraps up this
chapter what we're are going to do next is we are going to configure a local tunnel which will be able to communicate with our uh clerk web hook right here our application and our database so every time a new user is created in clerk what we are going going to do is we're going to go ahead and actually populate this table right here and then instead of our drizzle Studio we're going to be able to finally see some users here great great job in this chapter our goal is to synchronize our clerk accounts with our
users database in order to do that we're going to have to implement our very first web hook in order to do that we're going to need to establish a local tunnel there are a lot of solutions online for local tunnels but I would highly recommend using enrock it is by far the most reliable method of doing this and one very very cool thing is that they will give you a static domain right so this is very useful because usually every time you start a local tunnel using some different tools they give you a different URL
which means that you have to change your whole configuration in all the websites where the web Hook is coming from but if you use a static domain you can just remember it once put it once and it's always going to work also just to clarify we're going to need enrock only for development in production that's not going to be a problem because we will have our URL you know our website.com but in development mode we don't have a URL we cannot tell uh a production app to connect to our Local Host app right that's why
we need to expose our Local Host app to the public and for that we need something like enrock so again enrock is not required static domain is not required but both of these make uh development much much easier let's go ahead and let's do that first so I'm going to go ahead and create an account on engro right here once you are logged in you're going to see all the instructions you need for example on Mac OS which it recognized I can install it using brew and then I can add my token keep in mind
again you should not share this token with anyone this is my uh dummy account which I just created and I I did not even connect this to my PC but you should not uh share this with anyone right or you can just use the download button and the same thing is true if you're using Windows or Linux or any other solution once you have enro installed you should go ahead and confirm that so let's go ahead and check my version so enro 3.9.0 is my version basically Andro should not throw you any errors it should
work as a command the next thing you have to do is you have to establish a static domain so instead of your enro dashboard here let me zoom in a bit you should find domains and go ahead and click create domain and inside of here you're going to get one only one free static domain so for each account you create an enrock you're going to have this static domain like this so now if you go ahead for example and do the following uh yeah this is not going to work for me so for you this
is going to work so you can just copy whatever domain you have here but I have to use a different one because as I said this is my dummy account I didn't connect this so let's do this in one terminal run your application on Local Host 3000 and in another go ahead and run that enr command let me just go ahead and uh why can't I do this okay so angro and seems like I have to do this so let me just angro like this and I have to modify my URL and in case this
doesn't work for you don't worry because I will show you one little trick so if I go ahead and change this to my proper domain and change this to look at the Port 3000 because we are running on 3000 here in another terminal it will start my tunnel but when I was developing this I had a bug that if I tapped uh if I typed D- URL I kept getting an error that URL doesn't exist in that case you can try Dash Das domain and it should still work right so looks like they have changed
that name but they still have backwards compatibility so it will depend on the version of enr you have uh so if D- URL throws an error you can use D- domain and you will get the same thing so go ahead and run this and now uh if you go ahead and expand this you should be able to see the URL where your application is exposed to the public so right now if I go here you will see that it will load uh the app which we have been building previously there we go so NewTube with
our basic layout here perfect so just confirm that this is working confirm that you can visit this new URL here let me go ahead and just enable developer mode here there we go so this is My URL evolve the humbly goer whatever that means it's random right great so one thing that's kind of a hassle in development is that you have to remember to start this every time you need local development but I found a neat little uh trick you can do and in order to do that we first have to do uh we have
to install concurrently so let me just show you the npm version of this so concurrently enables you to run multiple commands concurrently uh the issue is you probably think okay well I know how to run T commands but that's not running them concurrently and attempting to to run a web hook and npm runev at the same time will fail so that's why you need concurrently so let's go ahead and add concurrently with version 9.1.2 so I'm going to shut down both of my apps here and I'm going to do bun add concurrently at 9.1.2 there
we go and then what I'm going to do is I'm going to go inside of my package Json you can confirm that you have concur currently installed right here and then go ahead and do the following add Dev web hook like this and Dev web Hook is going to be your enr command let me try enr there we go so basically this command which you just previously run right make sure that the port is 3,000 and make sure that you're using the proper-- URL or D- domain and simply add that here to be Dev web
hook so now if you actually try and do Bun Run Dev web hook it will start the endr great so that's great that's now shorter you don't have to remember the whole the main thing but let me show you another little trick you can do you can add Dev all like this and then you can run concurrently we can run it like this because it is installed inside of this project right otherwise it you would have to have it globally installed so concurrently and then you have to add uh a backwards slash because you have
to escape this uh regex and inside of here go ahead and run bun runev web hook so that's the first one we're going to run uh and we have to escape this like this this is a bit confusing but yeah backwards slash quotes and backwards slash and then add a quote basically we have to escape this quotes right uh and then you can just go ahead and do the same thing again so backwards slash and then Bun Run Dev and then backwards slash and close this so now it's going to run together the web hook
and our uh next Dev so if you go ahead and try it now let me exit this and do Bun Run Dev comma colum all it will concurrently run the web hook and Bon run Dev so if I go ahead and try going on my evolved humbly gopher web hook you can see that it should be fully available it's refreshing now there we go so no errors this route definitely uh exists and I can log in here great and if I go inside of my Local Host Here Local Host works as well so both of
my apps are now working uh with one command so that's why I wanted you to have a static URL with enr because it's super simple again you can change this to domain if URL is not working for you and if you forgot where I got this from it's right here right so inside of your enr domains and you have one single domain here you can click Start a tunnel and then you will get this Command right here just make sure to change the port from 80 uh to 3,000 like this so This should work because
enr is globally available right so if I type Ang rock you can see it's globally available concurrently is not globally a available for me as you can see but uh it works here because we have it installed in our project and yeah technically we can move this to Dev dependencies I should have probably installed it in Dev dependencies uh but yeah it's okay it's not going to be a problem if it's in my dependences it's not too big of a package great so we now have that now let's go ahead uh and let's do the
following we have to go inside of clerk dashboard we have to go inside of configure right here and we have to go inside of our web hooks so let me just find the web hooks there we go right here and now we have to add an end point so this will be our URL right here so let's just add https your static URL which you get got from angro slash this is going to be API slash users uh I think that that's where we are going to put it please excuse me for a second I'm
just uh confirming that with the original source code all right let's also add SL web hook here so it is a bit more precise so SL API SL users SL web hook make sure it is like this so this will be the equivalent to Local Host 3000 API web hook right if in case you're confused but just make sure that inside of here you have to put your actual local tunnel because this app has no idea what Local Host is right that's only on yours uh great and now inside of here if you want to
you can add descriptions for example user synchronization you can write that if you want and then inside of here you can go ahead and find what you need in our case I need everything user related so created Created at Edge deleted and updated uh I don't really need anything else here great and let's just click create and what's going to happen now is that you can always of course change this URL if you uh accidentally wrote the wrong one and now let's go ahead and copy this sign in secret right so you have to click
on the little I icon you will see the secret again do not share this secret with anyone go inside of your environment file and I'm just going to add this with my uh clerk with my clerk um secrets so signing signing secret will be my new signing secret like this there we go uh how about we prefix it with clerk signing secret and yeah no need for next public in front of this because this will not this should not be exposed to the client right this should be on the back end uh only great and
now what we have to do is the following we have to take a look at the document ation for the web hooks you can find them right here in the web hooks section here and you can see and learn more about how they work uh why do they use Spix U some payload structure and if you scroll a bit down you're going to find synchronized data to your database and you can find a little guide here this guide will give us a little template for the API route which we need to create and you can
see they also use enrock precisely because it has this very useful uh domain right this very useful static domain so we already did all of this steps that's great we didn't have to do that we even created you know this cool little uh Dev all which starts both the web Hook and the project at the same time but now we have to uh set up the web hook which we actually did and we subscribed to all events uh for the user not just user created we added the signin secret to the environment local so we
did this that is is fine and now it says to set the web hook route as the public in your middleware we don't have to do that right because remember our middleware by default allows all routes right so I have to I can only protect that route I cannot make it more public than it already is now let's go ahead and let's add swix so let me go ahead and click here I think this will open npm uh looks like 1. 145.1 is the current version so let's do bun add swix at one point I
already forgot 45.1 there we go once we have swix let's take a look at what else we need so we need to create the actual Endo all right so they have their own endpoint here we're going to do a different one so let's go inside of source inside of app folder and I didn't really demonstrate how to create routes in nextjs that's something I forgot but it is as simple as creating uh client routes right instead of having page file name you have a route. TS file name so for example if you wanted to create
a test like this inside route. DS you could very simply uh write export const get and return response uh can I just write hello I think this should work so if if you now go to Local Host 3000 slash test uh do I have my app running I do not Bun Run Dev all so from now on you're going to use Bun Run Dev all not Bun Run Dev right and there we go you can see how it works right so that's how you create an API route inside of the nextjs app router very simple
right so remove this test for now and now where I'm going to put my API routes is not required but I quite like them to be instead of an API folder so yeah it's not required but I think it makes sense to keep them here and now let's go ahead and let's create our users and then let's create our web hook right here right so this is basically SL API users web hook so it needs to match whatever you wrote here SL API users web hook and ins here at route. DS file great and now
we can copy this example they have right here um in case you decided not to follow the documentation right or just want to uh see me typing don't worry I'm going to pause the screen so you'll be able to see each part if you want to do it like that so we first import web Hook from swix headers from next headers and web hook event from clerk nextjs server instead inside we export an asynchronous post function so similarly to how I just demonstrated the get function that's how you do Post function here one thing which
we immediately need to modify is the process environment signing secret that is because inside of our environment right here I've named it clerk signing secret just in case we have multiple signing Secrets I think it makes sense to add a prefix here so I'm going to change this process. environment to use clerk signing secret and I'm also going to modify this warning here to add clerk signing secret to environment or environment local the rest of the naming which is signing secret does not matter because it's a constant all it matters is that it reads the
clerk signing secret here great inside of here we initialize the new web hook using that signing secret again the web hook comes from SX which they use to retry web Hooks and have a very reliable way of using them we obtain all the necessary headers which we are going to use to validate this request because this request will be coming not from a user but from a random uh server so we need to validate that this request is actually clerk telling us hey new user was created what do you want to do with that user
right we don't want anything anyone else to spam us so if any of these things are missing from the headers it most likely means that someone is just trying to access this endpoint and it's not a vbo Handler if we pass this validation we are going to get the body so we are going going to get the Json version of the body but we are also going to get the text version of the body and then we are going to verify our web hook with all this information above the swix ID the time stamp and
the signature all combined with our unique signing secret so this is a very very secure way of only allowing our specific instance of clerk web Hook from contacting our application no one else can force themselves and Trigger this web hook and and thus uh manipulate our data in an unwanted way so that's why we are doing this kind of validation if this fails we throw an error and we say I cannot verify that this is who you who you think it is I'm just going to break the function otherwise we can finally access our events
here so you can see that we have received the web hook with id id and the event type of event type like this so I believe that uh clerk might allow you to test this out I'm not sure is there a way to like test it out uh let's see maybe event catalog logs if I go inside of user user created okay I thought that maybe they're going to give me uh some yeah I have no idea oh okay user created okay we can see um well we can can see the body but not exactly
what I want all right this is what we are going to do now we can remove or you can leave this console logs if they help you understand but this is what I'm going to do I'm going to check if event. type my apologies if event type is identical to user. created you can see we have autocomplete here because of typescript in that case what I'm going to do is I'm going to await database which we can import from database right this comes from our recently created drizzle database here so we're going to call that
database and we're going to insert into users you can import users from database schema right this constant which we have exported right here make sure you have exported it as well into users we are going to insert the following values we're first of all we're going to give it a clerk ID of data. ID my apologies we don't have data here yeah because it's only the structuring ID here I don't like that so I'm just going to call this uh data like this and then this can be data. ID if that's easier for any way
right so data transfer my apologies is data. ID will be the clerk ID like that name here will be data first uncore name and data last underscore name and image URL will be data image URL now we are getting some errors here and I believe that's because data can be all of these things but we know that it's going to be user Json if it's inside of this type of web hook so what I'm going to do is let me see can if I move it here there we go looks like that solves the issue
because if you read the data inside of this if Clause uh it knows uh more precisely what it is so it's a user Json whereas if you move it outside it can be all of these things great so just move it inside uh and well you can remove this conso logs so they don't cause any issues great so we now have the data from event. data and yeah if you want you can dist structure that like this and this is why I told you to choose Google as the only uh sign in option because if
you chose email I think you're not going to have this properties so you're not going to be able to create a nice name for your user right so if you use Google login I think you will almost certainly have first name and last name otherwise all of your users will be called null null because this can be null and since we are uh using this inside of template ler rals uh it's just going to show you null null so if that happens it's most likely because of the provider you're using so what you can do
if you really you know care about it you can do do name and then you can check if data first name is missing in that case U you can use data email addresses and then get the first and then get the email address but as far as I know this can also be null maybe or maybe not uh yeah basically just be careful with the provider you chose all right so this is enough for the user created now let's do event type user deleted I'm going to D structure data from the event here and I'm
going to do await database delete users where and now we have to use drizzle orm and we have to import equals from drizzle RM here we're very simply going to Target whatever the users. clerk ID has data. ID let's just see if I did this correctly so data data. ID can be string uh or undefined all right so this is what I'm going to do if for any reason whatsoever we cannot access data. ID we can only throw uh not an error but we can return new response and let me see how do I throw
an error all right I just specifi the status and I will just say missing user ID like this so if we are for any reason we don't have this which I don't know how exactly can happen we don't know which user to delete because remember when we create the user we store that data. ID which in here clearly always exists and we store it inside of clerk ID field so we have to find by that clerk ID whatever we have in the current data ID all right uh so now one more thing that we need
is if event type is user updated like this in that case we're going to do await database update users go ahead and set the name and you can just copy well you can just copy two of these right so the only thing we are not going to modify uh oh let me D structure the data from the event the thing we're not going to modify is the clerk ID instead we're going to use it to specify which user to update so clerk ID field and we query by data. ID there we go so that is
our web hook make sure to return the new response uh successful response at the end otherwise all of your web hooks will fail by default right so this is quite important that you have that here and I believe this is is it let's go ahead and try it out now so this is what we have to do go ahead and make sure you have Bun Run Dev all running make sure that you can go ahead and find this static URL make sure that you can visit it and it should load your app right this URL
should not fail great now the second thing we should do is the following we should go inside of dashboards clerk.com inside of your users and delete whatever user you have so just completely remove them what's going to happen now already I think is that it will attempt to go to/ API users web hook you can see that it got to API users web hook uh 200 right here and you can see the status of that inside of configure you can find web hooks right here what I think is going to happen now is that this
web Hook is failing I think all right so it looks like it is okay let's see why does it say that it's okay should have it failed let's see I wait to delete users yeah perhaps this doesn't throw uh an error it maybe just returns back null all right it's I mean it's okay we don't really have to throw an error if the user didn't exist in the first place now let's try and see do the other events work in order to try that out go ahead and run drizzle kit Studio so let me go
to local. drizzle. studio right now I have zero users inside of my database as you can see so what I'm going to do is go to Local Host 3000 don't go to your uh local tunnel so go directly to Local Host 3000 here and go ahead and create a new account I'm testing with Google so I know that I will have name and surname for this user so let's go ahead and try it out I'm getting created there we go and now if I go inside of my drizzle studio and refresh hopefully there we go
we have a new user record and you can see it says one right here as well so we have the clerk ID the name John do my image URL created that updated that we have all of those things right here great so now what's interesting is that if you go ahead and take a look inside of uh here you should see this successful uh this successful route let's see user created so this was successful great and now if you for example manage your account and update the profile picture let's go ahead I'm going to try
and upload something so here's my new picture right and what will happen now is that I should get another post request API users web hook here and I should also have another log here user updated as you can see it seems to work just fine so user has now been updated and instead of my drizzle Studio well you won't really see any difference but this image URL has been updated right it is showing uh this image right here which was not what was previously uh my uh image so great all we have to check now
is does deleting the user also work so let's go inside of dashboard here inside of users and let's delete this user From Here and Now what should happen inside of my drizzle studio is that I no longer have that user here and that is true amazing amazing job so remember from now on every time you start your app you're not going to use Bun Run Dev you will do Bun Run Dev all this will be crucial otherwise your data will not be synchronized and then the best course of action is to just you know dump
your database of course this is only for development this will not happen in production because in production uh this will just be your url whatever your app is hosted on this is a solution for local development this is the only way to do it actually there's no other way of doing this uh great great great job let's see if we did everything we planned so we created an enrock account we obtained a static domain we added a script to concurrently run a local tunnel in our app we created the users's web hook and we connected
the web hook to clerk dashboard that's it we did chapter five great great job the goal of this chapter is to integrate trpc inside of our project so why did I even choose trpc for this project well first of all end to endend type safety you've probably noticed that this is something that I'm quite fond of especially in some of my previous tutorials where I chose to integrate hono inside of our nextjs project and then I leveraged hono RPC procedures so that I can have very nice and typ safe uh API routes uh results mutations
queries all of those things well thpc is I would say a step ahead of that uh precisely comparing to hono RPC I'm not comparing trpc with hono right those are two different things I'm comparing hono RPC with trpc in this case trpc is a bit Superior I would definitely say so specifically because we have a much much closer integration to react query which allows us to use use Query and use mutation and all of those other hooks you you've heard of coming from used query natively what do I mean natively I mean for every single
procedure that we create inside of trpc we will automatically have use Query use mutation and all the relevant uh uh queries and functions and hooks generated automatically right in my hona RPC integration I had to write these myself but with trpc they will come generated out of the box but none of that is the actual reason why I chose the RPC the ACT ual reason I chose trpc is because version 11 of trpc finally allows us to do authenticated prefetching so no no matter how much I liked hono RPC one thing that honor RPC was
not able to do was do an API call inside of a server component which is basically this right here right imagine this said hono instead of trpc that was not possible the reason it was not possible is because what hono did in the background is it would initialize a fetch API call in a server component that's completely fine nothing wrong with that but that's not a native RPC call on the server it is a server which is calling fetch to my very own server right not exactly elegant nor optimized but the most important thing that
happen is that the headers got lost meaning that in hono it was not possible ible to prefetch authenticated queries we could only prefetch public database queries we could never do a prefetching for something that's specific to one user so that was a big issue and when I found out that version 11 of thpc has solved this issue I had to try it and trust me it's worth it I think you're absolutely going to love it it is understandable if you are a bit confused by this intro this is all very new especially if you've never
heard about prefetching or server components in the first place so I'm going to try my best to explain it and we're going to slowly build around this concept and get familiar with it so I hope uh it will finally click for you as well as it did for me while I was building this so why do I care about prefetching that much first of all I very very much like the render as you fetch concept because this specifically means faster load times and they are definitely visible right when I tested uh a prefetched server components
versus just a client component fetching for the first time the prefetch server component was visibly much much faster besides that we also have parallel data loading and one concept that I really like which is leveraging react server components as loaders so this is where I finally saw react server components as okay this is what I want to use them for every single time this is why they are so powerful for and this is the pattern that we're going to try to achieve keep in mind that that I made this graph and I have no idea
if this graph you know is Breaking All the Rules of uh uh any other uh sorry not graph the the diagram right I'm just trying to translate what's in my head inside of here so I hope you will understand basically we're going to start with a server component which will use trpc version 11 to prefetch data and it can also prefetch authenticated data which is something which was not possible before so I never really entertained this idea of uh server First Data pattern but now that we can do authenticated calls this is completely uh uh
completely doable right so we're going to use a server component to prefetch the data thanks to tpc's very close react query integration it will automatically be set inside of react queries data cache and then we're going to create a hydrate client component in which we are going to wrap our client component so that our client component can easily access the cache which was pre fetched right here and then in case we need to revalidate our client component we are going to keep that inside of this layer right here between the data cache and the client
component so we're not going to go back to server components we're not going to do any revalidate uh or revalidate path or any uh any of those things right it's going to be like this we're going to leverage this new thing server components to speed up our app and we are going to continue using our familiar client components just as we've learned to use them in years of previous react tutorials right this is the pattern that I really like because we are leveraging server components to truly speed up our application but we are also leveraging
our existing knowledge of client components to continue doing everything we know and love about them if this diagram doesn't make sense I prepared uh a code snippet here so inside of here we have an example uh video Dynamic video ID page. DSX route and inside of here I have a server component and inside of here this is what we're going to do we are going to call trpc Dov videos. prefetch and we're going to prefetch a single video ID and then we're going to use this hydrate client which we're going to create inside of this
trpc setup to wrap our video client component right you can see that in this video client component the only thing I'm passing is the video ID prop I'm not waiting for any data to be loaded here I'm just prefetching the data in the server component and then once it's been pre-loaded from server and cache what I will be able to do in the video client here is use thpc do videos that use suspense query and simply pass that video ID and this right here is already populated so you can see that inside of my videos
I can immediately do do map I don't have to do uh if this was you know waiting for load we would have to do videos question mark. map right because it's possible for videos to be undefined well not possible in this case because the moment this video client component is rendered this has already been prefetched so that's why this is a much faster concept for us to work with uh and of course we are also going to leverage suspense and error boundaries to display nice loading States and then uh we're going to try and do
both of these we're going to try one trpc videos. normal usequery and we're going to do one with the prefetch here and we're going to see which one appears faster and let me already tell you the answer this one will be much much faster that being said uh this is a fairly new pattern right I have uh contacted the creators of trpc you know for their opinions is there some is this something that I can teach is this an inv pattern and I've gotten a confirmation from them you know that this is an okay pattern
this is as far as they know uh the best way to do this kind of pattern so it's not something that I have invented myself and now you know trying to force on to you uh great so let's go ahead and try and do this so for this chapter we're not really going to have uh any complex queries but I do hope that we will at least be able to make this kind of example one uh server component prefetching and then a client component using that prefetched data uh inside of it great so we're going
to start by going to trpc website so move fast and break nothing end to endend typ saave apis Made Easy so depending on when you are watching this uh you might or might not see this version right here which is you are looking at drpc version 11 right so if you click here to see the list of changes for version 11 you go ahead and click here here's the thing uh this is version 11 is not currently out meaning that if you go ahead and go write mpm info uh for example let me see one
package so trpc SLS server for example if I do this uh let me just find there we go you can see that the latest version is 10.45 point2 right that is the latest version but inside of here it clearly says that version 11 is the newest version well uh it's not fully out then this is the state of it this is a current work in progress representing version 11 but the functionality the current functionality of version 11 is stable and it can be used in production the reason uh they are adding this alert is because
they might have some small breaking API changes between patches until they uh reach the this version right here so this is what we are going to do we're going to go ahead and click on docs here uh let's click on quick start and let's go ahead and find uh okay we actually have using nextjs here I would highly recommend that you go to the documentation as well so you can see exactly what I'm seeing here so click on using nextjs right here uh we can click on setup here and then we have this caution here
this guide is for nextjs pages router if you're using njs App router with server components CL check out the react server component docs so you can just click here and that will basically open client usage re react query integration server component so this is where we are going to start great so first of all we're going to need to install all of these packages trpc server at next trpc client at next trpc react query next and then 10 stacked react query Zod which we already have or maybe actually we don't have S and basically we're
going to have to install all of these things so I'm going to go ahead and do them one by one because I want to see the specific versions of this so I'm going to do mpm info like this because I want to see uh the current next version which is 11.0.0 D RC 730 so let's go ahead and do that one add at thep p c SL is it server there we go so AC your pc/ server at 11.0.0 RC 730 and I would recommend that you follow you know exact versions as I'm using and
I'm pretty sure that all others are going to be here as well so let me see trpc client yeah all of them are 11.0.0 RC 730 but I will check all of them just in case so that is your pc/ client let me just confirm it is the RBC client okay there we go then the same thing for drpc react query so let me go ahead and check with mpm info the latest is this one so mpm my apologies B add at drpc react D query at 11.0.0 dc730 there we go uh then we use
the latest version of 10stack react query uh it's pretty important that they match right so what I'm going to do is I'm going to do the same thing info for 10 stack react query at latest let's see uh this one is I'm not sure what should I be looking at here um how about I just visit npmjs let me go ahead and search for it here tack react query this one so 5.65 one that's the version we are going to be using here so BN add 10 stack react query 5.65 .1 like this and let's
see what else do we have to do we need Z client only and server only now I don't think these matter too much so we can just add all of this because you can see their versions so 3. 24.1 uh 0.0.1 and 0.0.1 here as well uh if you want to you can specify this versions specifically but I don't think any of these can introduce any too much of a breaking change right great so let me go ahead and go inside of my package Json here just to confirm my changes so I have 10stack react
query 5.65 one I have trpc client which is 11.0.0 dc730 and all the other trpc packages like react query and server are exactly the same my client only is 0.0.1 and I have also added uh server only which is also 0.0.1 now let's continue uh with the docs here so first of all uh it says to start with creating a trpc router so let's see if we can do something like that yes you can just click on view sample backend and you can just copy this entire thing so go ahead inside of your source and
inside of here go ahead and create drpc like this and inside create in it. DS and again just paste this inside so it's going to import init trpc from trpc server it's going to import cache from react it will create a very basic trpc context uh in here they just give you an example like a user ID that you will destructure from your out endpoint right uh and they create a basic trpc init project here and they export some procedures and some factories which you will be able to use later on so make sure that
you have this very simple uh init file F let's go back to the docs and see what we have to add next so we have trpc in it.s and now we just have to create like a basic hello route here so we're going to do that inside of trpc folder we're going to create routers and thenor app. DS so let's just copy this here inside of trpc we're going to create routers and inside of here ancore app. DS and just add it here and you should have no errors here because we have the base procedure
from here we have create trpc router export from here and we just have a very very basic you know hello procedure base procedure means your normal public procedure the input here is basically a Zod validation of what this procedure uh expects to receive and this is what it will return right so we just have a very basic API endpoint here so we can close the sample back and I believe that's all we have to do here uh my apologies not all we have to do uh we also have to properly set up our app folder
API trpc and then the catch route here so let's go inside of our app folder API and let's go ahead and see so we have to create slash drpc my apologies so drpc and then we need to create dynamic trpc and then inside route. DS like this and we have to copy this and we're going to have to fix this because they are using this till there uh as the Alias but we are using the ad sign as the Alias so we need to create trpc context from trpc init which we have right here create
trpc context and we need the app router from the PC routers app right here so that is our uh basic setup here and now that we have a minimalist uh backend setup here right I think we can finally close this there we go uh and now we can go ahead and continue with the setup and that is to create a a query client Factory so let's create a shared file inside of our trpc folder called query Das client. DS let's go ahead and do that so inside of our trpc folder we are going to create
query Das client. DSX uh actually it says DS so let's call itds properly and let's just copy this and put it inside like this uh looks like we are missing the module super Json uh yeah I'm going to comment it out for now you can comment it out as well because you can see it's commented out here but we are going to enable this later so it's okay it can be commented out for now so a very simple query client here um this isn't drpc specific this is 10 stack react query specific all right so
we have our query client Factory and you can read more about some default options they have set here I decided not to modify anything here I was quite satisfied with whatever word default settings one thing that we will change is we are going to enable uh data serialization that's so we can safely pass you know um things like date between server component and the client component if you don't serialize your data you will not be able to do that so that's why they prepare the super Json for you besides here we're also going to have
to I believe enable that inside of trpc in it there we go inside of here we also need to define the Transformer but we're going to come to that in a moment great and now we're going to have to create a tier PC client for client components so what does this mean well this brings us back to this example right here we're going to have two trpc instances one will be for prefetching so this trpc right here let me grab my pen so this trpc right here will be a server instance of trpc and this
trpc right here will be a client I have no idea why that has darkened but basically this one here will be a client instance of trpc so now we need to create both of those this one will come from import you know trpc client and this one will come from import drpc server so let's go ahead and first create a client one so this is quite a long one uh I'm of course going to pause my screen in case you want to type it out but you know you can also find it in the documentation
or in the source code so let's create trpc client. DSX client DSX like this and I'm going to go ahead and just you know show you a bit so make sure we mark this as use client so we can mount the provider from a server component these are our Imports make sure you are getting no errors here specifically for this relative Imports so if this is red it means that you put your client. TSX in an incorrect place or your router something is incorrect if everything is is is inside of the trpc folder there should
be no errors here after that we create a trpc react uh uh instance here and then we simply combine this uh with our react query instance here so basically this is what we are going to call uh for our uh client components right here and this is where we're also going to have to enable our Transformer uh a few gacha here is well this part so they use versel URL as the environment variable um if you choose to deploy some other than versel you will obviously have to change this uh you can change this in
a couple of ways it's not that difficult you know you can you can also use um process. environment next public app URL and then inside of your environment. local you would simply do next public app URL by by default inside of here it's going to be Local Host 3000 and then when you deploy you would change this to be you know your actual application which would be www.code withth antonio.com right that's how you would do it uh if you are not deploying on versel but in case you are deploying on verel you can just leave
this to be versel URL and let it fall back to Local Host 3000 when you're in development and everything's going to be just fine so in order to complete this tutorial I would recommend just deploying on verell because it's super simple and it's guaranteed to work and then if you want to explore it yourself just add a little too here uh modify for outside versel deployment just you know a little note for yourself in case you plan on experimenting with this later so you don't get confused oh why is it broken all of a sudden
and obviously this is also important this get URL leads to SL API drpc which is obviously targeting uh our app folder app drpc which we just created right now so if you for whatever reason decided to name this something else or perhaps made a typo here make sure to fix it and to be specific you need to change it here as well and you will also need to change it inside of this client. TSX great so uh I hope that I've shown you enough of this file so that you can write it yourself uh you
you can also find it in the documentation you can find it in my source code as well and now that we have finished this step I believe what's left is to create a TPC caller for Server components and this is where the magic happens right usually not single other RPC had this solved right uh we were simply not able to natively call an RPC instead of a server component which is basically this part right we were not able to do this this was always just a normal fetch or API request which loses all the headers
and has no idea which user made this call but thanks to this magic of thpc uh caller we can finally do that so let's create thpc server. DSX on the same place on the same level as client so server. DSX uh and let's copy this let's add it inside make sure you import server only in the beginning what this will do is it will throw an error I think both in local development and in production I think it will even prevent the build from happening if it detects this file being imported in any way you
know even abstractly so without your explicit knowledge or approval it will throw an error saying hey this file somehow found its way into a client side and that's dangerous you can't allow that so this is you know to ensure that our uh server only trpc instance can only be called on the server so no malicious requests can be made great so this one is quite simpler again just confirm you have no errors in this relative Imports and this new query client should now be used here as well great so so far you should have the
following the trpc folder the routers with a very simple hello based procedure you should have bit larger client. DSX component here an in it file a query client and server. DSX and you should also have app API trpc trpc route. DS and now you can finally use your API in here they have some examples so how about we go ahead and try and prefetch uh hello here and now we're going to try out all of these examples so first of what I'm going to do is I'm going to do Bun Run Dev all remember we
have this new Dev all command we are no longer running just Dev so I will just try to load my app you know just to see what the state is so this one says I will load videos in the future how about we go inside of the app folder home page. DSX and let's do something simple let's mark this as use client like this so this is now if you refresh a client component and now what you should be able to do is you should be able to extract data from trpc which you will import
from trpc client right this is this big component drpc do uh hello do use Query and it expects a message I think let's see what does it expect a text all right so let's just pass in text uh Antonio like this and then I will say this client component says data and is it data. greeting my apologist and you can see that he already put a little exclamation uh question mark here so let me go ahead and try oh yes I think we forgot one important thing how did I miss that and uh let's see
how did I miss that we created this for client component and I'm pretty sure oh yes Mount the provider in the root of your application okay so that's a big a Miss by me let's go ahead and do that so we're going to go ahead inside of our app folder and let's go inside of layout. TSX and inside of body let's add trpc provider and wrap the children inside of that so trpc provider comes from uh trpc client let me just move it here so make sure that you have imported the trpc provider it should
be this component right here uh great and make sure it's wrapping your children so you're doing this inside of the app layout root layout now go back instead of homepage. DSX and I think that now uh it says client component says hello Antonio right if I change this to Antonio 2 it says hello Antonio 2 and now the way I refresh is I use uh this sh I'm not sure if you can see but I use shift command R which basically clears my cookies and my cash right so I always do that uh because if
you just refresh sometimes it will have cash and you won't really see the loading uh happening but you can see how right now it's always kind of loading this this is your normal usual way of loading data nothing special with this now let's try an alternative method which I was talking to you about let's remove this use client and let's go ahead and instead of importing trpc client let's import trpc server and what we can do here is first of all we can mark this as an asynchronous function for example and we can do data
drpc hello uh and let's see do I just call it like this I think yes text Antonio and we can just await it and then right now it will be the same thing except I think we don't need this right so you can see that now what we're doing is we are literally fetching on the backend so this is a server component now fetching you can see how we await here so yeah once we await of course it's going to exist right there's no doubt about this uh and I just remembered one important thing I'm
sorry but I have to I have have to stop here because some of you might have some issues um let me just bring your attention to one thing I promise we're going to come back to what I was demonstrating but something did happen to me when I initialized trpc and I wasn't sure how to fix it exactly uh in case when you hover over your data here you should be seeing data greeting string when you hover over text you should see text string when you hover over hello you should also see all of these proper
types what can happen is that you are constantly seeing any I'm going to try and find uh if they have some troubleshooting for that there we go it doesn't work I'm getting any everywhere if that's happening to you uh you should go ahead and read this part right here and try and do these things if you followed the entire setup the same way I did it will probably work but this is what finally fixed it for me it was adding this to right so typescript dot SDK to use this typescript and typescript enabled prompt use
workspace TSD K let's add both of those to our vs code settings. Json so I promise I'm going to come back but this is quite important because some of some people will uh stop watching here and try to debug themselves and perhaps uh they won't know that I explained the fix in a moment so let's add vs code here and settings. Json and just add them here and if this opens up simply click allow like this and what you can do is press command shift p and then just reload window just so it kind of
resets the entire thing and go ahead and visit your Source app Home page. DSX and you can see that now I'm still getting uh my types here but uh in case you were getting them as any this will most likely fix it but even if you even if this worked for you initially if the types were okay I would still recommend having this because you will commit this and then your colleagues who open this project will have the same settings and then they're not going to have any problems as well so that's one troubleshooting issue
that I had uh and it was fixed immediately once I added this and you can see that they also highly recommend committing this file so the colleagues can also get the same experience all right now let's go back to uh our example so we just learned how to use trpc purely on the client we learned how to use it purely on the server which is this but how about our our prefetching solution how about a hybrid between those two how about leveraging what uh leveraging the speed of server components but leveraging the familiarity and the
interactivity of client components right now because we can't really do much with this sure this loaded obviously faster than client side but what exactly do we do with this server components are not that interactive we can't even add a hook here we can't add on click right it's a bit more complicated to work with it it seems like every everything we gained with speed we lost with interactivity so that's why we are searching for a hybrid solution to get the best of both worlds let's do that now instead of actually expecting any data here we're
going to change this to be void hello. prefetch like this keep in mind that I'm not exactly sure if this needs to be asynchronous or not uh and even more if you don't make it asynchronous it's not going to throw any warning unfortunately I can't exactly tell you if it needs it or not but in my project I wrote async everywhere where I'm using prefetch right so I would recommend you do the same thing and now what's happening is that we are not using trpc on the server to get the result in a constant and
use it we are using it to populate our data cache in on the server side and then allow the client component to use pretty much already loaded data right that's what we are trying to achieve so what I'm going to do is inside of my homepage here I'm going to create a little client. DSX and I will do use client here I will import trpc from client like this and I will export const you know page client component like this page client and I will import page client from do/ client like this so now it
should just say page client here but if you take a look uh inside of your uh project here if you do a hard refresh here uh oh yeah so nothing's going to happen yet but the moment you add const this time inside of square brackets not curly brackets because we'll be using trpc hello pre uh use prefetch Query like this uh my apologies use suspense query not prefetch query uh let's see what exactly am I missing here an argument for input if you try now page client says data. greeting first things first you will already
notice that this is always loaded there is no possibility of this being null right because this is a suspense query we already started loading this on the server but we are going to finalize the cache and everything on the client here so right now you can see that we are using this as a client component so we can now go ahead add use effect and all the other things you know which we were not able to do uh in the server component but we are still leveraging the speed of server components because remember in our
first example which was you know just uh trpc hello and it was just use Query that was a bit slower for for a second it was empty right and then it got populated so when we combined these two we get the best of Both Worlds but this is not exactly a finished example there are some things missing here and that is the hydrate client suspense and the error boundary so let's go ahead and add hydrate client from trpc server let's add suspense from react and let's go ahead and give it a fallback padding loading like
this so now if you do a hard refresh uh I think this query is not really uh um complicated enough to ever show the loading state but now we do have a suspense which will show loading right uh we also need to add uh an error boundary here and for that we can use uh react error boundary let me just go ahead and see if this is the one I want to use uh yes this one so let's use 5.0.0 react error boundary bun add react error at 5 0 Let's import it here so import
error boundary from react error boundary like this and let's give it a fullback error right now and nothing should change really but if you go ahead inside of your uh trpc routers app folder and if you go ahead and throw new trpc error let's see I think you can just select code uh bad request so this is also another super cool thing about trpc is that you have this strictly type errors right so you can just add not found if you want and it will be typed so now you can see it says loading it
will attempt to load it a few times and then I think after three or four retries it will fall back to error so this is uh the server First Data pattern that we will be uh trying to achieve throughout this uh project we are going to leverage the speed of server components to prefetch the data and then we are simply going to let the client component not have to fetch anything initially it will just have the data ready inside and in for any loading States or errors we're going to leverage the suspense and the error
boundary uh and yes it will still have a loading state but these loading States will be much much shorter uh than in comp comparison if this was just you know use Query and then we would have to destructure data and is loading here and then we would do if is loading right we would have to do all of those things here that's going to be much slower especially noticeable on larger queries than this hybrid combination of both of this and now there are a few you know tips and tricks that I just want to end
the chapter with every time you use use suspense query you have to have an equivalent prefetch here same thing is true if you do use suspense uh I'm not sure if I can do it now yeah I cannot do it right now I think because I'm missing um the proper returns to create this into an infinite query right but if you are prefetching infinite you also need to suspense infinite query right you can't exactly you have to be careful uh about uh doing something incorrectly also uh for example if you have some some input inside
of here if you suspend squarey Antonio 2 but you prefetch text Antonio that will not have its uh effect right let me just remove the error that I throw here and remove the import from here so if you you know refresh this now it works because this is super simple but it's actually not leveraging the pre fetching that I was talking about right because this was uh this doesn't make sense right now because all that these queries are doing are just returning back the input but imagine this input was a video ID we would first
have to go to our database and load the video right so imagine if you have prefetched video ID one and then you suspense query video ID two that would completely lose its purpose it would not be able to prefetch what it was supposed to prefetch right I hope that kind of uh made it clearer uh we currently don't have any authenticated examples so I can't really show you any other gotas here but basically in case you use use suspense query for an authenticated procedure and you forgot to prefetch you're going to get an error that
the uh procedure has fold back to client and it's unauthorized that's because the cookies from the header which were supposed to be prefetched here we're never passed inside of here so yeah it is kind of you know not exactly perfect but there is I mean when I say not perfect I mean you have to be careful it works but you have to be careful with how you're doing it but I will just pause the video for a second and I will try and find one GitHub discussion where they are uh finding a way to make
this a bit more stricter so the application itself will warn you if you're doing a mistake like that so this is the discussion that I was just mentioning um basically this is one of the uh authors of the of tanack react query and inside of here they are introducing or discussing this exact issue which I just mentioned right if you forgot to prefetch and then if you use use suspense it will not work but it will also not throw any errors right so the main problem we are seeing with prefetching is code this location I
guess that's the proper name for it so if you just call us supen Square in your component and having it work it's very convenient but without a compiler like relay it will not be possible to extract those data requirements and Trigger prefetching somewhere else automatically so what we're left with is duplicating what we do have a component called use suspend query with certain options and use the same option in your route loader to do pre fetching right so that's what we are currently doing for every used suspense query we need an equip equalent route loader
to prefetch that query uh obviously there are some issues with that uh and here is a proposed solution so they are working on query strict mode component which I suppose you would wrap around your application or you will most likely just add it to for example uh trpc client right you would most likely just add it inside of here you would wrap the query strict mode and it will then warn you if you are missing a prefect or if you have an unnecessary prefetch so I think that's a very very cool solution here you can
read more about it so just go ahead and uh Google this even I left a comment here because I noticed that if I call prefetch infinite but combine it with use suspense query instead of calling use suspense infinite query in that case there is also an issue here but this does not really fall on under the same problem as they are trying to solve here and I did get an answer for that but yes you do have to be careful about that and I'm going to try and emphasize this throughout the project the more we
build but I hope that for now it kind of is a bit more clearer than it was in the beginning especially the first time you saw this diagram I hope you now understand what I'm trying to do so basically we're trying to get the best of both worlds we are trying to get the speed of server components and the interactivity of client components we are going to exclusive use server components as route loaders and nothing more so server components will just R prefetch the data and then client components will have that data ready to use
and it's going to be very very fast for the end user uh great so I think we uh successfully enabled trpc what we're going to do in the next chapter is we're going to combine our clerk authentication with trpc and we are slowly going to organize our routers and procedures in a nicer way great great job the goal for this chapter is to continue our trpc configuration which we started in the previous chapter in this chapter the goal is to enable Transformers on trpc ADD out to our trpc context add a proper protected procedure and
we will also Implement rate limiting and that will pretty much wrap up everything we need in regards to our T trcp configuration and and then we will be able to finally start adding some other entities to our database uh we're going to start with a very simple entity later on called categories but let's not focus on that let's focus on what's in front of us let's do the easy one first enabling Transformers on trpc let me just refresh my app to ensure everything loads nice and well and let's go inside of trpc right here inside
of my init method right here you will already see that I have this commented out Transformer super Json so let's uncomment that and let's go ahead and import super Json from Super Json now keep in mind that we don't yet have a super Json so let's go ahead and do Bon add super Json and you're going to see what which version I'm using in a second so I'm using super Json at 2.2.2 like this so let's go ahead and ensure that we have that here that's great now let's go inside of query Das client and
let's do the same thing here so import super Json from Super Json enable serialized data to use super Json serialize and enable uh der serialize data to use super json. deserialize and in the server. DSX I think we don't have to do anything let me just confirm in the client here do we have any super Json here we do have super Json here as well and we also have an error here so find trpc client here HTTP batch link so I'm inside of client. DSX go ahead and uncomment this remove this line and now we
also have to import super Json from Super Json like this and now we should no longer have any errors I'm just going to search throughout my code to ensure that the are no errors anywhere and nothing much should really change here if I refresh now I should be getting no errors at all great so now that we have that let's go ahead and see what's next what's next is to add out to trpc context so right now let's go ahead and go in one of our routers here if I go ahead for example and add
on the log here hello world and if I refresh here we should be able to see Hello World right here on our server side so now I'm going to try and consol log the currently logged in user at the moment we can't really do that uh well technically we should be able to do it if we just the struct your user ID from await out which you can import from clerk nextjs server and if you mark this query as an asynchronous query and then if you just add user ID in the console log and refresh
you can see that my user ID is null here but if I go ahead and log in now with my email here let's continue let's get redirected back okay let me try again there we go you can see that now I have a hello world and I have my user ID so that's great some of you might already have some ideas uh how we can you know fetch the user from the database now and get the actual uh the database record but it doesn't really make sense uh to do this every single time because what
we have to do then if we want this to be a protected procedure is check if there is no user ID and then throw new uh trpc error like this and give it a code of this would be unauthorized or unauthenticated right so it doesn't make sense to do that every every single time so for now that's not something we're going to do we can leave this we can even remove the async we don't need any of this let's create a reusable way of doing this in order to do that we're going to go back
inside of our inth method here and we're going to revisit our create trpc context if you want to you can learn more about the context and you can see how they give you an example of using next out to get session inside of that context uh I I had a talk with uh one of the maintainers and creators of trpc and I asked them you know how exactly what exactly should I do in the context and they gave me a tip that I should not really do any database queries inside of here so the context
is something that will be available for every single uh API call that you do so we want to keep it as light as possible in order for us to do that the perfect method is the await out method from Clerk nextjs server and then simply return clerk user ID user ID the reason we want to use this one uh is because the out helper Returns the out object of the currently active user uh only works server side such as server components rout handar okay I was hoping they're going to give me a description of what
I was hoping for here uh basically this doesn't do as far as I know uh any uh fetch any fetches right it simply D structures the current jvt token or the current session right which makes it a very lightweight method to call as opposed to for example current user from clerk nextjs which uh Returns the backend user object of the currently active user so it calls the fetch as you can see right here it calls this method whereas out does not do that right here uh all right so now that we have this I also
return this in this context under clerk user ID so the reason I return it under clerk user ID and not just user ID is because I want to be very explicit right my user ID right here inside of this context uh whenever I see user ID I will explicitly mean on this my database user ID but what I have here is my clerk user already so I want to make sure that all of my uh procedures will know that whatever I'm doing in the cont context is not the database user ID it's just the clerk
user ID all right so we have the context Here and Now what we have to do is we have to create a proper type for our context so export type context will be awaited return type of type of create drpc context like this now when you hover over context you can see that we have a potential clerk user ID inside and now we're going to add that inside of this init thpc do context passing the context type like this and then pass in the create like that there we go and now uh what we are
going to do is the following let me just see uh can I now go inside of my app here we have options is context okay I think that instead of options here I think I can do console log now options sorry options there we go do context. clerk user ID like that so that's what I wanted to uh see so let's go ahead and just add uh from Context options context clerk user I mean since the moment we are getting a proper types here it probably means that it's working just fine so let me go
ahead and refresh and there we go from Context and we have the proper logged in user ID here great so that's one step better we don't have to repeat our uh await Al method right uh we only did it once but how about we create another uh type of procedure which we can then uh use every time we know the user has to be authorized for that call in order to do that we can expert cons protected procedure and what we're going to do is we're going to extend the base procedure like this and then
we're going to use use a middle inside so let's use asynchronous function is alphit like this and it's going to have options inside let's go ahead and let's destructure context from the options if we have a missing context clerk user ID we are simply going to throw new trpc error make sure you import this uh from trpc server uh from the past package not our instance of the PC server and passing the code unauthorized like this and then what we can do inside of here is uh well we can you know end the call here
for example we can just return options. nextt like this and we can just spread the context inside and user ID will be context clerk user ID well actually we don't have to do that because okay this doesn't make sense now but I will explain why I'm doing it like this in a second but already uh nothing should really change right now if you go inside of your router here for example and replace the base procedure with protected procedure now and remove the base procedure here if you refresh uh you shouldn't get any errors everything should
work fine but if I log out and refresh now I should be getting uh 401 as you can see right here something is going on something is wrong that is because I explicitly used protected procedure but if I change it back to base procedure and refresh there we go no error at all I simply get a conso log from Context to be null great but let's go ahead and do one step further which is to actually always add the database record which we have because remember we have our web hook running I'm running Bun Run
Dev all which means that I have a proper web hook here which means that all of my users are synchronized let's just confirm that if I go to bunx drizzle kit Studio Here Local drizzle. studio and if I go inside of my users uh I see my user right here so I want this object to also be available in my protected procedure and that's what we're going to do now so let's go inside of the init here and first of all we throw the error if this doesn't exist and now let's go ahead and let's
get from await database which we can import uh from Alias database like this so we're going to do database. select from users which we can import from database schema where equals users. ID context clerk user ID and I made a little mistake here we're going to fix it in a moment first of all when need to import equals from drizzle orm and second of all uh this query will not work why well because users. ID will never equal clerk user ID instead we will have the field user. clerk ID there we go like that and
uh drizzle will always return an array of items right so even if you do limit to one which is completely okay to expand effect here it will always return back uh an array of items right you can see that it's always an array the reason they chose that is because that's how SQL Works SQL will always return an array so no matter how you want to do it you can limit it to one but just make sure you know to uh you of course if this has changed in the future you know don't worry just
hover over data and confirm is this an array if it has a little array here that means data is an array so what I like to do is I just destructure the first item from here the user like that and then what I do is I just check again if there is no user in that case I'm going to throw a new error just the same one as above uh which basically means okay we you are logged in but for some reason I cannot find you in the database and I cannot let you continue further
with this request all right and then what we can do here is we can spread the context and just simp simp L pass our user here like this there we go so now inside of our app here I'm going to go ahead and use the protected procedure here this time and now I'm going to check database user to be our options context. user as you can see here so now I can get that user let's ensure that we logged in so this query does not fail Let me refresh let me scroll all the way down
here and there we go my database user is loaded here so that's how we're going to use the protected procedure right uh we could have done this entire database call inside of the context but remember the context will run every single time for every single procedure that we do regardless if we want that to be a public procedure or a private procedure so that's why it's best to just destructure the jvt token something that doesn't do any fetch calls which in this case is await out in case you yourself are you know planning on changing
this to next out this would be get session for you basically something that's not doing an API call and then in your protected procedure you can go ahead and do the API call and here you can see exactly what we previously did right we created a type of context and we passed that here uh and then we were able to access options. context inside of a procedure uh excellent so we finished that let's see what else do we have to do what we have to do next is we have to add uh rate limiting so
that's another interesting thing that we have to add and it's a great introduction to up stash because we will be using up stash in uh different places in this project the first place will be an easy introduction to up and uh a good practice for us to uh play more with this trpc middlewares we're first going to implement rate limiting so no one can spam our trpc end points and then we're later on we're going to use up stash for background jobs for long running tasks when in which we can completely rely on up stash
for retrying and keeping them in order those are going to be long running AI uh tasks which more often than not will time out in any non-edge or serverless function right they can take up to a minute or something like that or even you know hours up stash workflow will be a perfect solution for that so that's why I wanted to introduce to redis uh very early on so let's go ahead and finish that last thing because right now you know no matter how many times I refresh my app here uh it will always load
I can never spam it enough so let's go ahead to up stash.com and let's go ahead and create an account once you're in here go ahead and create a database I'm going to call this new tube for the region uh I really uh don't know which one is better for you you can just select the first one and let's go ahead and select next uh select the free plan right and let's go ahead and see everything that we need right now so what I'm going to do is I'm going to click here and I'm going
to click on rate limit analytics uh and maybe I have to go to docs here let's go ahead and go together to learn how to uh set this up so examples nextjs okay this leads me to this will lead me to the GitHub file let me see if we can do it uh easier like this so we can start by just installing the packages which we need starting with the up stash redis package so let's go inside here and let's do bunad at upst RIS at 134.5 that is the current latest version at the time
of this tutorial so I'm gonna add that and then we're also going to do bun add at up SL rate limit and that is uh 2.0.5 like this so let me quickly show you my package.json there we go so upstage up stash rate limit and up stash Reddit red my apologies okay now that we have those two we have to add our environment VAR Ables so you can find them here in your you can just click you know on your project here click on your database and you should be able to see your uh environment
variables right here so you can copy the uh up RIS URL and up rdis token right this you can just click copy go inside of view. environment. local and add them here so up rdis URL uh and upd uh red rest token like this again do not share this with anyone now let's go ahead and let's go inside of init.d and what we're going to do here is we're going to import uh both reddis and rate limit so let's start by importing redis from at up stash redis and let's import rate limit from at up
stash rate limit like this and now what we are going to do is we're simply going to separate add some space between base procedure and the protected procedure and let's add our reddis here to be new reddis with the URL to be process. environment and simply copy the up R rest URL and we are going to have a token to be process. environment and then copy your op RIS rest token copying is almost always safer than um right typing it because I've made so many typos when I did that and you know let's do this
in a proper way let's go ahead and create a lib called red. DS and let's do that instead so new redis from UPS reddis and then we can export this like this so we don't have to Define it here for no reason uh and we can do the same thing inside of our lib here for rate limit. DS so rate limit. DS like that let's import rate limit from up stash rate limit let's import reddis from do/ RIS and then let's do export con rate limit to be new rate limit pass in reddis as the
first argument and then the limiter inside of here will basically be uh your definition of what is too many requests so for example 10 requests within 10 seconds will cause a timeout or you can do 50 requests with with uh within 10 seconds will cause a timeout request I believe that in docs here if we search for sliding window you can perhaps learn a bit more about how it works let's see sliding window traffic protection right here there we go so inside of here you can learn a bit more about this limiter and how it
works for example 10 second 10 requests within 10 seconds uh and you can you know block this by anything if you have an IP address you can block by that if you have a user agent you can block by that I'm going to show you a very simple way to block by the currently uh logged in user ID so let's go ahead and do that inside of the protected procedure what we're going to do is the following once we ensure that we have this user inside of our database what we're going to do is we're
going to check if we we will allow this user from making any more requests by using a wait rate limit which you can import from lib rate limit and yeah we don't need these two Imports I think it makes no sense to have them defined inside of this file so await rate limit. limit and pass in the user do ID like this and in case success fails we are simply going to throw new drpc error code to many requests like this and now if you go ahead and try out and refresh a lot of times
so still not failing not failing and now as you can see I have trpc error too many requests and I believe that if you go inside of here rate limit analytics uh and we select new tube database here uh I think that you should be uh Maybe not immediately but I think you should be starting to see uh some requests here in a moment or perhaps this will only appear in production uh great so you've learned how to build all of that if you try one more time then it goes back to normal of course
depending on you know uh your development style you might uh really really spam this refresh so feel free you know to increase this to 100 or you know you can completely just disable it if if you think this this will cause more problems than not you can just conso it out like this uh and yeah I think that pretty much um says everything we need uh to do in this chapter right let's see so let me get my pen here so we added the Transformers to trpc we added out to the context we added out
procedure and we added uh rate limiting and we also got familiar with up stash and reddis great so now let me do one more thing uh and the only reason I'm doing this right now is because I did it in my project and I saw it in a couple of other in a couple of other uh projects but I'm not exactly sure if this how much this benefits you but inside of client. TSX when it comes to trpc create client inside of this HTT HTTP batch link after URL what I did was I added asynchronous
headers right here I Define the headers to be new headers like this and then I did headers do set x- drpc source to be next js- react and I return back the headers now I found this to have some logging improvements but not more than that I just want to stay true to my source code uh if this ends up causing any problems for you you can of course remove move it because we just tested everything still works fine with or without it but I think even the T3 stack has this line of code so
I think it will be helpful for logging and debugging right so if you want to you can add that and I think that this new headers is native it's a fetch API yes mdn reference so there was no import uh for the headers great so I think that wraps up our chapter right now uh what we're going to do next is we're going to go ahead and add uh categories to our application so we're going to load that carousal right here you're going to see a picture in the next chapter and we are going to
organize our router a bit because right now we only have this entry point underscore app and we just started writing procedures here and you can probably imagine that can be quite a mess if we just start putting everything here so we're going to restructure this just a little bit bit uh and yeah that's going to be a good start into uh going back to our previous lesson which was uh the hydration right so we're going to come back to this prefetching thing but this time not with dummy data but with actual categories which we need
to prefetch from our database great great job in this chapter the goal is to create video categories this is the component which we we are going to create and we are going to put it right here below the search page and just above where our videos are going to be and this is what it's going to look like we're going to use the carousal component from shaty and UI so it's going to look pretty smooth we're going to start by creating the categories schema after that we're going to push those changes to our database then
we're going to create a script to help us seed the categories because categ are not something that users are going to create it's more of a super admin thing that will simply be uh set in the project for users to then choose from and then we're going to utilize our knowledge from chapter six about prefetching uh data in this case prefetching categories in a server component and then we're going to uh use suspense query them inside of a client component so we're finally going to leverage that knowledge in a real world example and this is
a fairly simple uh this is a very simple entity for us to do because there aren't really any create read update methods on here and one more thing that we are going to do here before we prefetch is organize the RPC routers right that's one more thing we're going to do all right let's start by creating our seed script so I'm going to go inside of the source folder and I will create a new scripts folder inside and in inside of here I'm going to add seed categories. TS what I'm going to do now is
I'm going to uh add it to do here create a script to seed categories so let's just have this ready we can't do it yet because first we have to go inside of database schema. TS so let's go ahead and Export con categories to the pg table categories we're going to have the primary ID to be uu ID so let's add that each category will have a name which will be required and also unique so no two categories will have the same name now the description well it will depend on how you want to handle
these categories as you can see you know in here the descriptions are really not going to be used for anything but they might be useful for you if you ever decide to create a dedicated screen for each category so then you can have a little description saying you know for comedy you can have a unique thing saying like the funniest videos or something like that so if you want you can add a optional description here like that so not not null so it's just an optional description and let's just copy our two created ad and
updated methods here and one thing that I'm going to do here is I'm going to add an index for the name of the category in in case we want to query by the name so I'm going to get uh T and I will return unique index here name index and I will do it on t. name there we go so now we have categories here perfect so what we have to do now uh is we have to go inside of our inside of our terminal and we have to run bonex drizzle hit push like this
and once you run this you should get a well a success message that everything was pushed fine and then we're simply going to check our drizzle Studio to confirm that this schema has truly been uh modified uh so it says no changes detected oh my apologies I am in an incorrect project let me go ahead and go back here ignore that for you everything probably went well uh what happened to me was I was in a different terminal so new tube project bonex drizzle kid push I will just run it again and now I expect
everything uh to work fine there we go so changes applied pulling schema from the database I noticed something was wrong uh and that's what it was so now let's go ahead and do bonex drizzle kit studio and let's go to local. drizzle. studio just to confirm that we now have have categories in here with ID name description created at and updated at great now we can go back uh inside of our seed categories right here and let's go ahead and Define our categories so const category names are going to be the following cars and vehicles
comedy and well it makes no sense for you to watch me type this out I'm just going to copy all of the categories which I'm going to have and you can pause the screen and add them if you wish it really doesn't matter right the more you add them the better the site is going to look like but these are the ones I was able to found in the YouTube Studio category select input so it only allowed this categories right here so I decided to only add them as well great now let's do an asynchronous
function main let's add a conso log here seeding category like this and now let's open a try and catch block let's resolve the catch block first so we're going to add an error here error seing categories like this and let's add a little space here I have a typo here and of course let's do process. exit one because we will be running this in a script and make sure that you execute the main method here outside of itself of course and in the try method what we are going to do is we are going to
do category names. map and get the individual name and return an object here and we are going to reuse the name and we're going to generate the description by saying videos related to and then just name to lower case like this so basically we're just going to create a a very generic description for every single category name so it's going to say uh you know videos related to sports videos related to travel and events so we are creating the values and what we have to do now is await database which you will import from at/
database do insert into categories which we now have from our schema as well and pass in the values which are values like this after that cons log categories seated successfully like this now in order to run this script you are going to have to use something that supports this kinds of imports right that's why uh in the beginning of our tutorial I told you that I highly prefer if you used bun for this because bun lets us easily run typescript scripts with es6 Imports so so I'm going to show you first how it looks like
in bun so let me go ahead and try running this let's go inside of the terminal here and I will do Bun Run actually not run just bun scripts actually Source scripts seed categories there we go seeding categories and then categories with my typo seeded successfully so if I now go ahead uh and open the drizzle kit Studio again and if I refresh the entire thing now you should see uh 14 categories here people and blogs film animation everything here and you see that you have the relevant description as well right now I'm not sure
exactly what happens if you try to do npm uh my apologies not npm if you try to do node Source uh scripts seed categories there we go you get errors right because we are using import which is not something that you can uh use uh I'm not even sure what's the error can I use import statement outside a module right it's just things like this that make me highly prefer using bun in these tutorials uh if you really really want to use uh want to use node you can do it by adding cons this to
be requir and honestly I'm not even sure if this can use an aliens let me try this I'm not even sure how this works I this is so funny I have not used them in such a long time I'm going to pause the video and try and give you a solution if you really want to use a node so I think that the solution might be to use requires like this I think so I have to escape this and go inside no or maybe just here see this is why I really really just prefer us
bun let's just try if I run node cannot find module okay yeah I give up I'm I'm really not going to try and solve this because bun just makes it so much easier so please just just use bun for this it's going to be 10 times easier for you uh to continue with the tutorial all right so now that we have our categories set what we're going to do is we're going to create our categories uh procedures right so let's go ahead and do this let's go inside of source inside of modules and let's create
a new folder called categories like this and inside let's create a server folder and then a procedures. TS like this and inside of here what we're going to do is we're going to import database from database we're going to import categories from the database schema and we're going to import base procedure and create trpc router from trpc in it and then we can safely export const categories router to be create trpc router and passing get many and this will use a base procedure meaning it's going to be public to everyone inside of this query we're
going to have an asynchronous method and all we're going to do is we're going to get the data using await database select from categories like this and we return back the data that's it that's our categories router one issue though is that we are not really using it anywhere so let's go ahead and let's refactor uh refactor our tipc routers here so go inside of tipc routers and what you can do now is you can remove the hello protected procedure you can remove this from here you can remove Zod as well so you are only
going to need to create the RPC router and above you're going to import categories router from modules categories server procedures and then passing categories here to the categories router like this there we go now obviously we're going to have some errors instead of source app folder home and inside of here we have page right hello no longer add exists so how about we modify this to load our new categories so let's prefetch the categories uh let's see categories my apologies categories get many prefetch like this and let's go inside of client here and let's do
the same thing categories get many and nothing is being passed here and what we are going to do here is simply Json stringify data like this so if you now go here and refresh and of course have your app running B run Dev all and reload your application you should now see the categories inside of a Jason format right here there we go great so now what we have to do I believe is uh inside of our chapter eight so let's keep track we have successfully created the categories schema we push the changes to the
database we have seed the categories and we have organized our trpc routers and now we have to properly pre we are already prefetching them but not in their proper place right so now we have to create the categories component so let's go ahead and fully focus on this now we are not going to be needing this protected anymore we just use this for demonstration let's remove that we are also not going to need the client. DSX anymore let's remove that and let's exclusively focus on homepage. DSX inside of here let's remove the page client and
let's also remove the suspens in the error boundaries as well we are only going to do void trpc categories get many. prefetch and here's an important thing when nextjs or versel tries to build this part of our app it will think that that this is a static application because it has no idea that this is actually prefetching something so what you have to do and don't worry you're only going to have to do this in page. DSX and we're going to keep track of every single page. DSX in our app which does prefetching we're going
to be careful uh what you have to do is expert cons Dynamic to be Force Dynamic like this otherwise you're going to have build errors if you don't do that in places where you pre batch and now inside of here what we are going to do is we are very simply going to pass the home view now we don't have the home view yet but we are going to create it and I'm also going to kind of explain to you uh the places where I prefetch and the places where I call the use suspense query
because I will make some decisions about where I will do that and where I will absolutely not do that and also to answer you know where did the suspense and the error boundary go we are also going to take care of that but let's finish doing this how about we don't call this Home Instead we just call it a page I prefer using Arrow functions export default don't forget the default export when it comes to Pages let's create an interface page props here and we're using search bars here which means we have a promise like
this and an optional category ID so this is not required uh to exist only if the user selected the category ID will it exists and let's go ahead and D structure that here page props uh search params so not params but search params like this and then inside of here you can destructure the category ID from await search params and in order to use await we also have to ensure that this component is asynchronous and then we can go ahead and pass in the category ID there we go so we are leveraging the server component
to to await our search prams and we are also leveraging it to fetch all the categories to prefetch the categories and now we're going to create a new module here actually we already have it called home uh we have the home layout and we have the components here so now what we're going to add is another type of UI components called views and inside of the view we are going to go ahead and we are going to create a home view. DSX the home view component will have an interface home view props which will accept
the category ID which will be already destructured here from the promise so no need to do that let's export con home view here passing the category ID from home view props and inside of here what we are going to do is we are going to return a div and we're going to give this div a class name of Maximum width of 2,400 pixels I'm going to explain why we are doing this in a moment let's add MX AO margin bottom of 10 PX of four padding top of 2.5 Flex Flex column and GAP y 6
like this basically what we are doing is the following I think if I add border and background red 500 it might be a bit more clearer so let me just go ahead and do this and let me go uh use the home view inside of my actual app folder home page so I'm going to import the home view from modules uh views home view I will refresh here and there we go you can see uh my uh home view now but you can see that at one point it will stop expanding its width right so
if someone is looking at this at an ultra wide screen we are not going to keep expanding the videos infinitely right we're just going to limit to how wide we are going to support our category carousal and our video grid so that's what this does it limits to how wide we are going to support that so we can now remove the background color red and the Border we don't need any of those and now instead of the home view we are still not fetching anything instead what we're going to do is we're going to render
the categories set ction and passing the category ID to be category ID like this so the last type of component for me to introduce to you are the sections like this and inside of here simply create category section. DSX and the section will finally be an actual client component I will just copy the interface from home view props because it's exactly the same and I will simply rename it to category section props let's export con category section my apologies not category section categories multiple I want to have proper names like this categories and inside of
here what we are going to do uh is we are going to fetch our categories like this so categories erpc let's import from client make sure you're imported from client here but in page you have to import from server right so in the client here we are going to access the cache which we have uh my apologies get many get suspense uh use suspense query we are now immediately going to access the cache which we have thanks to this prefetch in our server component here so now that we have that let's go ahead and return
a div Json stringify categories so the same thing we previously did but this time in a structure in a structure that we are going to follow throughout our project so let's go ahead and add this and the structure the category ID uh all right and let's go ahead and import the categories section from dot do sections category section great so now if you go ahead and refresh nothing much should change everything is pretty much still the same but this is my general UI structure so inside of my app router I'm not going to do any
UI elements as you can see I will immediately import the home layout from the modulus home for the page I will always import a single home view but I will hydrate the client here the reason I'm choosing to hydrate the client here and not inside of the home view is because I don't want to forget it right I don't want to accidentally forget that I have to hydrate this client so wherever I am doing a prefet is where I'm going to add hydrate client it's this kind of consistency that gives me a peace of mind
that I didn't forget to do any of those you know if I prefetch I have to hydrate and I also have to use the proper use suspense query this is giving me a piece of mind that I will always do the same thing every single time great so that's what my pages are going to be for my server comp components will be used to prefetch things to hydrate the client and to render its respective home view now the home view component itself will be purely decorational all it's going to do it's going to split the
screen into sections and then the section will finally be a client component which is going to Leverage The prefetching from my server component that's how that is going to work the reason I am separating them into sections here is because each of my sections will have their own suspense and error uh boundaries which means that I'm going to have categories sections here but below I will have videos section so both of these will be able to load independently of one another and if one crashes it's going to have a nice encapsulation as opposed to my
entire view crashing right that's what I'm trying to achieve here I'm trying to find the balance between two granular of uh of of suspending and also you know to grandio of suspending as for example suspending the entire home view right I'm rather going to go a bit deeper into sections uh I think this gives a quite a perfect balance between not too granular but not too Grand either all right so we now have the categories here but something that we don't have at the moment uh is any loading screens so you can choose how you
want to do this so you might think okay so I have to wrap this inside of you know suspense right because my categories section is using use suspense query we've learned that we have to wrap those inside but I'm actually not going to do that now I'm not sure how good of a practice this is but I really like it this way this is what I'm going to do instead I'm not going to export this and I'm going to rename this to category section suspense and then in here I'm going to export con categories section
like this I'm going to explain why I chose this it's pretty much the same reason why I chose uh to use hydrate client in the place where I'm doing prefetch I'm choosing to have inside of the same component I'm going to have the suspense in the arror boundaries so I never forget that I have to wrap whatever whenever I'm using used suspense query I never want to forget about wrapping them in these two so that's why I simply decided okay okay every single place where I use use suspense something I'm going to do the actual
suspense and error wrapping inside of the very same component and that way I will never forget about it right so that's my solution of doing it if you have any better practices feel feel to feel free to introduce them uh but keep in mind that I will be using uh this throughout the entire tutorial react error boundary like this and let's go ahead and add some fullbacks here so add a paragraph loading like this and the fullback here error like this and then simply render the categories section suspense but we also have to pass in
the the props so let's just copy this yeah this is the one annoying thing we have to do we're going to have to do double uh prop passing here yes but other than that it's pretty much okay all right so now we have the categories section here and nothing has changed here because what we're importing here is this the suspense with the error boundary that's why I'm only exporting this and I have renamed this one to be category section suspense so now you can see that I have a proper loading suspense happening here and if
for for whatever reason inside of my categories router here I were to you know throw new trpc error here with code bad request I would get the the respective error boundaries so it's loading it's still loading so only after a couple of retries will it fall back to my error boundary there we go error boundary activated uh great so I'm now going to remove this error here and I'm going to go ahead and what we're going to do now is finally develop this nice UI right here so let's go ahead and do that the component
that will be responsible for this will be called a filter carousal and it's not necessarily going to be used just for categories it can be used for something else for that reason I will not put it inside of any module I will just put it inside of my components but I'm not going to put it inside of the UI folder purely for the reason that shaten uses this folder so I will reserve the UI folder for whatever shaten command line interface decides to put inside uh but I will use the components folder directly like this
outside of the UI and I will call this filter DC carousal dots X so as you can see filter rowal is inside of components but outside of my UI folder this will be a client component and let's go ahead and do the following let's first of all uh create an interface here I'm just going to copy it so you don't have to see me typing this thing out so go ahead and pause the screen the value can be optional string or null is loading can be optional on select will choose a specific value and the
data has to be in this kind of format value or label like this and the data actually has to be required right actually uh yeah let's make the data always be required because we do allow an empty array so that makes sense great let's export con filter carousal here we now have to assign the props like that and inside of here uh we can now the structure value on select data and is loading like that now what we have have to do is we have to use uh the carousal elements from shat CN UI so
let's add all the Imports necessary for that so that's the carousal carousal API carousal content carousal item carousal next and carousal previews all coming from components UI carousal double check that you have that component great so now that you have this we can go ahead and just build a basic look of this so let's go ahead and do the for following I'm going to go ahead and return a div with a class name relative and the full width and now what I'm going to do is I'm going to add the carousal like this and I'm
going to go ahead and give this an options of align start and I'm going to give it drag free to be true I will also give this a class name of BU width and X well like this I'm then going to add carousal content here and I'm going to give this a class name of minus ml minus 3 if you're wondering why minus ml minus 3 uh that is because this is how you add certain padding between the items it's a trick from the shaty nuui documentation if you want to you can have it opened
uh but yeah the shaten in in the background for the carousal I believe uses uh embl carousal react and basically that's how you add padding between items in each carousal here right so what we are going to do is the following how about we just add one carousal item here which will simply use our badge component from slui badge or SL components UI badge however you want to import it because we are in the components folder so we are much closer to the UI older than usual so whatever whichever one you prefer and we are
simply going to write all inside like this and I'm going to pause here so that I can go back inside of my source modules here inside of Home sections categories section and inside of here all I all I'm going to do is I'm just going to return filter carousal from components filter carousal and I'm going to pass in the value to be category I ID and the data to be data my apologies uh yeah we can call it data you can call it categories however you prefer let's call it categories uh yeah the issue is
that we actually have to uh give it a specific type because data expects a a value and a label right so let's go over our categories get the individual is it name like this and ID and return an immediate object with value to be category actually just ID and label to be name like this and now you can pass in data there we go or you could have also you know passed this as category and then you could have used category do ID and do name whichever one you prefer Let me refresh now and there
we go you can see uh a small all badge right here so I just wanted us to render this component so that when we develop it we can see the changes so obviously it doesn't really look uh perfect yet but we are going to modify it let's go ahead and let's give this badge which renders the all text a dynamic variant if the value is currently null we're going to use default otherwise we're going to use secondary and let's also give it a class name of rounded large PX of three py of one cursor of
pointer white space no wrap and text small so we are going to leverage the fact that if no value has been passed and we are just going to assume that that means that the badge all is selected right uh great so now as you can see this is what that looks like so now let's go ahead and do the following let's go ahead and while we are inside of the carousal content I want to check if we are not loading and if we have data actually if we are not loading in that case just iterate
over the data like this and render the carousal item and inside of here render the badge let's go ahead and give each carousal item a key of item. value and inside of each badge item. label so now if I refresh looks like I'm still not getting anything here let's see instead of my categories section let me check what is my data here am I getting all the data necessary here it looks like I am this seems to be my data so we definitely have this but for some reason it seems to not be loading so
is loading all right so if not is loading and and data. map uh am I using something wrong here if I just go like this will that work uh no it still acts as if my data does not exist so maybe it's not a problem in it's not yeah the data is available here as well so perhaps I'm using the UI incorrectly inside of my Cor Al content we are rendering carousal items here and the badge here let's see could it be that I'm simply missing the classes so class name pl3 basis Auto is it
that it's not that H very interesting okay so pl3 basis Auto uh carousal item badge item label all of these things should work but for some reason uh they don't appear to be working all right so I was debugging a bit using inspect element and I did find the rest of our items they're right here they're pushed all the way uh to this other side it seems so let's go ahead uh we are obviously doing something uh incorrectly here uh I think that for this Cor browser item I'm missing the same class name here pl3
basis Alco there we go so I was missing that class name which accidentally seemed to have pushed all the elements in this corner here uh great so we can now see all of our categories and now let's go ahead and give some additional Styles here so the badge will do the same thing it will have a dynamic variant if value is equal to item. Value we're going to use default otherwise we're going to use second like this and let's give the badge itself a class name rounded LG basically the exact same thing as above so
let's just use this there we go there we go now these are looking much better and you can see how we can drag them but there are a couple of things I don't like the first thing is I'd like to have some arrows the second thing is I don't like the cut off here so let's go ahead and resolve that as well we can do that quite easily by going outside of the carousal content and simply add uh carousal previous and carousal next so simply add them give this one a class name so for the
previous give it a class name of left zero and Z index of 20 and this one write zero and Z index of 20 and there we go you now have nice little arrows that you can use as well one thing that's missing are the cut offs so I really don't like how the cut off is looking like so I found a very simple solution thanks to AI uh which is just to create like some Fades so let me remove the conso log here inside of relative full I'm just going to add a comment left fade
so you know what this is for and this will be a self closing div and all it's going to do is have a class name but we're going to have we're going to have it have Dynamic classes so for that we're going to utilize our lib utils which we got when we installed shat CN UI I don't think we've used it uh at any point so far but what this is good at is making Dynamic Tailwind classes yes you could just use you know normal JavaScript here but trust me this makes it way easier especially
when you're about to have a bunch of classes a bunch of options a bunch of variants right you will eventually give in and start using this and most importantly it will uh properly merge dewind classes so you might think you're doing something uh in a simple Javas script way and then your Tailwind classes are not going to behave as you expect so it's always safer to use this in my opinion when you need Dynamic Tailwind classes the way it works is very simply it accepts an infinite amount of params and you can write them either
statically like this absolute left 12 top zero bottom zero width of 12 Z index of 10 background gradient to right from white to transparent point enter events none and then add a comma and again you can continue background red blah blah blah or you can make Dynamic one and well we can't we don't really have basically I want to make something here so this is what I'm going to do if false hidden this false will be a variable in a moment we don't have it yet but now you can see how we have a nice
fade here but there is an issue is that I don't want this to be hidden already right I want it to be active in this case but I don't want it to be active in the beginning because it's hiding my first element here but basically this is what we want to do we have uh F uh left fade here let's go to the end of our div here and let's add right fade and instead of left 12 this will be uh right 12 like this and this will be uh gradient to L like this and
this will also be false and now you're going to have a nice little fade at the both of this things here so what we have to do now is we have to uh enable um well let's go ahead and call this proper API for the carousal right because when something is clicked I want that to be sent to my um well to the outer component right right and from shatan documentation what you can do is you can enable carousal API and expose it and then you're going to have you're going to have the ability to
programmatically control this carousal which is quite useful so let's go ahead and import use state from react here and let's pass in uh a type of carousal API do we already have it imported we do great so carousal API here the current value will be zero as in the starting position and we're going to have the count of total items let's add use effect I'm not a big fan of using use effect ever but sometimes you just have to so make sure you've imported use effect from react here if we did not load the API
for the carousal there's nothing we can do so we can just break this method otherwise let's call set count API do scroll snap list. length execute it and then call the length and let's do Set current to be API selected scroll snap plus one API on select Set current API selected scroll snap plus one so now we have total control over how many items we have and what is the current position and then we can properly hide this fade if we are on the first element or the last element right so yeah it's a bit
of an effort but it makes the project look much better right it's the details that matter let's go inside of the carousal here and let's add set API to set API and I think that's all we have to do for this and then instead of false we can check if current is equal to one in that case hide this so now you can see no fade in the beginning but but when I move it starts to fade so it doesn't have the ugly cut off perfect and for the last one instead of a hardcoded false
here we're going to do if current is equal to count right so if we reach the end of our line there we go no fade here but if we are not it's going to show like there are more things here great so we have our basic carousal here so what I'm wondering now is this this shouldn't be if value is equal to null this should just be if value does not exist and then all will be selected by default that's why it wasn't selected because it wasn't explicitly null all right so we have that and
we can now you know uh send our data to this filter carousal but what's missing is a nice loading state so how about we leverage this filter carousal and give it a prop which we already have is loading and let's go ahead and you know kind of make this a nicer experience so we are only going to render the data if we are not loading so if exclamation point is loading only then render the data otherwise we're going to go ahead and do the following if is loading in that case you're going to render an
array actually so yeah don't return uh any parenthesis just collapse the element like this we're going to create an array from 14 elements because that's how much we have in the database currently so skip the first argument and just get the index and then we're going to mock the carousal items here so give this a key of index give it a class name of pl3 and basis AO and inside of here we're going to use the skeleton from slui skeleton or from components UI skeleton whichever one you prefer this also came in our shatan add
all CLI command we're going to give this skeleton a key actually does not need a key my apologies we're going to give it a class name of rounded large PX3 pa1 height full text small width of 100 pixels and font semi bold and inside we are simply going to add a Unicode for a white space so it populates the font but so it puts the skeleton in the right you know uh height and width and everything uh but it will not actually show anything so it's still going to appear as a skeleton and if we
are loading we also don't want to show this default all badge so only if we are not loading are we going to show this all right like this there we go uh and let's also now go inside of category section and let's add his loading value here and this is how our skeleton is looking right pretty good right but don't worry we are just adding it here now we will add it to the suspense but before we do that let's actually go ahead uh inside and how about yeah I mean I think that we should
enable we should make the value required as well no no my apologies the value does not need to be does not need to ex exist always uh but on select should probably always exist what's the point of the carousal if you can select an item right the value can be reset if we click on the first element here which is the all so let's do that onclick on select null like this so we are basically resetting the entire thing otherwise if you click on uh this proper one in that case uh what we're going to
do is we're going to call on select question mark because it can be actually no it has to exist right so we can just execute it item. value let me just collapse this items here some of you have asked me why don't I just use prer for this I use prer when I develop the project but I don't use it when I record the project because preter can sometimes change the stuff that's not recorded like I run a format now and it changes some code here and then you're wondering why our code looks different so
that's why I don't like using preder while I'm recording a tutorial but yeah feel free to use preder I I I do use preder I do use formats and AOS save and all of those things but not when I'm recording a tutorial all right so just make sure you have added on the real carousal iteration on click select item value and also yeah no need for this because on select will always exist uh great so now on select is missing so how about we add on select here X on the log X like this let's
try out when I click here there we go you can see each of my elements and in order to wrap up this chapter we have to actually assign this category to our URL to the search bars so that it can be properly passed back here and show that it is selected before we do that let's actually add a proper suspense here so what we are going to do is we're just going to use the filter carousal here and we're going to give it is loading prop like this uh oh so it's missing on select okay
yeah let's give it data an empty array and unselect an empty Arrow function not perfect I know but I think it gets the job done and now we have a much much smoother uh loading experience here right so you can see how uh this was my decision basically whenever I'm using uh suspense suspense query just a component above it I'm going to handle the suspense and the error boundaries so I can decide how they're going to look like and not so that I have to go you know back to my uh view here and then
see oh okay so I have to change the suspense I have to change the error boundary to some other component no I'm just going to have it all controlled inside of here one step further for example could be categories skeleton simply to be explicit right I mean I know this is funny now this doesn't make any sense but you know if you want to be explicit have category skeleton you can do it like this it's just passing the categories skeleton it's the exact same thing don't export it no need for it to be exported but
yeah I mean it gives you a bit of a larger control if in the future you want to add some more structure around the loading element I think this looks very good let's wrap it up by actually adding the on select option here so I'm going to add const on select value string null like this I'm going to parse the current URL using window location href if we have the value I will do URL search forams do set category ID to the value otherwise I will delete them from the search forams make sure you don't
don't add any typos for the category ID here and then I'm just going to go ahead and add my router from next navigation and I will do router. push URL like this and let's see URL to string and let's go ahead and pass in the on s here so now what should happen when I select here you can see that my URL has Chang to category ID and include the URL that I have clicked on now there is a way to make this even faster because router. push is not exactly the fastest way of doing
things because it will not prefetch anything so if you want to you you can use router. prefetch as well uh but perhaps this is something I will explore in maybe some other chapter I will explore if maybe this filter carousal uh can wrap the badge element inside of a link component because we can add the query elements the search params to the link component as well the link component will be much faster because it will prefetch by default that's something to see maybe maybe I will come back and do that but at that point we're
almost certainly going to have to remove the filter Cowal from being a reusable component and use it exclusively for categories so I'm going to confirm with my project with my completed project if that's the case then yes I think we have room to make that optimization to make this even faster right now you probably have no much opinion if this is fast or not but keep in mind that later on every time you switch this it's going to query the database for specific category and their videos so then you might have start to fill this
a little bit slower even with our prefetching and everything uh I'm going to see yeah basically this should all work for you you should be able uh to change this URL and it should reflect the carousal here in case it's not here's how you should debug this you should start from Source page right here where you have the category ID make sure you didn't accidentally misspell the category ID make sure you didn't misspell it when passing to the home home view make sure you did not misspell it here and make sure you did not misspell
anywhere here so those are the only ways it can happen right uh great so I think that makes us properly prefetching the categories and we created the categories component we can end this chapter great great job in this chapter our goal is to create a studio layout we're going to start by creating a studio router group then the actual layout and finally we're going to protect the studio routes this will be quite similar to our initial basic layout if you remember it from chapter 2 so yeah this is what we're going to implement and luckily
for us we can copy a lot of things from there so just to confirm this is the layout uh we are going to be building right so just this part right here and the other part is going to be the page well we'll come to this later when we actually have some content that we can render now but for now we're just going to use this as you can see we will have uh this special component right here which is something uh different from the basic layout uh and this one will be maybe interesting because
we are going to have to implement users uh procedure which is something that we did not have up until now uh and it will also to be an example of us using trpc use Query without any prefetching or suspense and uh I will explain why well I can already tell you the prefetching doesn't really work inside of layout files so it's best to use it inside of uh page files and just not do it inside of layout files and it will also be our first proper protected procedure you're going to see all about that we
can even try and prefetch so we can see if we get an error all right let's go ahead and do that let's start by creating the studio route group so we're going to go ahead inside of source app folder and we're going to create inside of parenthesis Studio route group inside of this route group we are also going to have an actual route named studio and now I'm just going to add a page. TSX inside and I will simply return Studio back and since we added this route inside of its own route group it will
not have this layout so if you go to localhost 3000 SL Studio you will just get a text which says Studio without any of the layout that we have because it's not a part of the Home Route group before we go on let's go back to our Local Host 3000 and how about we add a nice Studio button here in this user dropdown this is something that I briefly mentioned I believe but didn't really uh develop completely so let's go inside of source modules Al components out button and there we go we have ADD menu
items for studio and user profile what we can do here from clerk nextjs actually I don't think we have to import anything I think we can just open up the user button instead of having it be a self-closing component and inside we can do user button. menu items and then we can do user button. link and we can simply uh this is actually a self-closing tag like this and we can give the user button. link a label of my profile an hre of uh my apologies not my profile this will exist but later an hre
of Slash studio and a label icon of clapperboard icon from Lucid react and let's give The Clapper board icon a class name of size uh four like this so this is supposed to be menu items like this so now if I click here there we go you can see that I have a studio redirect in case you forget to put use client at the top here I think that this will lead to errors where it says that user button. link uh is an invalid export so in case you got that it's because of this you
need to add use client to the top and then you will be able to use links here if for whatever reason you are struggling to do this well don't worry you know you can simply add a button outside of here add a clapperboard icon like this studio and just add you know a link from next link to lead to studio so I will just give you an example just in case you're not able to add any menu items here you know there we go it's it's a button and give it an as child prop there
we go and if you click here you can now go to the studio and it will only appear to logged in users so whichever one you prefer you can also give it a variant of secondary to make it look a little bit better there we go so a nice Studio button here if you prefer that you can do that uh otherwise you can just put it inside of the user button menu menu items and also you can rearrange these things so if you want to put manage account to the bottom you can do that but
then you have to uh explicitly say that so you have to add user button do uh action and Target it by label which is manage account make sure you don't add a typo here and then this will recognize that this comes from here and there we go now manage account is below the studio just in case you want to change that here I will remove this to do here and I will add add one here to do add user profile menu button there we go so now we can use our clerk user button to go
to the studio and now let's go ahead and let's develop the actual UI so let's prepare by going inside of our modules and let's create our studio module here let's go ahead and create UI and let's go ahead and create layouts and what we can do is we can copy uh we can actually copy the entire home layout here so how about we add it here home layout and what I'm going to do is I'm going to rename it to Studio layout like this we can ignore these two errors for now let's just go ahead
and rename all instances of Home layout to Studio layout here like this there we go so now let's go ahead and let's copy well I think we can copy all components from UI here and just put it here instead of uh UI and I think that now if I reload this window everything should be okay if I go inside of components I'm not sure do they depend on something here no looks like they're pretty self-isolated no errors in each of the components oh great so we just easily copied the entire layout and then we're just
modified to work for our studio case so let's go ahead inside of our app folder studio and we're going to create layout. TSX for the route group not for the route but for the route group right and we can copy the content from here because they're going to be pretty similar instead of importing the home layout we're going to import from modules studio studio layout which in turn will be Studio layout like this so now if you refresh you can see that I am on studio and everything is exactly the same except it's not supposed
to be the same so let's go ahead and start by how would we first modify the Novar I think that's a bit simpler uh to modify so let's go inside of the studio layout and let's go ahead and let's rename uh the home Nob bar here to be Studio Novar and if you're being asked to update the Imports you can press yes and that will update this import to studio nvar so for now we just change the name of the folder so let's go inside of this new studio Navar instead of index. TSX and let's
replace this to be Studio navbar as well now we have an error inside of our layouts Studio layout we have to replace the name of home navbar to Studio navbar there we go so now this is a studio Navar but nothing's really changed let's go inside of this studio Navar here and let's modify it so this image logo can stay but this will change to studio so now when we are on Studio this here says Studio like that and you can decide what you want to happen once you click on that I think it's actually
better if it stays on studio so when we are in the home right if I go to cars and vehicles and click here I want it to reset to its homepage but if I go to studio and if I click on Studio I want to stay on the studio because we will have some sub routes in the studio as well so I think it makes sense to change this as well and we are actually not going to have to have any uh uh we're not going to have to have any search bars so we can
remove that but we do now have a little issue and that is that we need spacer here so how about we just add a div with a class name FX one simply for this to be pushed at the end there are of course many ways you can do this but this is one of them uh okay so we got rid of that part which we don't need and now let's go ahead and focus on this you can see that the studio has a border and it seems to have some Shadow here the same will be
true for the uh sidebar so that decision came from the original YouTube and YouTube studio uh they have for some reason you know different uh different layouts so I will follow that as well and I think it's nice because it's pretty clear that you're in a different application or at least you know you're supposed to feel like being in a different application so I agree with that that while we are here let's remove the search input import and we can remove it from here make sure you don't accidentally remove the one from your home right
you need it here right make sure you don't accidentally delete this one we are working inside of a studio all right so yeah technically this no longer has to be a folder but let's leave it like this for now and let's go ahead and modify this a bit by adding at the end of this studio Navar border bottom and Shadow medium like this there we go we can already see the difference clear as day now let's go ahead and let's close the studio navbar for a moment and let's replace I mean rename our home sidebar
to be Studio sidebar and click yes to update the Imports and you can go back inside of your studio layout and you will see that now it has changed this but this is still called home sidebar so how about we change that by going inside of Studio sidebar here and renaming this to Studio sidebar and now we have an error here because we have to replace both instances of Home sidebar with Studio sidebar nothing much to change yet but you should get uh you should get well uh different um nothing nothing should change sorry all
right let's go inside of the studio sidebar here and how about we start uh by giving giving uh some specific um I want to basically give it this borders here I'm just not sure uh what is the correct place to do that I think we can just remove border none here and there we go now it has a nice border as you can see right here great and now we're going to have to modify the content here and the content will actually be much much simpler uh than in my than in my home sidebar so
I'm thinking do we even need this kind of Separation here I think it's easier if we just you know um yeah but okay let's go ahead and still call this main section but let's go ahead and H all right let's see how can we do that instead of having personal section we're just going to do this we're going to add a sidebar menu item from components UI sidebar so make sure you add this import then we're going to have sidebar menu button from components UI sidebar this will be a tool tip exit studio and it
will have an as child prop then we're going to add a link from next link here and the HRA will lead back to the root page and inside of here we're going to add a logout icon from Lucid react so make sure you have added this let's give the logout icon a class name of size five and let's give this a span of exit studio and a class name of text small like this there we go so now we have exit Studio but something seems up here uh I might have forgotten something I think that
I need to wrap my sidebar menu item uh Inside Yes it needs to be inside of sidebar menu in the first place I think yes yes yes let's do it like this let's make sure to import the sidebar menu there we go and let's also add sidebar menu group is it called group or is it just sidebar group it is and I think that sidebar group should actually encapsulate the sidebar menu there we go so I think that now everything should be aligned nicely there we go so now we have the exit Studio here and
what we're going to do is very simply we're going to copy uh we're going to copy let's see um I'm just trying to think of the best way to do this all right we're just going to copy the sidebar menu item here so make sure you have two of those uh and this one very simply is just going to lead uh to slash Studio slash videos like this and this will be uh an icon video icon from Lucid react and this will simply say content actually it can go to SL studio right this basically this
page right here and we can uh use path name here use path name and we can mark this one as active so let's see is it um it should be the sidebar menu button is active if path name is equal to SL Studio like this and we also need to mark this as use client in that case there we go so now it will say content here because we are on the studio let's go ahead and let's remove the main section entirely let's remove the separator and we can now fix the indentation here remove main
section and personal section and you can remove the separator for now because this entire thing is much simpler no need for us to uh separate that inside of its own components as we had to with the home sidebar which clearly has different sections but inside of here we will just have content and exit Studio which should just lead you back to the root page and if you click Studio here you should go back to the studio great so that seems to be working just fine uh let's see what do I want to do now so
I do want the sidebar but I want it to be uh the separator but I want it to be here in between sidebar menu items so just add back the side the components UI separator there we go so we now have that how about we go ahead and now create the header which will load the user let's go ahead and let's prepare uh that component so we are going to put it inside of our sidebar menu here so it's going to be called Studio sidebar header and it will accept no props let's go inside of
the studio sidebar and we are going to have to remove these two but for now let's just create the studio sidebar header. DSX like this and and let's export const Studio sidebar header and return a div header now let's go back inside of the index and let's import the studio sidebar header from do/ Studio sidebar header and you should see the header text here now let's go ahead and let's remove the unnecessary main section and personal section from the studio sidebar again make sure you're working within the studio module don't accidentally remove your home module
you can always confirm by going back to ensure that you have everything here great now inside of here let's go ahead and continue developing the header I think that in the beginning of this chapter I told you that we're going to have to create the users router well that's actually not true we don't have to we can but we don't have to we don't have to rely on the database record because we can use the entire clerk object here and clerk will actually have the most upto-date data right because every data change that happens in
clerk will have to get resynchronized to our database so we can actually safely just use this user from use user from clerk nextjs that's it so this is what we are going to do we're going to go ahead and change this to the sidebar header from components UI sidebar and we are going to give this a class name of flex items Center Center justify Center and padding bottom of four and then we're going to add a link component from next link with an hre of Slash users slash current and then we're going to have to
add a component called user Avatar and I think we have it user Avatar uh let's see uh user Avatar we don't have it is it called Avatar maybe uh it is it's called comp components Avatar okay so this is what we're going to do we're going to go ahead and create this reusable component instead of source components we're going to create user- avatar. DSX like this and we are going to impro the avatar from /u Avatar or if you prefer components UI Avatar because Avatar exists but I want to create a reusable component for the
user Avatar so we can have proper tool clips and Al image and everything else let's also import uh CN from leave utils let's also import Avatar image here and let's also import CVA from class variance Authority and type variant props if you're wondering what this is well if you take a peek at components UI and then pick anyone for example button this is what it is it's basically a util which allows us to create different variants and it's going to be useful for us so we can create different sizes uh I think it's just more
manageable to work that way than using a bunch of Turner operations so for example we can do const Avatar variance CW CVA and in the first argument you just give it some default classes which I think we don't need we don't need anything to be here by default because every class name is going to be different depending on the variant so add variants and a variant size by default we're going to have a height n and width n extra small is going to be height four and withth four small will be height six and withth
six LG will be height 10 and with 10 and then we're going to have extra large which will be height 160 pixels and width 160 pixels and then let's add default variance size and choose the default version and that's it we now have a nice way to control all the variants for our Avatar now let's create an interface user Avatar props extends variant props type of Avatar variants passing the Avatar URL it can also be image URL if that's what you prefer let's call it image URL it will be a required string a name will
also be a string class name which will be an optional string in case we want to modify something and on click will be uh an optional event as well let's export con user Avatar here like this let's assign user Avatar props let's get the image URL name size class name and on click and you can see how we have access to size even though we did not add it here that's because we extended the variant props so in it inferred all the possible size types great so we have that and in what we're going to
return is our Avatar component our Avatar image which will accept source to be Avatar sorry image URL and an ALT of name and a class name here will be CN aatar variance and we're going to pass in size and class name and an on click there we go reusable and modifiable user a great now that we have that we can use the user Avatar so user Avatar component here from our uh component uh user a like this let's go ahead and pass it some BRS so the image URL will be user image URL like this
then we're going to have name which should be user full name or user in case it's null and we are also going to have a class name here where we are going to add a very specific size I know this kind of breaks what I just built there I built all those variants and I ended up not even using a single one of them but I prepare that for all the future use cases of user Avatar right and we are adding some transition uh when we hover here because this will have a it will not
have an on click it will have a link around it great so now what we have to do is we have to do this if there is no user return now we are going to add a proper skeleton in a moment but for now let it work like this and there we go we now have a nice little when you click here it leads to 404 because we didn't Implement users current yet but as you can see it's already starting to look like what we imagined here so let's go ahead and go outside of this
link here and add a div here with two paragraphs like this in the first paragraph we're going to say your profile and in the second paragraph we're going to say user. full name like this your profile and then my name here let's go ahead and give this a class name of flex Flex column items Center and margin top of two let's give the first paragraph a small text and font medium and let's give the second paragraph a text extra small and text muted foreground there we go this is looking much better now let's go ahead
and let's actually uh create a skeleton for this so we're going to go ahead and return sidebar header component let's copy the class name here so we don't have to write it again and inside we're going to render a skeleton from components UI skeleton like this it's going to be a self closing tag the first skeleton will represent our Avatar so let's give it a size of 112 pixels and let's make it rounded below that let's go ahead and copy this div class name here and we're going to pretend to have two paragraphs which are
going to be two skeletons here the first one will have a class name of height four and a width of 80 pixels and the second one will have a height four and a width of 100 100 pixels just to have a little variance between so now when you refresh okay it's looking good but we are missing something we are missing Gap y1 here and let's also explicitly add Gap y1 here as well there we go this is looking much better uh great so now uh let's go ahead and just I would like to wrap this
to make it a proper if Clause I I don't like how it looks uh one issue that we have I believe is that if I Collapse this it looks horrible right so let's just fix that as well in order to fix that we can leverage our use sidebar from components UI sidebar uh you can only use this if you are inside of a sidebar so you have to render something inside of the sidebar like we do right here and then you can use this hook inside so you can access the state and then you can
check if state is collapsed in that case you can return a different sidebar menu item here we have to import this so let me just close it here but I also added an import for sidebar menu item let's also add an import for sidebar menu button and now let's go ahead and continue developing here so we're going to have a sidebar menu button with a tool tip your channel and as child and let's actually call it your profile because I will not be using the channel name everywhere anywhere anywhere this will be a link component
to users current and we're going to have a user Avatar here again with image URL whoops this is self closing tag image URL will be user image URL and name will be user full name or user in case full name doesn't exist and size will be extra small and then we're going to have a span here your profile and a class name text small like this so we are basically simulating uh as if we had you know this collapsed State here there we go so we can now uh handle both of these cases and we
also have a nice little loading here I kind of don't like this jump here I like to obsess over those details let's see can I can I do something about that what if I added a gap 1.5 here or maybe two whoa there we go this looks much better if you ask me no visible jump now uh great so we just had this layout finished I think the only thing that's missing here is the create button so let's just go ahead and prepare it because it will be a little bit different uh it will be
called Studio upload model so we can prepare it right let's do that let's go inside uh of components here and outside of the nav bar we're going to create Studio upload model. TSX like this and we will export const Studio upload model but we are not going to develop the model yet we are just going to add uh the button here so let's add a button from components UI button and let's add a plus icon from Lucid react and create let's give this a variant of secondary like this this and I think that's it nothing
else needed here but obviously we are going to continue uh developing here let's mark this as use client so now let's go ahead and go inside of our studio studio Navar index here and let's go here where we render our out button and let's render Studio upload model like this which we imported from do/ Studio upload model and we should now have the create button right here uh there we go I think that is exactly as it was here perfect uh and I think that is it yes in the next chapter we're going to go
ahead and actually develop the functionality and you know before we can develop the functionality we have to create the videos schema we have to push those changes to our database uh and then we can go ahead and create a new video uh when we hit the create button uh and then we're going to have to connect to Max which will be our video service which is absolutely amazing you're going to love it especially with all the things that we are going to be able to do uh we are going to give you a real you
know YouTube experience our videos will be processing for some time you know because they need to transcript and transcribe and all of those things we will have subtitles we will even have a bunch of AI features you're going to see it's going to be very very cool but yeah you should now have a studio layout here which looks fairly different than the one from our home great great job let's just see did we do everything here I'm just going to enable my pen here we created a studio route group and layout but we forgot to
protect the studio routes so let's go ahead and do that before I wrap up so instead of source instead of middleware dots instead of aiming for SL protected we're going to aim for slash studio so now if I happen to sign out let me just refresh and if I manually go to SL studio now I should get redirected right here sign in and if I actually sign in I am redirected back to the studio because that's the last place I tried to access there we go we just finished that as well great great job in
this chapter we're going to focus on creating Studio videos by that I mean we're going to create the basic video schema for our database after that we're going to push those changes to Neon database then we're going to create Studio procedures so we can finally add video record video record uh record sorry creation right so we're going to enable this little button which we created right here to actually create that record in our database and then we're just going to Json stringify all the studio videos that we can load inside let's start by going inside
of our schema right here so inside of source inside of database inside of schema. DS and inside of here what we have to create is the uh videos so let's go ahead and make sure that we have users defined we have categories defined and now let's go ahead and let's export con videos so videos PG table let's copy the ID which is going to be uu ID let's give each video a title with which will be a required text so not now a description which will be an optional text then we're going to prepare um
let's go ahead and how about we do this let's copy this two created at and updated ad so these are the ones which we almost always need and now what we have to do alongside title and the script is we basically have to connect each video to a certain category and a certain user but the category can be optional while user is required so let's handle the user first this is how we're going to do that we're going to add user ID and we're going to add uu ID and we're going to call it user
uncore ID and then we're going to add a foreign key reference so references users do ID we are basically targeting the schema above right users. ID that's why we knew that this has to be a type of uu ID let's also Mark this as not null so it's clear that this is required and since this is a foreign key we can also give it specific instructions on how to behave on certain actions for example on delete we want this video to Cascade so in case user ID has been deleted for any reason whatsoever all of
their videos are going to be deleted as well but we are not done with defining this relation what we have to do now is we have to import relations from our drizzle orm so let's go ahead and do that import relations from drizzle orm and then we have to go ahead uh and do the following let's go ahead and go below our videos and let's go ahead and let's create export const video relations let's use relations passing the videos inside and then in the second argument you can destructure one or many depending if you need
one to one one to many or many right all of those combinations and now we are going to type in the user to be one users targeting the fields videos. user ID and references users. ID like this and we don't need many like this now before we push this you probably have some questions if we added this why do we need this right well thankfully drizzle orm has very good documentation on this so if you go to the documentation and go into relations here they will explain to you what they are so the sole purpose
of drizzle relations is to let you query your relational data in the most simple and concise way be that using uh their relational queries like this or what we are going to do we're going to use SQL like query Builders like using joins right now inste of here uh they go over all of their one to one basically all of these things which we just did like one to one all of those other things but if you click on foreign Keys here you get the answer to what we just asked you might have noticed that
relations look similar to foreign keys they even have a references property so what's the difference well foreign Keys serve a similar purpose defining relations between tables they work on a different level compared to relations foreign keys are a database level constraint they are checked in every insert update and delete operation and throw an error if a constraint is violated on the other hand relations are a higher level abstraction and they are used to define relations between tables on the application Level only they do not affect the database schema in any ways and they do not
create foreign Keys implicitly basically what that means is that relations and foreign keys can be used together but they are not dependent on each other we can Define relations without using foreign keys and vice versa this allows drizzle to be used with databases that do not support foreign keys so I hope that kind of makes it clear and we can even try it out so if I go ahead and comment this entire thing out this will work just fine and let's go ahead and do this so I'm going to go ahead and do bonex drizzle
kit push so now I'm going to push my schema and I actually have you know you can see it says pulling schema from the database and changes applied so if I go ahead and run my studio now let's see what's going on here so I'm going to refresh this entire thing and I now have uh videos as you can see here and you can see that I have user uncore ID right here and I have a proper relation to the user here you can see that this user uh is not exactly a field it's representing
the relation and if I go inside of users at the moment we also have videos here right so now let's go ahead and enable this video relations like this let's enable it and let's see what happens if I go ahead and do bonex drizzle kit push if I'm correct this should not notice any changes and it did not do and it did not notice any changes right so this relations right here they work on application Level they are used so that drizzle uh let's how I'm not sure how to express myself but yeah the application
Level will be the would be the correct way to express um it doesn't matter what database you use this is so the drizzle knows when you are querying when you're using your relations uh queries let me just go ahead and go back to this one if you're using relational queries and if you want to do with posts true you would have to add this type of relation if I understood it correctly which means that if we use joints I think that we actually don't even need to have this type of thing here now there are
two courses of actions we can take uh for this tutorial right so if you want to you don't have to type these relations right here though you would have to type them if for example you're not using postgress if you're using for example Planet scale which doesn't have foreign keys in that case you would have to add these types of relations the other reason reason would be if you're using relational queries I believe that these queries cannot work if you don't have defined relations but if you use your normal select with joins I'm 99% sure
that you don't need this why am I even saying this well I'm saying in case you're watching this far into the future where relational quaries version to API API has gone out I'm now going to pause the screen and show you the GitHub pool request discussing the breaking changes which will come in the future in regards of that API here we go this is the poll request that they have ready at the time of making this video so it's been uh opened last week it seems and these are the breaking changes that are going to
happen right you will still be able to access the relational queries version one by using database doore query for example what does that mean for you if for whatever reason you want to experiment with these types of relational queries and you want to use this exact syntax you will have to add a little underscore here to access the version that I have in this tutorial uh but that really doesn't matter to you because we will not be using relational queries we're going to be using query Builders but I just want to bring to your attention
that there are some breaking changes coming and I want you to be aware of them both because it's a good practice to keep track of uh dependencies you're using especially your orm that's a pretty big dependency for your project and also if you're watching into this future I want to help you in the best way I can uh for you know for you to move along with the tutorial so you can go ahead and try and Google this uh and you might actually find the pull request from drizzle orm and you can see some other
breaking changes so the following Imports are moved from drizzle orm uh and/ relations to drizzle orm underscore relations right so those are some of the ways you can access the relations uh from version one in version two when this comes out but at the time I'm making this tutorial there is still uh some things that they have to complete so in our case we are going to write this just so we learn how to write them but if you encounter any issues with this if you notice hey this is throwing me errors this doesn't exist
remember you are not going to need them for this tutorial so I'm purely doing this in order uh to teach you how to uh write these types of relations which work on application Level in drizzle and I think that you will be able to import this from SL relations in the future I think but you know for now if everything works if there are no errors no need to change anything at all one thing we're also missing here is to properly EST establish user relations here as well so the forign keys already done its job
it detected the user ID in the videos and if you remember in our drizzle Studio when we clicked on the users table we already had videos but we have to add that manually if we uh plan on using uh the relations application Level API so let's do that here so const user relations will be relations users many and passing videos many videos just like that and let's do export const okay if it's throwing any errors you don't need to write this everything will work without it and you can again try it out if you try
and push something it will just tell you there are absolutely no changes in your database you didn't add anything new here which is correct because what this does is just works on the application Level great so we have established a relation uh with videos and with users in two ways one using database constraint for in key which works because we use poess SQL but in case you opt for using Planet scale for example which doesn't have foreign keys or any other database solution which doesn't use foreign keys this will be the proper solution for you
all right so we have that and now let's go ahead and let's define an optional category ID so category ID will be uu ID category uncore ID and we will add references here as well category uh categories do ID like that so the difference between this one of course uh is that category ID um is not going to be required and if we want to add some specific uh action here we can do this in case the category gets deleted what we can do is just set now right we don't have to delete the video
if the their category gets removed right that doesn't sense that's a bit excessive we can just set the category to null because it's not required in the first place whereas in here I don't think oh it does offer us null as well but that would I'm pretty sure break the app so Cascade is the only option we can do here great so we have that now let's go ahead and also uh populate here so we can just go ahead and copy this and just call it category one categories videos category ID and inside of here
categories ID just like that and let's go ahead and just copy the user relations here and let's add category relations here and I'm pretty sure it's the same thing basically uh categories can have many videos like that uh great so we have this now what I want to do in the videos uh is well um push this schema because we did add a relation with the category so let's do bonex drizzle kit push so this now will cause some changes as you can see but again not because of this not because of this because of
our category ID for in key so it's important that you understand that I'm probably being very repetitive now but in case you're getting any errors with building with Rel API you don't have to do it just remove it from your project nothing's going to happen right as long as your foreign keys are working and you're using the same database as I am I'm pretty sure you're not going to have any problems at all uh great and you know just to wrap up my babbling about uh RQ B2 the docs will come out by the time
this comes out so if you're really really interested you will probably be able to find it in the relations and you will probably see that this version 1.0 is much closer than 75 5% or it's fully out which means that the version two of the drizzle relations is probably out and you will be able to read all about how they're being used inside of here okay enough about me and relations I hope that I kind of made it clearer now let's go ahead and once we've pushed everything inside of our database let's go inside of
our modules let's go inside of studio and let's create a server and inside procedures. TS like this let's exper con Studio router and let's use create trpc router from trpc in it like this and let's go ahead and let's do get many and let's make that a protected procedure because only authorized users will be able to load their uh will be able to load their videos here so let's go ahead and do this I'm just going to go ahead and do query here later we will add something else here but for now I just want
to demonstrate something so what I'm going to do now is the following I'm going to go ahead and get my data using a weight database from Slash from as/ database I will select everything from my videos schema so I have imported that uh and I don't think I even need to add um yeah I don't I I don't really care about uh the fact that I didn't query but by the user ID for now because that's not what I'm trying to show you I'm trying to show you one uh cool little feature if we can
call it like that so A very simple procedure here now we have to add this to our router here so studio will be Studio router like this I like to order them by length if you think that's completely insane feel free to not do that uh okay okay so now what we're going to do is we're going to go inside uh of our source app folder studio studio again page. TSX we're going to turn this into an asynchronous method we're going to do void trpc from server. studio. getet money prefetch infinite so I had no
problems getting prefetch infinite but I will have a problem uh calling use suspense uh infinite query and I'll show you why in a second so let's just add our hydrate client here from the server as well uh and let's just add Studio view here like this now let's go ahead inside of our modules inside of the studio we already have a UI folder so now let's open up a view here let's go ahead and let's add a studi view. TSX Studio View and this is not a default export this is just a UI component inside
of here what I'm going to do is just open up a div and we can add videos section here like this and let's go ahead and create our sections here and let's go ahead and add our videos section. DSX which is finally going to be a use client with videos section component here videos section and now inside of the studio view we can import the videos section component and we can go back inside of our app studio studio page and we can import the studio view which is hydrated and it will have the cached and
prefetched data and if I now go inside of my modules Studio UI sections video section right here let me just refresh this to confirm that the videoos section is being loaded here if I now go here for example and if I try to do the following I will try to get the data from trpc from the client I will choose my studio and I will choose get many uh right now oh it looks like it works use suspense infinite query okay I was trying to demonstrate something but uh I failed what I was trying to
demonst is that uh if you name your query a certain name I believe it will not always uh give you a strictly typed use suspense infinite query I don't know if you remember but this did happen to me uh in my very first procedure that I've created so this was a big fail on my side I wanted to demonstrate something that is not true so let me just try and make that happen I'm I'm going to go inside of my trpc you don't have to do this I'm just trying to show you something let me
bring back my hello base procedure here you don't have to do this so just a random query here and let me return I I've already forgot how to even write procedures so I will just write this return hello world like this I think this is a normal based procedure I think that if I try and change this tier pc. hello all right no matter what I do I cannot reproduce it but in case you're having any problems uh with use suspense infinite query not appearing for you uh there is a fix for that one fix
would be just reloading your window but the second fix is what we are going to do all right so I'm going to stop with all the explanation and demos and I'm going to remove this hello based procedure and I will focus by going back inside of the studio router basically what's important for you to trigger that us suspend infinite query in case it doesn't work for you is to add a proper input here and inside of this input you have to import Zod so let's me just go ahead and import do we have Zod uh
let me just go ahead and see so Z from Zod we do have Zod so basically what we need in the input is an object this object needs to have a cursor and this cursor can then be whatever you want and once you add a cursor like this you will almost certainly be able to get Studio get many and then you will be able to do use suspense infinite query right uh I'm not talking about this error this is a different type of error in my case it was not even allowing me to autocomplete use
suspend infinite query because it noticed that it's not something that can be prefetched in that way all right so one important thing that uh is very logical but still I managed to mess it up so I will give you a tip for that you have to be careful if you're using prefetch infinite you have to use use suspense infinite query you cannot do use suspense query here and mix it with prefetch infinite or vice versa use just prefetch here and then mix it with use suspense infinite query this will cause errors so you have to
be certain about what you want in 99% of the cases if we do get many that's why we're going to be concise with our naming if we do get many it means that we're going to do infinite querying right we're going to do infinite loads so go ahead and add prefetch infinite here and and also prepare use suspense infinite query here now let's go back inside of our uh Studio router procedures here and let's properly Define this cursor that we will expect it will accept an ID which is going to be a string and a
type of uu ID so that's how we are going to know what's the last item we are left off and we are also going to need to have updated ad so basically another key to have this infinite loading cursor work on and this entire cursor can be not meaning that it's not required especially if this is our first request right the cursor will only be used as uh paging method when we need to know from where to start querying and besides the cursor we're also going to have a limit which will be a number with
a minimum of one and a maximum of 100 you can of course uh Define this differently if you prefer it and now this is an important thing so inside of pref infinite here I'm going to add a limit five like this this also means that inside of my videos section I have to do the same thing I have to do limit five here as well and there is one more thing missing here uh every time you do use suspense infinite query you need two arguments first one will be your basic input and the second one
is get next page Pam which you basically uh are telling react query okay how do I get the cursor well that's going to be last page next cursor this will throw an error right now because we don't really uh return anything back from that query so it's confused right now so we are going to fix this in a moment so let's go back inside of this procedure now but before we uh do it there is one more thing I just I think I didn't finish my thought if you add limit five here you also have
to add limit five here so I think a good way to do this would to just establish some Global constants so instead of your Source how about we add some let's see inside of my source I will add a new file why can't I name this I just want to add a new file and name it constants dops like this so inside of my in the root of my source folder I now have constants and I will export con default limit and I will make it five so now that's what I'm going to use everywhere
default limit from at constants like this and I we will do the same thing in the videos section default limit from constants there we go now let's go back inside of our studio procedures and let's go ahead and add a proper get many uh query here so the first thing I want to do is since this is a protected procedure that means that we can go ahead and extract uh from here here context as well as the input so from the input we can destructure the following we can destructure the cursor and we can destructure
the limit but from Context we can get the user from the database and we can get the ID but I like to remap it to user ID right you can do it like that or you can do it like this but I don't like having user here simply because uh sometimes I will fetch the user and I will name the constant user here and I will rarely need the entire object here so I'm okay with doing context. user and then ID user ID or you can do like deep destructuring I believe so user and then
this I think that ends up being the same thing for some reason I like this more so I'm going to use it like that ID user ID this is also called an alias I'm basically renaming this because ID is a bit too generic I want to know that this is the user ID all right so now let's do a proper query here so we are going to query from videos but what we have to do is we have to add a wear query and equals videos. user ID needs to be our user ID so we
no longer have to use the clerk user ID here because this is an actual database user it has a clerk ID but we don't need it in this case let's go ahead and import equals from drizzle omm Right Here and Now what we have to do is we have to implement a proper pagination here so in order to do that we have to improve our wear query so let's add the following from drizzle orm and or larger than and I think that's going to be it let's also add descending yes so and or larger than
and descending all of these things are what we are going to need in order to do pagination here so let's first wrap our very simple equals query which will simply load all videos from One user let's wrap that inside of and like this and then we can add some combinations here so if we have the cursor that means we have to create the following query if we have the cursor uh let's let me just do or here and then just already prepare undefined here in the end and now in the or this is what we're
going to do we are either going to query by larger than videos updated at and our cursor updated at or we are going to query by end both videos updated at and cursor updated at and larger than videos ID and cursor ID like this let me just go ahead and indent uh these two I think yeah there we go like that and this can be collapsed I just want it to be indented properly um okay I think this is fine uh great so we have that and now what we have to do is we have
to add order by here the sending videos updated at and descending videos ID like that and we also have to add a limit limit plus one this limit comes from the input so whatever we wrote how many records to load but we need to add plus one why are we adding plus one add one to the limit to check if there is more data so we are always going to check for one more item than whatever the front end requested simply so we can calculate if we actually have something else to load in the next
batch so let's go ahead and do const has more to be if data. length data being what we we just loaded here so if data. length is larger than the limit in that case we have more uh now what we're going to do is the following remove the last item if there is more data so const items has more in that case data slice 0 minus one otherwise just use the data so if we do have more we have to remove this extra item which we used to check to see if there's more right we
don't want to bring that back to the user the user is not supposed to know that we even loaded that we are only using it for ourselves to know okay there is more data after what user just requested great so we have the items now let's set the next cursor to the last item if there is more data so now we have to set the cursor to whatever is the real last item not this plus one the real last item in this uh data which we loaded so that the next iteration knows where to start
loading from so const last item is items items. length minus one a very simple array usage here and then we can Define the next cursor so if we have more in that case next cursor is going to have an ID of last item. ID and updated at of last item updated at otherwise it's going to be null meaning that the cursor can be completely undefined there we go now that we have that instead of returning the data we can go ahead and we can return both the items and the next cursor like this and now
once you've done that if you go inside of your studio UI uh sections video section here you no longer have an error here because we are returning the next page cursor so this is the information that we are going to use to query for the next page great so we just did that and if you go ahead and try now and do Json stringify here and pass in the data uh you will simply get uh nothing here right you can see nothing in the page perams nothing in the next cursor nothing in the items but
you can see the structure which we have here right so this is what we have to do now we have to go back inside of the procedures here and besides get money get many we also need to create uh our create procedure but here's the kvn I don't want to put that in the studio router I want the studio router to only be responsible for loading videos on the studio page I want that to be a protected procedure so I don't have to you know dynamically pass in the user ID I just want this route
to only be accessible if you're in the studio so what I'm going to do instead is I'm going to go ahead inside uh of my modules and I will create a new module called videos and inside server and then procedures. TS like this so I'm just going to go ahead and copy this basic uh import here like this I will remove uh the okay actually I do need the protected procedure I I'm just think I don't need any of these here so what I'm going to add here is first of all rename this to video
router is it uh or maybe it's called uh videos router yes it's called videos router so let's go ahead and add create here which will be again a protected procedure so we only want uh we only want the uh author to create this so this will be a mutation let's get asynchronous context here and we can destructure the ID from Context user because we are logged in at this point uh and basically what we're going to do here is create the video so await database insert into videos and pass in the values and we're going
to pass in the user ID the title will be undefined Untitled actually uh and I think that's all we need right now and in order for this to be populated here uh you need to add returning otherwise it's not going to return anything back so you can just go ahead and return back video video like this there we go so that's a very simple procedure which we're going to use to create new videos so now let's go ahead and do the following let's go inside of source inside of trpc routers here let's add videos videos
router like this and now how about we go inside of our source app folder uh my apologies uh modules we have to go inside the studio Navar right components Studio Navar actually Studio upload model this is the place we need and let's go ahead and add create to be trpc from the client. videos. create. use mutation so just added this and then on click here how about we just do create. mutate mutate like this so what's going to happen now if I go ahead and click create here nothing should really change but if you refresh
you should now see data here uh if you're not seeing data go ahead and check inside of drizzle Studio I'm not running drizzle studio so let me just do bonex drizzle kit Studio here there we go so I should now have one video here in case you uh cannot see anything here uh you better check for logs here you can see that I got post 200 okay for creating my videos you might have gotten an error in your case uh otherwise if you can see something in the database in the videos section perhaps you did
not connect it to the user properly right you can see that I have my user now has videos if I click here I can see the Untitled video here for my user so if all of that is uh if you can confirm in your database that all of that is true but you still can't see anything here then it must mean that you did something incorrectly inside of your uh Studio procedures here inside of the get many method so just double check that you've added equals videos user ID to this user ID from the context
user like this uh great so we have that but it's not exactly Perfect Isn't it because if I click create here nothing happens I have to refresh in order to see uh the next item this was usually problematic in server components because this is where we are technically fetching this videos instead of studi page. DSX this is where we are fetching them right so it was always problematic okay do I now do router. refresh right do I do revalidate path what do I do to make this uh fetch again where here's the cool thing about
our procedure we don't care about this part anymore we are only using it as the initial loader to prefetch something after that everything ends up being on this client right here this client from now on takes care of any type of revalidation here which means that if you go inside of your studio upload model here and you add utils from trpc do utils use utils here open up on success here and you call utils do studio. getet many. invalidate can you already guess what will happen now I'm going to refresh my entire thing here I
will click create and there we go you can now see the changes happening in real time leveraging both the prefetch from the server components for very fast initial load and also leveraging the fact that the rest is done on the client meaning you are familiar with that right this is not new to you so we literally get the best of both worlds I hope that it's getting clearer and clearer how we're going to use this prefetching thing right I think it's easier to understand it when you actually have some real world examples so that brings
us back uh to this point right remember when I explained this to you we use this server component to prefetch the data but once we prefetch it all this AO refresh I probably should have called this revalidation not Auto refresh so all this revalidation only happens in this uh layer here it doesn't go back to the server component you don't need any router refresh or revalidate or revalidate pad or I don't even know it exists anymore right it's all very familiar great so uh I want to do a couple of more things before I wrap
up so the first thing I want to do is inside of my studio upload model is just disable this button if create is pending like this and I also want to do this if create is pending I'm going to use one icon called loader to icon from Lucid react so just to have uh oh I'm also missing this is disabled and this is supposed to have a class name of T of animate spin so let's try now there we we go this looks much much uh smoother now perfect and we can also do another thing
here so if I go inside of source inside of app layout inside of my tier PC provider I can add a toaster from components actually I'm never sure where I have to import this from okay from components UI soner that is the place where you want to import this from components UI soner don't accidentally import it from toaster so make sure you just add it somewhere near the children like this then go back to your studio upload model and what you can do here is you can do toast. success and you can just say uh
video created and you can import toast directly from soner like this so now let me just refresh first there we go video created and you can also handle the error in the same way and what's cool about this trpc thing is that you can actually be very confident about your errors here right so if you want you can do toast. error error. message right and then if you were to go inside of your procedures instead of Studio server here my apologies video procedures where you create yours and let me throw an error on purpose so
throw trpc error which I've imported from trpc server server here let's see what happens if I just pass in the code you know bad request uh trpc error is not callable oh throw new drpc error bad request so let's see what happens when I refresh this if I click create there we go it just says uh bad request right here uh you can also specify a message if you want you know specific message so I think that now if I click create it will give me a specific message right so you can go ahead and
do that uh as well I'm only interested in what happens if you know an uncontrolled error happens what if I just throw new error here uh whoops will that also work let's see okay so it manages to do these things pretty well so I guess that we can rely on it right the reason I'm doubting this is because uh usually whenever you know I work with errors they are never this reliable know they are always in some weird objects and I can often get you know some weird uh either Json string ifications here or the
app breaks but I guess if the types say that this is a string that will always exist I think we are in the clear but if you prefer it you can also just do a generic something went wrong here and not use the error at all depends on how explicit you want to be with your users do you want to tell them exactly what went wrong do you think that's uh that's information that should be off fiscated for the user it depends on the type of app you're building right uh I'm going to stay true
to the source code and I'm going to be using this but keep in mind that you can use the error and then just get error. message inside of here uh great so I'm going to stop it here there is uh one more thing we have to implement which we're going to do in the next next chapter and that is the infinite load because we have prepared our data for infinite querying but we did not do it uh uh on the UI side and while I have you here let's just learn this what happens if I
go inside of Studio page and for example if I forget to prefetch let's see what happens then so I'm going to refresh this and everything seems to be fine yes it's a bit hard to to reproduce this issue but I think I am getting the issue right here and this is the issue unauthorized right so let me try and refresh more and more you can see that I keep getting the unauthorized I will try and add some space here to confirm am I still getting it yes you can see that I am basically getting tipc
client error unauthorized right why is this happening well it's happening because uh it lost the headers somewhere in between the server component and finally inside of my videos section here so you have to be careful about this whenever you are doing prefetching you need whenever you're doing use suspense query you need to have the equivalent prefetch this brings us back you know when I was explaining to you that the react query team has some in place called uh strict query or query strict wrapper which will warn you uh you know on on the project level
like hey you forgot a prefetch or you have an unnecessary prefetch things like that uh but you know you can always just do use Query if for any reason you don't want to prefetch right so if you don't want to prefet you can just use use Query but in that case you know this changes the data you know basically things get different then we are going to cover the normal examples as well you don't have to worry I'm going to show you how to do all kinds of things and also if you prefetch you also
have to change this to use suspense query because uh I'm not EXA sure what kinds of Errors will appear let me see I think that maybe the exact same error will appear if I refresh here yeah it's it's if you miss the wrong combination of prefetch it's almost as if you didn't write the prefetch at all so if you prefetch infinite you need to use suspense infinite all right I'm going to stop here and we're going to do the rest in the next chapter amazing so let's just go inside of here and let's use our
handy tool we created the video schema we pushed the database ch changes created Studio procedures and we added a video record amazing amazing job in this chapter we're going to go ahead and wrap up what we started in the previous chapter one thing that's missing is infinite loading so let's do a short chapter on how to do that we already have the backend part ready now it's time to implement the UI part besides that we're also going to add suspend and suspense and error boundaries to our videos section component and we're going to create a
reusable infinite scroll component and we're also going to demonstrate how it works so let's go ahead and do that first let's go inside of our videos section and what I like to do here is rename this to videos section suspense and then export comes videos section like this and then what I can do here is I can return the suspense from react and error boundary from react error boundary and then finally videos section suspense let's give this a fallback of error and let's give this a fallback of loading like this nothing much should change but
when you do a hard refresh here you should now see loading for a few seconds now let's go ahead and let's actually implement the infinite scroll component in order to do that we first have to implement a reusable hook called use intersection Observer so use intersection observer. TS let's import use effect use ref and use State all from react let's export con use intersection Observer and let's go ahead and Define the options to be intersection observer in it so this is globally available type inside of here we're going to prepare our state to tell us
whether we are intersecting or not so is intersecting set is intersecting basically we will use the Observer to detect if the user has reached the bottom of a list so that we can safely initiate a a function call let's also Define a Target ref to be used ref and let's add HTML div element here let's call the use effect let's define The Observer here to be new intersection Observer execute it and then inside of its method here go ahead and get the entry return the arrow function here and simply do set is intersecting to be
entry is intersecting like that and passing the options here the options will be our optional prop coming from here if Target ref. current if we have the target set right so we're going to add an empty div at the end of our list for example videos section imagine that at the end of this list we have a little div with the ref Target ref right and then we have our use intersection Observer hook here so basically the intersector will look if we have reached this with our UI so for example on this screen it will
immediately call the next request because it will be immediately rendered here but if I was you know zoomed in or on mobile I would have to scroll and then uh we would see that little uh Target rep so you can remove for now this was just a demonstration so if we have that Target ref we are just simply going to tell the Observer to observe the target ref. current here and of course we have to have a proper cleanup method here to disconnect the Observer so we don't cause any overload here like this and pass
in the options as the dependencies here and you can return back the target rough and is intersect in like this there we go now that we have the used intersection Observer let's go ahead inside of our components and inside of here we're going to create a reusable uh component called infinite scroll. TSX first of all uh these are the props we are going to have so interface infinite scroll props optional is manual bullet has next page Boolean is fetching next page Boolean and fetch next page method now let's export con infinite scroll and let's simply
assign the props here like this let's go ahead and the structure all the props and set is manual to be false by default inside of here here what we are going to render is a div with a class name of flex Flex column items Center Gap four and padding four and then we're going to render our very simple ref which will be the target ref and we will give it the class name height of one so now we have to add uh our use Observer what did we call it uh use intersection Observer so our
use intersection Observer Hook from hooks use intersection Observer and from here we can get the target ref and then we can pass that here there we go so this is what we are going to try and uh detect if we've reached it or not so inside of here what we can do is we can pass in the threshold if we want to modify it for example 0.5 and root margin to be 100 pixels this is the uh configuration that I found to be uh working quite well with my infinite load needs and now let's add
a use effect method here in which we are going to have to decide whether to call fetch next page or not so if we happen to be intersecting which we can uh return from here is intersecting like this so if we are intersecting and if we have next page and if we are not already fetching the next page and if we are not set to is manual only then are we going to decide to fetch next page and we also have to add all of these items here so is intersecting has next page is fetching
next page is manual and fetch next page all of them right so make sure you didn't uh forget any exclamation points or accidentally added some exclamation points here great so we have that now and now what I'm going to do is the following I'm going to check if has next page in that case we will render a button component uh from UI button like this let me just change this to components UI button inside of this button we're going to check if is patching next page we're going to render loading otherwise load more variant will
be secondary disabled will be if it has no next page or if it's fetching the next page and on click here we'll manually call fetch next page so this will be kind of like a fallback in case the intersector for whatever reason doesn't work the user will always be able to manually click load more right but that's only if uh our backend returns that we have next page otherwise we will return uh a paragraph which will say you have reached the end of the list class name text extra small and text muted foreground like this
and now we can go inside of the videos section and we can add the infinite scroll component now this infinite scroll component uh of course needs uh its um well props so let's go ahead and do the following besides data we're going to get the query and then inside of here we can pass in has next page to be query has next page is fetching next page B query is fetching uh next page and fetch next page will be query fetch next page and for now what you can do is you can also add is
manual just set it to true this will automatically uh pull back to true so now you will be able to see the button uh load more and when you click load more it will load more until it reaches the end of the list uh the load more will of course depend uh if you don't have it it means you probably don't have more than five items in your database so you just go ahead and spam the create a few times of course until you have more than five items let's see what the error is here
okay if this happens to you remember uh we did set up our where is it in my protected procedure uh I have the rate limit here so if you want to you can increase this to 100 requests in 10 second and then try again there we go so you should be able to now uh test out your infinite load here and what we're going to do now is the following let's go back inside of our video section and this time let's remove is manual let's see what's going to happen so I'm going to refresh the
entire thing and you can see how it automatically starts loading so that's how our infinite load is going to work if the Observer detects this uh it's automatically going to call it in the use effect but if for whatever reason it fails uh unless it has loaded every single data possible which our back and will ensure that it does uh the user is always going to have that button but if for whatever reason we want to turn off the Observer we just set is manual to true and then it will work like this by the
user clicking on this um great so we are finished with that and uh I actually want to leave it at this I'm trying to think of the best course of action for our next chapter at the moment I think that the next uh chapter should be starting to implement MX actually connecting uh to Max so that we can have some proper videos some proper thumbnails and all of those useful uh things so that we can actually render this data here in a nicer way uh uh but there are some things that we can already do
here let's go ahead and let's go back inside of our studio UI and let's go inside a view Studio view right here and how about we give this div a class name of flex Flex column Gap y of six padding top of 2.5 then inside of here add a div with a class name keyx of four and an H1 element of Channel content like this below that a paragraph which will say manage your channel content and videos let's go ahead and give the H1 element a class name of text to Excel and font bold and
let's give the paragraph here a more of a secondary class like text extra small and text muted foreground like this so so now you should have Channel content written right here uh if you ever get this type of error you can just reload your window this seems to be uh the typescript server going wild let's go inside of the videos section and let's actually create a nicer display of this data here rather than just stringifying it in order to do that we're going to have to import the necessary components from our table so ensure that
you have ADD components UI table as I do Source components my uh yeah can I close everything else close others my apologies not this one close others like that there we go so inside of my components UI you should have the table component here and you should be able to add all of these Imports and now what we're going to do is the following we're going to go ahead and give this div here another div with a class name border on top and bottom and then inside we're going to render a table table header a
table row here and then a table head this table head will simply say video first here with a class name PL 6 and width of 510 pixels after that the next one will be visibility which is not going to have any special classes after visibility we're going to have status then we're going to have a date then we're going to have views comments and likes and for these last three we are going to have a class name text right and the last one we'll also have PR six so right now you should have uh this
table here video visibility status date views comments and likes now let's go ahead and remove the Json stringify here and while we are inside of the table outside of the table header add a table body here and inside of here we're going to go over our videos so let's go ahead and rename this from data to videos and what we're going to do is we're going to combine all the pages available so videos. pages. flatmap get the page page. items and then map and get the individual video like this we're going to use the link
component here and give this an hre of Slash Studio slash videos and then video ID and the key will be video ID as well and then inside of here we're going to add a table row and this will have a class name of cursor pointer and inste of a table row we can just add uh a table cell here which will render video. uh title for now so just make sure you have added the import from for link from next link like this and we also have to uh yeah the infinite scroll can actually be
outside of the table it doesn't have to be inside and you can now see that we have uh some items here it's not exactly perfect and there are some errors going on around here so we are going to go ahead and just resolve this now uh so let's see what exactly is happening here so inside of this for each video we are rendering a table row we are rendering a table cell so how about we add uh you know some more table cells here so we need a table cell for visibility which right now doesn't
exist right then we need uh one for status I believe then we need one for date then we need one uh for views then we need one for comments and I believe the last one is for the likes uh all right so now we can see Untitled visibility status date views comments likes all right uh and let's just see uh if I'm doing something incorrectly here I think that I might be missing uh something uh let's see see because this does not look uh oh yes I think that our link needs to have Legacy behavior
on there we go yeah if you add Legacy Behavior then you can use Link uh to wrap your table row right in case you cannot put Legacy behavior on there is a solution uh well you're going to have to remove the link wrapper so you're just going to have the table row so go ahead and add key here which will be video. and then you're going to have to add the router from used router from next navigation so you didn't have to do this uh I'm just showing you in case you get stuck you would
have the router and then in here on click you would do router. push slash uh Studio slash videos and then video ID which will eventually go to the same thing which right now is for 4 uh the router. push is slower than link because link will prefetch the route so I'm going to uh roll this back without using use router and I'm simply going to use uh the link wrapper with my legacy Behavior prop because without Legacy Behavior it breaks the component so I'm just going to add Legacy Behavior here this uh is something that
exists inside of my next 15.1.2 all right uh great so we have that now uh and I think that uh we are okay uh with ending the chapter here I think we' wrapped up our um we wrapped up our infinite component and we prettified this look right here what we have to do next is we have to uh actually connect to Max our video service we have to find some videos to upload and and then based on that uh info we're going to add some more Fields uh to our schema for the videos right now
because the videos are pretty simple only title and description actually but later on we're going to go ahead and give this a thumbnail we're going to give it a preview URL we're going to give it a bunch full of useful things uh including some Max specific States as well so that's our next goal nevertheless this is definitely starting to uh get some shape great great job in this chapter we're going to go ahead and Implement Max video service we're going to start by creating a responsive Dialogue on desktop this will be a normal model but
on mobile this is going to turn into a Sleek drawer component like this let's go ahead and do that first I'm going to go ahead and go inside of my uh components here and I'm going to create a responsive dialogue. TSX like this I'm going to go ahead and import use is mobile from hooks use mobile and then I'm going to import everything I need from my dialog and from my drawer and you can see that the Imports mostly match the dialogue and the drawer and then the equivalent content header and title make sure you
add all of these Imports you should not get any underlined here because both of these components should exist in your project great now let's go ahead and let's create an interface responsive model props and let's rename this the responsive model actually there we go responsive model we're going to give it a children of react react node we're going to give it open of Boolean title of string on open change which will accept open and return void let's export cons responsive model let's assign these props right here and let's go ahead and destructure all of them
children open title and on open change now let's use our is mobile hook use is mobile this Hook was automatically generated with shat cnii in case you were wondering where does this come from from now let's go ahead and do if is mobile return and let's create a drawer we're going to give it an open on open on open change on open change and inside drawer content drawer header and drawer title and render title inside outside of the header we will render the actual body which will be the children otherwise we're going to return a
dialogue we can copy the props open and on open change here and inside add a drawer content drawer header and drawer title and render the title inside outside of the header you can simply render the children again that's it that's our responsive model now that we have the responsive model let's go ahead and try it out uh looks like I'm not using dialog content dialog header yes my apologies so instead of dialogue I'm supposed to use uh dialogue here there we go make sure you don't do the same mistake as I did so in your
normal return Clause it's dialogue dialogue dialog dialogue dialogue instead of is mobile it's drawer drawer drawer and drawer great you should have no errors in this code now let's go ahead and let's go back inside of our modules Studio UI components and let's go inside of Studio upload model what we're going to do now is we're going to render this responsive model so let's wrap this entire thing into a fragment by default nothing should change if I go inside of my code here this uh should still just render a button here but what I'm going
to do now is I'm going to go ahead and add a responsive model here from components responsive model like this and inside of here I'm simply going to add a paragraph saying this is uh this will be an uploader and let's go ahead and give this a title upload a video like this and let's give it a value of open for now and let's give it on Open change for now to be an empty Arrow function and now as you can see if I just refresh if you are on SL Studio basically wherever you render
this create button you should be seeing uh a dialogue but if you switch to mobile mode it will change to a Sleek drawer like this great so our responsive dialogue works now let's go ahead and let's properly open this so this is what we are going to do we are going to leverage uh from this create method well we can actually yeah we don't have to destructure it what we can just do is the following we are this going to open this if we have the following create Dot data like this so if we get
the data in that case we are going to open it and on open change here can simply call create. reset like this so let's try out let's see if this will work so when I create something new you can see that it will only open a model once my data exists and then when I close this it will simply reset this mutation meaning that we are no longer going to have any created data from here so this is kind of a my solution to use derived State we could of course have created an actual is
open State and then change it depending on on success state but you know why shouldn't we just use derived State uh from here right so this should work just fine uh great and this is pretty much it uh as to how much we can do without creating a Max account so let's go ahead and learn a bit about Max my decision to use Max uh and so on so we are now going to create a free MAX account which means that credit card is not required the reason I emphasize this is because I know that
you know some of you are students some of you uh some of your credit cards are not accepted depending on where you are in the world so I try to find a service which is uh which allows this kind of uh access to their product which is enough for us to complete the tutorial right and I have some experience experience with video Services uh I have a platform of my own and I actually switched through two video Services I first used Vimeo and then I decided that it's not good enough and it eventually became quite
expensive almost $1,000 a year for not that high of a traffic and then I switched to bunny CDN which is great uh it's a great tool and it's quite cheap but it's not even close to Max I would say it's a great tool but I am considering switching to Max now especially with all the new features that keep coming to Max uh much faster than they are in any other video service besides that uh Max is used globally with extremely extremely high uh Prestige clients uh from NBA uh to Shopify uh to all of those
big uh companies that you can find on their website they truly are the video API of the internet right nothing comes close to them uh chances are if you ever watched a live stream online uh if you ever watched any kind of video platform they are using some Services of Max not only that it is also the most coste efficient video service platform right so it's not just some random video service that I found I truly try to find the best solution uh for a YouTube clone and besides that it will give us everything we
need and the good news about this all is that they don't have a credit card require requirement in order to try out their product they don't even have a like a limit in the sense of locking your account no it's free forever but there are some limitations in the free MAX account so these uh limitations are the length limit of the video you will only be able to upload videos which I think are around 30 seconds you can try and upload a longer video and I think they will just cut the video at 30 seconds
the second limitation is that the video will be deleted 24 hours since uh it's been up uploaded and you will also have a Max Watermark so these are the limitations none of this will really cause any problems for our tutorial but keep this in mind in case you want to go into production so what happens if you want to go into production what can you expect in regards of pricing well I will tell you this I added a credit card uh for my initial development of this project and I spent exactly 0er and I unlocked
the full benefits of Max mean meaning I Cann load you know up to 12 hours I videos are of course never deleted by themselves and there are no watermarks at all and I spent Zer I believe that they have a very very high threshold until they actually start charging you which I think will require around 100,000 views uh for for a video before they start charging you you can of course visit uh this calculator. max.com and uh I calculated for my uh platform code with antonio.com and I think I think it will come up around
$10 a month which is extremely cheap considering that I do have a certain level of traffic uh and over you know 170 hours of content right so it's quite cheap considering all that but uh in case you don't have a credit card you can still continue with the tutorial don't worry uh besides Max account uh you will have to either create or obtain a very short video with English audio I would highly recommend finding something with English audio or simply uh recording a 15-second clip yourself if you for whatever reason can't do that uh you
can visit this tinyurl.com YouTube dclip I will of course um I will open this in a moment and you will see and then you will find a short demo. MP4 video which lasts 15 seconds and it's just me speaking into a microphone in English the reason I emphasize on English audio is because we are going to demonstrate a subtitle generation for English videos so and then we're later on going to use the transcription for some AI features so that's why I'm emphasizing this and 50 second is only because that will be a very small video
so you will be able to test faster you know you don't have to wait for upload of a megabyte sized video and also remember length limitation right uh great so we can actually Mark our responsive dialogue as finished now let's go ahead and let's create a MOX account so head to max.com and go ahead and click on login once you're inside of here uh you should be able to see this screen store and stream video files online create your first asset and all of those things and you will also see this right here upgrade to
remove all restrictions and the restrictions are the watermark the duration limits and the fact that videos are deleted after 24 hours don't forget about this in case you're using this you know for your school project uh or want to demonstrate this to someone and 24-hour passes and your videos get deleted don't be surprised this is why it happens uh if you want to upgrade you can go ahead and click upgrade I chose this option which is pay as you go which means that they will not automatically start charging you anything until you actually start uh
reaching something as you can see in your plan it will be included 100,000 views per month right so I think that you it's very hard for you to accidentally reach 100,000 monthly views uh so I think you will stay within the free tier this is if you want to upgrade you don't have to for example I will not upgrade uh for this tutorial here uh great so what I want to do now is I basically want to uh create this this uploader uh right here and we can do that by first creating an environment so
I'm going to go ahead and create my new environment here and I'm going to call this uh new tube like this and I will select development so let's call this new tube development like this and I'm going to go ahead and select all users and click add environment and I I should now have new tube uh development right here and I should be able to click on host and stream video here like this and now how about we go through the docs together and learn how to uh add the necessary uh the necessary um packages
and environment variables uh for Max so let's go ahead and start with integrate with your app right here and I'm going to select NewTube deployment uh development and the permissions here uh let's see let's select MOX video well let's select all of them right I think uh it's okay for us to have them all the access token name will be NewTube development and let's click generate token and now you have the access token ID and the secret key and as you can see they don't store the secret key so memorize it or simply download it
so let's go ahead and let's add these keys to our uh project so I'm going to go ahead and go inside of my project inside of environment. local and I'm going to add Max token ID that's going to be the shorter key here and then Max token Secret right and this will be exclusively for this environment which we created new tube development right so make sure that you are inside of here every time you visit this page you know just make sure that it didn't automatically maybe select development or production here uh I decided to
use this one our new one so that it's very clear that this is for that project in case you want to reuse this um in case you want to reuse this account of viers for any other projects you might build so I created NewTube development here and clicked integrate with your app I selected NewTube development here as well I selected all of this and then I clicked generate token I believe that you can also find the token in the settings here there we go access tokens there we go new Tobe development but I can only
see as you can see uh the max token ID I cannot see the max token Secret in case you think yours got lost you can just click generate a new token again select new tube development and I'm not sure if we need all of these but it's not going to hurt us to have it uh okay great once we have that let's go ahead and let's look at the documentation here together and specifically I want to focus on uploading the videos and we're going to use the max uploader for web uh basically this is what
we are going to build so let's go ahead and see I'm not sure if we need this because I think there are instructions specifically for nextjs I just want to see uh okay this is this is for playing the video I just want to confirm that there we go Max uploader react I think this is what we're going to need so let's go ahead and install that but you know be careful just wait for you uh wait for you to see wait for me to add it so you can see the version that I have
so I'm just going to add it to my project and then you're going to see the version so just a second uh there where which one is it there we go so one . 1.1 so what you can do to ensure that you have the same version is this bunad Max Max uploader 1.1.1 let me just uh confirm in the package Json Max uploader react 1.1.1 uh great so we now have that so let's go ahead and develop our studio uploader here so I'm going to go ahead inside of my uh modules inside of my
studio inside of UI components and I will create a studio uploader TSX like this and let's go ahead and create an interface Studio uploader props which will accept an endpoint and on success here like this and let's also import everything we need from the max uploader which we just installed so from Max Max uploader we need a default export Max uploader and then we are going to to uh have this kind of granular uh Max uploader drop file select progress and loader status this will allow us to customize it to make it a little bit
prettier because you can see on their documentation uh let me see on the max uploader here it's not exactly the most beautiful uploader like it's okay uh but we're going to make it look a little bit better like this more similar to one on YouTube all right so now what we have to do here is we have to actually develop it so let's EXP const Studio uploader let's go ahead and D structure the props here endpoint on success and now inside of here let's go ahead and give this a div and render Max uploader here
and I think by default we should already kind of uh see this let's go ahead and do instead of Studio upload model just render the studio uploader you can ignore the typescript errors for now so if I go ahead and refresh this and click create there we go I can already see uh an uploader here right uh the one thing that's currently missing as you can see right here uh is we are missing let me just find okay so the react usage was here okay we are missing an end point right so endpoint is a
value that we need to pass to our Max uploader and in our case uh we're going to have to create that endpoint and then we're going to have to create some web hooks because remember uh videos can take hours or if not days to process like for example my videos in YouTube which last 12 hours they definitely take a couple of hours to process and we can't really await that in a promise we need to set up web hooks in order to make that to work but let's first solve this pass the URL to the
endpoint property on the Max uh uploader component so whenever we upload the video we're going to have to uh reach a specific end point in order to obtain uh the endpoint we're going to have to install the node instance of Max so again I'm going to do Max from Max Das node you can wait a second if you want to see the exact version so this is my exact version so for you you can do one add maxmax node at 9.0.1 you should have Max node and then what you can do is you can go
inside of your Source instead of lib and simply create max. TS import MOX from Max Max node and Export const Max to be new Mox and inside of here you will pass the token ID and you will pass the token secret and we have both of those inside of our environment. loal Max token ID and Max token secret so let's add them like this and Max token secret like this you can even point Exclamation point Exclamation points at the end so then uh you won't have any uh errors here but looks like there are no
errors with or without this exclamation points so this works fine so now what we have to do is we have to go back inside of our video module specifically our procedure where we create the video and what we have to do is we have to uh we have to create an upload right so this is what we're going to do const upload will be await Max which you can now import from lib Max like this max. video. uploads doc create and then we're going to open up new asset settings here and we're going to add
the pass through of user ID the pass through is a field as you can see arbitrary user supplies supplied metadata that will be included in the asset details and related web hooks can be used to store your own ID for a video along the asset so basically what a pass through is is um well let me remind you when we upload a video it's not going to be immediately processed we're going to have to wait for a web hook and web hooks are Anonymous web hooks will not come from our logged in user so we
need a way to preserve which user has uploaded this video otherwise MOX will simply fire a web hook on our part and we're going to have no idea whose video is this that's why we need some metadata and we can store that through pass through and we're going to use the database user ID for that great so we have the pass through set the playback policy to be public MP4 support will be standard so these are the settings that I found in their documentation right nothing other than that and I believe we also need uh
outside of here Force origin uh we're going to set it to uh all like this just an asteris but I'm going to give a little to-do here in production set to your url but just so we don't mess up you know the development uh you know course can be quite annoying of course it is secure but it can also be quite annoying so this is how we create an upload right and once we create an upload inside we will be able to pass the following we will be able to pass url upload. url like this
so now what's going to happen is that if you go inside of your studio upload model every time that you create something you will have alongside your video also a URL right and then you will be able to do the following you will be able to pass an endpoint which will be your create. dat. url now of course uh this is what we're going to do so I'm going to check if we have create data URL I think I need to add this otherwise we're going to render a loader to Icon and let just close
this so let me collapse this so it looks a bit nicer like this there we go and we can be more specific here as well so let's specifically check if we have the data URL like this uh great and here we can also pass on success just to be an empty Arrow function for now like this uh all right so now we uh should properly establish the end point to where this should uh upload and now it basically knows since we're using our MX lib to create this URL endpoint it knows that it has to
upload it to this specific environment right so these tokens come from my apologies from this environment right here newbe development right always whenever you click on assets always change to newbe development right so th those are the keys for this environment that's why this upload URL is important and I think we can already try it out so here's what you can do if you didn't obtain a video uh either go ahead and create it or simply go to Tiny URL l.com youube dclip let me go ahead and copy this so I'm going to go ahead
and go here and it should simply open this public Google Drive which I've created and you can just go ahead uh and download uh a simple demo and before video looks like I'm getting some error here perhaps something wrong with my settings uh I'm going to see if that's something I have to change here uh but basically it's just a regular video where I'm speaking uh in English subtitles here and let's go ahead and try and uploading it so I'm going to refresh everything here I will click create here oh looks like something uh went
wrong so let's see what that is looks like I got 500 back so let's see uh how we should debug that I'm going to go inside of my procedures here uh looks like we we have an upload here but maybe this fails all right so I think I've debugged our issue uh if I click create here I get an error here and in the network tab I can then see uh an error here which says invalid parameters deprecated standard MP4 support is not allowed on basic assets so that's probably because when I developed this initially
uh I used uh an account with my credit card added so I think I have to remove this let's see if this will now change anything if I click create there we go video created and we actually have the proper uh upload created as well so now I'm going to go ahead and upload uh this demo video so let's go ahead and go inside of your assets here select new tube development make sure you're here and if you upload it you should see it there so I'm going to select my demo video here oh it
looks like no URL or endpoint specif it cannot handle up looks like it's not exactly working as uh intended so I'm going to go ahead and do conso log upload and below that I'm going to do a conso log URL upload URL let's go ahead and debug This Together see exactly what's going on I'm going to add some space here and let's click create so looks like it uh I can definitely see the URL here that's interesting oh I think I know what's the issue the issue is not in our back end the issue is
that after I pass the endpoint in the studio uploader I never use the endpoint so let's pass in the endpoint like this and let me try again now demo. MP4 there we go now it's uploading and it just says upload complete nothing more more nothing less if I go here I can finally see one new asset right here uh and this is what it looks like so it is a free test asset it looks like it's limited to 10 seconds right and it will be deleted after uh 24 hours so I'm just going to turn
off the audio here and you can see it is for API test purposes only which is exactly what we're doing we're testing the API to see if it works uh but you can see the status it's ready you can see that we have the duration of the video uh and all of this other useful information uh I'm going to see if we can if it will allow us to generate thumbnails on the basic tier I hope it will uh but it's okay we will find some other solution if it doesn't allow us to do that
on free tier great so now let's go ahead uh and well discuss how should we exactly you know track that this video has now been uploaded but if you take a look in my BX drizzle kit studio right now and if I go here to Studio drizzle there is not a single video that I can tell you okay I know which Max asset is for this video right we have no idea neither which user uploaded this nor for What video did it upload it because our video database entity and MOX asset are two different entities
so we now have have to find a way to connect them together and we're going to do that the following way before so I just want to do that before we actually know spend some time with the studio uploader let's go ahead and do the following I will close everything and let's go inside of our database schema here we're going to go inside uh of the videos and we're going to add everything we need uh for uh regarding MX so after description go ahead and add Max status and yes I will prefix them with Max
simply you know for us to have it easy your understanding okay this comes from Max not something else so max status Max asset ID this will be unique for each of our video we're going to have Max upload ID again this will also be unique we're then going to have Max playback ID Max upload ID my apologies playback ID which will also be unique Max track ID unique as well and we're going to have Max track status Max track status uh like that so these are all the elements we're going to need for Max so
the max status will basically tell uh the user what's the state of their video are we still are we still transcribing the video are we uploading it what's the state right are there any errors or something like that the asset ID will be something we will receive once the asset was created the upload ID is something that we're going to immediately populate when we create the video inside of our videos procedure so because we have the upload here so we are simply going to pass the upload ID inside of here and then when the web
Hub comes we're just going to look for that upload ID and then we're going to connect uh the asset with the video the playback ID is something that will uh come once the asset has been created and we're going to use the playback to play the video and to generate thumbnails or previews the track ID and the track status will only be available if uh the if the um my apologies if the asset has subtitles right so if we can do any kind of transcription in that case we will see the track ID and the
track status as well great so let's go ahead and do that now so we're going to go back inside of these procedures and this time what we're going to do is we're going to set the max status manually as waiting as in we are waiting for a video to be uploaded in this place and we are going to set the max upload ID here to be upload. ID now of course this will not work just yet because we have to push this schema changes so let me shut this down and do banex drizzle kit push
I believe you even might get some errors at the moment right now because you can see Mac status does not exist so feel free to shut down the app and simply do Bun Run Dev all here now I got a prompt here uh what I want to do with this simply because we have 20 items in our videos table in our case it really doesn't matter what happens you can select both yes and no uh I'm going to select no just to see if it will work let's see no again no again no again I'm
going to select no for all of those fields and it just says changes applied uh it really doesn't matter for us because all of these videos are just you know empty videos matter of fact how about we delete all videos so we have a clear example so let's do bonx digal kit Studio let's open the studio and I will just remove all of my videos because all of them are empty anyway there we go uh great so I'm just going to refresh my app here and we're still not you know totally ready to connect our
asset with the because what we're missing is the web hook so that's the next thing that we have to do so let's start by going inside of our Max uh where is it here how about we go into settings and let's click on web hooks right here now again make sure you change this to new tube development and click create new web hook and now we have the URL to notify and luckily for us we already know what to put here because we used web hooks before that you can find in your Dev web hook
here uh I hope you did this because it's quite useful you can see that now with one command we are connecting to all these web hooks that we will use throughout our project so this is https make sure to put https in front like this uh and I'm going click create web hook here and now I can click on show signing secret and copy this let's go inside of our environment here and let's add Max web hook secret and add it here here like this there we go now let's go ahead inside of our source
app folder API and let's go ahead and let's create videos and then let's go ahead and let's create a web hook and let's create route dots like this and this will basically be our uh videos web hook so let's export const post request to be an asynchronous request like this this is our web hook obviously this will uh translate to Local Host 3000 apios web hook so I just remembered uh we also have to modify that here I believe and I think I made a mistake by creating this like that how about we remove this
one delete the web hook and create a new one uh and just make sure you select new tube development here and let's go inside of my package Json so I can obtain the URL again so https this SL API web SLV videosweb hook so make sure this is properly written right let me just confirm SL API videos SL web hook API videos web hook great and then you will have to copy the new signing secret and go back inside of your environment. loal and replace it right here there we go uh great so now this
works and now what we have to do is we have to first of all prepare our signing secret to be process. environment and then just copy don't type it out because uh you can add some titles here without you knowing uh great besides this what we're going to have to do is we're going to have to prepare the equals from drizzle orm so we can find by user ID by upload ID something like that and we're also going to need from mxmx node /resources slwe hooks. MJS looks like it also works if you just go
SL web Hooks and from here we're going to import all the types we are going to look at so video asset created web hook event video asset errored web hook event video asset ready web hook event and video asset track ready web hook event great and then let's go ahead and let's create one unified type called Web hook event and this should use all of these from above so our web hook event can be uh one of these types all right now inside of here first things first we're going to check if we're missing the
signing secret and in that case we can just throw new error backx uh web hook secret is not set and let's just say true Max web hook secret great okay uh then what we have to do is we also have to import headers uh let's see headers from next headers we have to do that so let's obtain the headers payad to be await headers let's get the max signature to be hea's payload get Max signature if we are missing Max signature it means that whatever is trying to access uh this API endpoint is not Max
it is something most likely malicious here so no signature found with a status of 401 if we do have the signature we can then go ahead and get the payload from await request. Json uh I'm not sure if we defined request we did not so the request comes from here it will be a type of request this is a native type all right so we have our payload and then let's also get our body which will basically be a stringification of the payload and then let's call Max from lib Max I will separate that import
here to verify this headers and everything so max. web hooks. verify signature pass in the stringified body select Max Das signature here to be Max signature which we get from the headers make sure you don't add any typos here because anything any anything wrong here can break the verification and pass in the signing secret that's it this will throw an error in case something goes wrong now let's do a switch here on payload do type as web hook event type and in case we have video asset my apologies video asset created in that case what
we're going to do uh is we're going to update the video record in our database with the the appropriate upload that has happened so this is what we're going to do const data will be payload do dat as video asset uh created VB hook event and simply Target the data if case we missing data upload ID it means we can't really find the relevant video uh I mean this should never happen right if this happened something went wrong on Max side so let's just return new response here no upload ID found and we can return
back a status 400 because this is not something we can work with otherwise we can go ahead and update the videos schema so make sure you imported the videos schema here and we can set the max asset ID to be data. ID and we can set the max status here to be data. status and we also have to import the database from s/ database my apologies so we are updating these two because we now have them and let's pass in the where to be equals videos Max upload ID to match data upload ID like this
and and break like this now let's just go ahead and outside of the switch here let's do a return new response web hook received just so we don't uh throw web hooks always expect a success request at the end and if you have too many failures of a web hook they might actually shut down your web hook so always be careful to do that so I think we've written this correctly I think this should now work basically uh what we're doing here is the following when we go inside of our videos procedures remember we first
create the max upload and we store the upload ID inside of our newly created video entity here so let's go ahead and demonstrate this now so I think this should work already I think everything should be fine but you know you never know so just make sure you do Bun Run Dev all so you should have your web hook running so if I go ahead and copy this and if I go here I should see my app running there we go as you can see I can see my app now close this and work in
Local Host right uh but it should you should just confirm that that is working so what I'm going to do now is the following I'm going to click create Here and Now video has been created don't close this just yet if you now go inside of drizzle Studio here and refresh make sure you have your you know drizzle kid Studio running as I have drizzle kid studio and if you now go inside of videos you should be able to see a Max status of waiting because we are waiting for a video to be uploaded and
we have the max upload ID so once the web hook hits our server we're going to use that upload ID from Max and tell our database hey I have a video which I prepared this Max upload ID for can you find that video and now give it some new information like Max asset ID and Max status right so let's go ahead and try that so for now Max asset ID is completely empty as you can see uh as well as you know Max track ID Max track status everything is basically empty besides Max upload ID
so what we're expecting to happen is that Max status changes to ready and Max asset ID changes to an actual ID if we've set everything up correctly so I'm going to go ahead and upload my demo video here uh let's wait for this to upload uh this should just say upload complete and if I now uh let's see in web hooks Max can I uh I'm not sure if if I can know keep track of my events here I think that's one thing that I I'm missing from here it's not exactly clear uh you can
exactly you know keep track of what's going on but what we can do is we can look at terminal and looks like something was definitely hitting our API videos web hook and it returned 200 which means that our verification was correct which in turn should mean that if I refresh this there we go it still says preparing so it's still doing something in the video but the max asset ID is right here perfect so this is exactly what we wanted to achieve because now our video is correctly associated with a Max asset right it's no
longer uh random as to which video we uploaded and uh which video uh uh we have in our database great so in case this is not working for you start troubleshooting from your terminal are you seeing API videos web hook if you are seeing them but there is an error here you are most likely doing something incorrectly in regards of your web hook verification so just be careful and go ahead and read it slowly line by line in case you want to see the documentation for that you can click on listen for web Hooks and
you have verify web hook signatures here which will guide you through the same thing that I was telling you about and then they will basically give you a generic pseudo code on how to do this but you can also click on node and they will uh tell you how you can verify the signature but I think this is the only thing that can actually fail at this stage so perhaps you are missing the web hook secret make sure you didn't misspell this make sure that you actually have it in your dot environment right here make
sure that you didn't accidentally add the wrong one but I think it will give you some useful information if an error happens uh great and furthermore if you think that the verification is not the issue but you're still getting some errors you can add some console logs here so I'm just giving you some guide on how to debug things how I would go ahead and debug this uh great so this seems to be working and um I don't think there's any point in US uh going further with all of these other types we will of
course add them but for now this was the most basic one okay the asset has been created how can I connect that asset to my video database uh great we've done that now what I want to do is I want to wrap up the tutorial my apologies the chapter not the tutorial uh with making this uploader look good so let's go ahead and do that we're now going to focus on the uploader so feel free to click create uh and just leave a video here here let's go back inside of our studio uploader and we
should have all of these uh unused files uh right here so this is what we're going to do we're going to go ahead and give our Max uploader here an ID because uh that's how the Styles will recognize for what it is so I'm going to call this video uploader like this I'm going to give it a class name of hidden and then I'm going to give it group SL uploader basically I'm going to hide it it's not going to be visible because we are now going to build our own so we need to hide
it uh CSS with CSS but it still needs to exist it still needs to be rendered here that's why we need the ID of it and now let's add our Max uploader drop component and let's give it a Max uploader of video uploader uh let's go ahead and do this how about we add this to a constant so const uh uploader id id and just add it here and then use the uploader ID in both of these places there we go and now let's go ahead and give this a class name of group uh Slash
drop so we can differentiate between different groups and what happens inside and then we're going to use uh this like native web components using slots so all of this comes from uh MX documentation on how to customize uh the uploader I think they have customized look and feel here and then you can see that you can customize all of these things using uh sub components right so basically that's what I researched and now I'm just telling you I'm showing you my finished result of how I managed to do it uh great so slot heading will
have a class name of flex Flex column item Center and gap of six inside of here we're going to have a div with an upload icon from Lucid react so just make sure you have added the upload icon here I'm just going to move that here and I think that already we should see the upload icon and we also see the text which says or that comes by default in the heading slot I believe let's go ahead and make this upload icon a bit better by giving it uh wrapping div a class name of flex
items Center justify Center gap of two rounded of full background of muted height of 30 32 and width of 32 there we go already looking better now let's give the upload icon itself a class name of size 10 text muted foreground group drop and then you're going to have to open square brackets add an add sign uh and sign and then open square brackets again active and then animate bounce so this will be a cool effect that when we uh like drag something let me try uh okay I'm not sure how exactly to demonstrate it
now but uh if you drag something it's going to start to bounce uh but I think that I missed something here let's see group uh drop active Okay I'm going to leave it I think it's okay for now let's add transition all and duration 300 like this we're going to come back and fix it if it doesn't work outside of this div add a new div with a class name Flex black column Gap 2 and text Center and inside of here add a paragraph with a class name text small and it's going to say Drag
and Drop video files to upload and below that another paragraph your videos will be private until you publish them because we will add privacy settings later for the videos and let's add text extra small and text muted foreground here like this and then let's add Max uploader file select here give it a Max uploader of uploader ID and add a button from components UI button here so make sure you have added this import let's close the button inside and the button will simply say select files the button will also be type of button that's important
and have a class name of rounded pool so now you should also have you know this nice input or you can click select files directly here great and now go ahead outside of this div and add a span with a slot of separator which is basically this the or text and we are just going to give it a class name of hidden it can even be a self-closing tag and now the or text should disappear below that we're going to add our Max uploader status it's going to be a self closing tag it will take
the max uploader uh status it will take the max uploader to be our uploader ID and class name will be text small and below that we're going to have a Max uploader progress self closing tag as well which will have a Max uploader of uploader ID class name of text small and type will be percentage and Max uploader progress again which will be Max uploader uploader ID and type will be bar so I want to display both of them because they look good together now let's go ahead and try it out now so uh I'm
going to click create video created and let me go ahead and select this and there we go I think this looks much nicer we have both of these uploads and at the end we have uh upload complete here uh great so yeah you might have noticed that there is a potential you know issue that we have is that if we click create the video has been created and then uh if we just close this you know that's it this video will forever stay without its equivalent record so I was you know contemplating which solution do
I have for this and I ended up you know leaving it like this because we will have a status which will basically say waiting so the user can always you know will be able in the future to go inside and just delete that record we can also create a Chron job or a background job which will recurrently look for videos like that but um there is no I couldn't find like a nice way to do it um because we cannot exactly know when an asset has been connected to uh the video which has been uploaded
right what we could do is we could you know redirect to slash Max upload ID and then use that in a route to find the video that's one solution maybe yeah that's something we can you know explore later this is the solution I went in the end with um but yeah I will explore it once again later on just in case you notice like hey I can create as many of this and they will never be connected yes that is an issue but you're going to see how it looks like when we actually have some
data it will actually look like something's wrong with the video it will show like a sad face because we couldn't load a thumbnail so it will indicate to the user that they can delete this but perhaps we can think of a solution to automatically handle that or avoid that from happening in the first place uh great but I think that we achieved everything we wanted in here so let's go ahead and use this we created a free account credit card not required uh at least I managed to obtain a 15-second video I hope you did
too and we created the upload model so that's all we wanted uh from this chapter we successfully uh integrated Max great so and we also have this cool responsive dialogue uh and you can check out how that looks yeah we forgot to check that when you click create there we go looks very very nice perfect so we have all of that here uh and what we're going to do next is we're we're going to explore some more events that we can track let's just see what's the error here oh on success here let's let's add
the on success to the max uploaders before we wrap up like this so just add the on success here uh and what we're going to do next is we're going to explore some more of these cases which we can use to upload our to update our video assets and then we will finally have some thumbnails and things like that to display here uh for more information great great job in this chapter we're going to go ahead and continue using Max web hooks to populate some more information in our video databases records let's start by creating
the UI part first so you can understand why we need the video asset ready event I think that's going to make a bit more sense so let's start by going inside of the public uh Guist that I have prepared where I told you that you can find the logo for the application uh well now I prepared a little placeholder for you which we're going to use when a video doesn't have a thumbnail set there are two ways you can obtain this you can of course use any image you want uh or you can simply download
one from my Guist here or if you have access to the source code just find the public folder and you will find a file called placeholder so the way I'm going to put it in my project is I will literally copy the SVG if you want you can just drag and drop the file there we go that's it that's my file so what we're going to do now is the following we're going to go inside of source inside of modules videos and we're going to create the components my apologies UI and then inside we're going
to create components like this and then finally you can create the video thumbnail do TSX like this let's go ahead and Export con video thumbnail here let's return a div with a class name of relative let's go ahead and add a comment here this will be our uh thumnail wrapper I guess so this will be that div and then below here I'm going to add a comment duration box video duration box right uh we're going to have that here to do add video duration box just for you to understand why we're going to have this
wrapper first and then a thumbnail wrapper again so both of this will need to be relative so let's go ahead and add relative here again pull with overflow hidden transition all like this uh actually we might not need all of these so let's just add overflow hidden here rounded extra large and aspect video later on we're going to add some more classes here but it will make more sense uh once we can actually uh I'm kind of keeping information from you because I want to naturally uh uh build this but since I already know how
this is going to look like basically we're going to have two image elements inside one will be a normal static uh image and the other one will will be a gif right an animation so when we hover over this element we're going to hide the static image and show the animation playing so we can show a few Snippets from the video to the user without actually loading the video so that's why I'm telling you that we're going to need to have some additional classes here in the future but for now all I want to do
is just render this placeholder which I just put in my uh project so now let's add an image from next image let's give it a source of/ placeholder.svg and I'll of video or placeholder actually thumbnail and let's give it a fill property like this uh we're also going to give it some class names here uh let's give it size full which uh yeah if you don't Know size- full is the same thing as writing height full and width full so whichever one you prefer and let's also add object cover here like this uh and I
think this will be enough uh to try out the video thumbnail so now what I want to do is I want to go inside of my studio UI uh sections video section and inside of the first table cell where we render the video title we are now going to render that thumbnail so add a div here with a class name Plex items Center Gap 4 another div here with a last name of relative aspect video width of 36 and Shrink 0 and then a video thumbnail inside now the video thumbnail will be imported from modulus
videos UI components video thumbnail and if you try it out you should now see the empty thumbnails here because none of our videos at the moment uh have thumbnails so of course we are uh using just the normal SL placeholder SVG so just double check that inside of your public folder you have the placeholder SVG here great so what our job is to do now is to use the handle video asset ready web hook right here and actually uh populate the thumbnail so let's go ahead back back inside of our route for the video inside
of source app folder API videos route and we have video asset created so what we're going to do next is we're going to do Pace video asset ready like this now inside of here uh what we have to do is we have to find the asset which we previously updated so now we can use the asset ID if we want to we can use either Max upload ID because I think both will be preserved in the data so first things first when we handle a new event we have to get the data so let's copy
the data from here data and let's use video asset ready web hook event so you should have this imported as well and now you have uh the new data here and this is what we're going to do first let's get the playback ID we can get the playback ID from data playback IDs and by the time we get to video asset ready we should almost definitely have it like this but as you can see it's still throwing some errors so we do have to use the optional chaining methods here like this actually it looks like
we don't need here we just need it here so we have to put it like this and then what we can do here is if we have no playback ID we can immediately stop this right or we can just return uh new response missing playback ID because without this there's nothing much we can do for this video so I'm just going to throw back an error here then what we're going to do now is the following uh we can easily get the thumbnail URL by using the playback ID so the thumbnail URL is made by
going to image. max.com playback ID and then thumbnail.jpg that's how you create a thumbnail uh using Mox and the playback ID and we have to wait until the video asset is ready and only then can we uh obtain the playback ID because the playback ID does not exist exist here as you can see they have made it optional because there's a chance it doesn't exist but I'm pretty confident that it will always exist here because in this uh web hook event um yeah I think they didn't you can see that when I hover over data
here they use the same asset object for all of them so they didn't exactly modify how the asset will look like on different web hook events but we know how it's going to look like but either way we have protection in case it's missing missing here uh great and now we have the thumbnail URL which is cool uh but one thing that we are missing is inside of our schema videos we don't have uh the thumbnail URL here so let's go ahead and go after Max track status let's add thumbnail URL and I'm not going
to prefix this with MX because we will be able to upload our URLs as well right we will also be able to use AI to generate a thumbnail great so when when I added this I'm now going to do bonex drizzle kit push just so the new thumbnail URL is uh pushed you know to my database and then what I can do in the route here is I can update my uh video so let's do A8 database update the videos schema do set and now let's update the max status to new data status because at
this point the status will be ready so it would be nice to indicate that to the user we will have the playback ID we will have the max asset ID to be data. ID and we will have the thumbnail URL like this and then we use where equals videos Max upload ID matches data upload ID it looks like uh data upload ID is also optional at this point so I'm going to check the same thing here if we have no data upload ID there's no way for us to know which video we are supposed to
upload uh modify so we're just going to say missing upload ID and a status of 400 here as well there we go and now we don't have to worry about that uh great and let me just add uh a break here let me confirm do I have a break here yeah a lot of people prefer you know using this switch cases for web hooks I don't know I've always preferred if clauses I always find them to be more readable but uh yeah okay let's continue using the switch case here just make sure you added a
break here and I think that now uh this should work let's go ahead and let's try it out let's go inside of our video thumbnail component and let's create an interface video thumbnail props and let's give it a image URL which will basically be a string but it can also be uh optional in case it doesn't work in case we didn't uh do this in case the video is not ready yet so video thumbnail props image URL and what we're going to do is the following we are going to check if we have the image
URL or we're going to use SL placeholder SVG like this so by default I think nothing should really change here right but let's go ahead and try this out now so make sure that you have Bun Run Dev all running so you should have your enro running as well so the max web hook can communicate and I'm going to add a new asset here and I'm going to go ahead and upload my demo video now I'm not sure if this will work on three tier uh but we're going to see either way so let me
go ahead inside of my uh MOX here I have my assets uh inside of my new tube development here this is the most recent one that I've added here uh 10 second okay playback and thumbnails this is the playback ID right and I think that if I click on create thumbnails they teach you how to do that here for example you just this is exactly what we did on the back end right image. max.com and then we used uh the playback ID which is exactly what we have here and then just thumbnail and then PNG
or something else so I think this should work let's see looks like we got a few post requests inside of our web hook here so that could mean that there is a web hook that there is a video uh looks like I don't have this running let me just run my drizzle kit Studio here so that should now mean that in my database I should have at least one video with a thumbnail URL let's see if that's true there we go looks like one of them has a thumbnail URL so let's go ahead and try
this out now I'm going to go ahead and refresh uh maybe they're not ordered correctly but looks like no looks like it's not uh working I think I know why it's not working because we forgot one thing instead of modules Studio UI set sections videos section I never passed the image URL to be uh video. thumbnail URL and let's modify this to accept string or null there we go so no errors now let me refresh uh all right an error an error is because image. max.com is not configured under images in your next. config.js so
just confirm that this is the host name you have image. max.com or if you have any other host name that's okay you just have to know what your host name is and in order to fix that we have to go inside of next. config uh. DS and we now have to add images remote patterns rocol https and host name imag max.com and the change of the environment will automatically restart the server so you just have to wait a bit longer this time because this counts as a server reload and let's wait a second and there
we go you can see that we have automatically generated uh tumbnails coming from MX this is really really cool so from now on for every video you upload you will have an equivalent thumbnail being generated great but that's not all we can do we can also create animated gifts as you can see for example let's wait for this to load you can see how this video is slightly moving right it's a very short GIF but it is an animation and we can do that as well so let's go ahead and go inside of our schema
here and besides thumbnail URL let's add preview URL as well like this and let's go ahead and do bonex drizzle hit push and then let's go back inside of our uh videos web hook here and what we're going to do now is we're going to create the preview URL in the same way so con preview URL will be the same thing https and this will be imag max.com again playback ID and this will be animated.gif like this and and then you can pass in the preview URL here as well uh great so let's go ahead
and check that out uh so this is how we're going to do that uh we can't really you know see this anywhere now so what you have to do is you have to upload another video again for example I'm going to upload another video and I'm going to verify with Drizzle kit Studio let me just uh find the proper command so drizzle kit Studio I'm going to go ahead inside of here again and I will refresh my videos and you know when these web hooks hit again that probably means that the video asset already has
been uh added here and I should now have a preview URL for at least one of my videos here there we go so what I'm going to do now is I'm going to uh force uh the code to display the GIF instead so I'm going to go inside of modules Studio UI sections video section instead of thumbnail URL I will now use the preview URL like this and let me just refresh this entire thing and now you can see that this is a gif it is changing right you can see how you can see me
scrolling in here great so that is a gif amazing and now this is what we're going to do we're going to wrap up our video thumbnail by making it behave properly so we're going to pass in for the image URL the image URL and then we're going to have the preview URL video preview URL and this is thumbnail URL right so we're going to have these two but besides that we will also pass uh some more things here we're also going to pass the title which will be video. title now let's go inside of the
video thumbnail and let's modify the props so it accepts those as well so the title will be a required string we're going to have the preview URL which is going to have the same properties and now we can extract the title and the preview uh URL from here and now we're going to go ahead and do uh our little uh trick with the video thumbnail here so this is how we're going to do it we're going to give this whole element a group like this and then what we're going to do for this first image
here is the following let me just go ahead and collapse all of these elements here in here what I'm going to do is I'm going to set the when the group hover is active I'm going to set the opacity of this image to zero right so right now if I hover it disappears and what I'm going to do is I'm going to copy this image element this one will use the preview URL and it can also fall back to the placeholder that's fine and I'm just going to reverse the logic so when we hover this
will be fully visible and by default this one will not be uh visible so now when I hover it activates a gif so I can kind of preview what's going on you can see for example this one does not have a preview so it just falls back to our placeholder like something's wrong right because it does doesn't have uh the preview uh great and one more thing we need to do is the duration here we need to kind of show a little box here in the bottom showing uh exactly how long this video lasts so
let's go ahead and do that and also we can now change the alt to both of this to be the title like this there we go so let's go ahead inside of our schema first and we're going to go ahead and add the duration so after preview URL add duration which will be integer from drizzle PG core duration like this make sure you have imported the integer from uh DM PG core and now we're going to go back inside of our API for videos web hook and in the same place where we generate the thumbnail
and the preview URL uh we can now generate the duration so we're going to do on duration will be duration my apologies data. duration question mark math round data duration times a th000 because it uses milliunits to store the duration or if for any reason we can't load data duration we will just fall back to zero and passing the duration here there we go so now after a video has been processed we're also going to know exactly how long uh the video lasts so let's go ahead and go back inside of our video thumbnail here
and let's add a prop for the duration so the duration will always be required and what we're going to do is we're going to destructure the duration here and we're going to go ahead here at the bottom where we added the to-do and add a div here with a class name absolute bottom to right right to px1 py of5 rounded background color of black with an 80% opacity text white text extra small and font medium and then inside of here we're going to render the duration like this so let's go inside a videos section here
and let's render the duration to be video. duration now this will cause an error because duration can sometimes be uh not defined so we can just set it to be uh zero as default and now our uh let's just see the errors uh column duration does not exist uh did I push my changes I did not push my changes don't forget to push the changes uh so you have an error like I do let's refresh now there it go so now you can see that I have this weird zero number here so let's go ahead
and make this a little bit here uh by creating uh a format duration U so I'm going to go ahead inside of my lib inside of utils right here inside of utils uh oh I have to go inside my apologies I was already opened let's export cons format duration and let's accept the duration which is a type of number let's get the seconds to be math floor use the duration use the modulus operator with 60,000 inside and then uh divide that by a th let's get the minutes using a similar formula math floor duration divided
by 60,000 and then we can just return the string how it's supposed to look like minutes to string pad start two with zero in string as the second argument and then we're going to add a column and then we're going to represent the seconds so seconds the string Head Start two and zero as the string like this there we go so now go back inside of the video thumbnail and you can import format duration not from date FNS from our local uh util right make sure that's the one you import uh and now it should
have a nicer uh displayed duration but still as you can see uh this previous videos of ours don't have any duration so let's try this again I'm going to go ahead and upload uh and this time uh I should have a proper duration from that video asset ready web hook so now you can see I have no thumbnail here and the duration is 00 0 0 0 but if I refresh there we go I can see the duration of 10 seconds here because MOX limits the video to 10 seconds and I have the thumbnail and
I have the preview URL excellent so what we're going to do now to wrap up this chapter is the following let's keep track of everything we did we updated the video schema we pushed the database changes we handled the video asset ready event we assigned the thumbnail and the preview and now we have to handle uh these two right here and these two right here and we also also what I want to do is I want to wrap up the UI on this part right there's nothing we can do about uh a few of these
like we can't handle the views the comments and the likes but we can handle the status and the date and some additional information here so how about we do that let's go inside of the video's section here and what we're going to do outside of this div which en soles the video thumbnail let's add a new div which will have a class name Flex Flex column overflow hidden and GAP y of one and then inside of here a span which will render the video title so now we have the video title here but let's give
this video title a class name of text small and line clamp one in case it's too long it will collapse now you can go ahead and copy this span this one will have a video description though the description is optional so we are going to add a fold back here to no description like this this one will have text extra small and it will have text muted foreground there we go Untitled and no description great so now uh what we're going to do is we're going to find the table cell which says status and what
we're going to to do right here is the following we're going to add a div with a class name flex and items Center and inside we're going to render videos Mark status like this so now you can see that we have the status which says ready ready ready and this one says waiting because these ones never received uh their respective uh their respective video right we didn't have the web hook ready at that time yet but let's make this stat appear a bit nicer how about we go back inside of our utils here where we
added the format duration and let's add one more uh util called snake case to title the string it will accept will very simply be used for some regex replacement here so add a forward slore SLG representing Global go ahead and add a space in an empty string and then another replace forward slash backwards SLB backward slw forward SLG find the character and do character to uppercase like this and now that we have snake case to title we can go ahead and wrap this inside of that snake case to title like this make sure you have
imported a snake case to title from lib utils I'll just move it here uh do I have an error it seems like I have an error here so snake case to Title Max status which can be empty so I'm just going to f back this to be an error right in case we haven't managed to assign any max status at all something's obviously gone wrong uh but there we go now you can see we display a nice capitalized already and in case the status ends up being because the statuses are stored like this they will
be you know video ready or video error or asset ready so when we pass that through snake case to title it will transform to video ready right and we can just fall back to error in this case uh great we can also handle the date while we are here so let's go ahead and let's import format from date FNS we never installed this explicitly but I'm pretty sure it came uh from shatan UI because we have a calendar package or I don't know some other component which uses date FNS in case you don't have it
you can just do bun add or npm install date FNS once you get the format from date FNS you can go ahead and find the date here and you can just use format new date video created at and then pass in any format you prefer great and I'm going to leave it at this let's just go ahead and do one more thing so I'm going to give this table cell a class name of text small and truncate in case it goes uh too far uh but yeah I think these are all the information we currently
are able to uh give to the user uh and we can also uh store the visibility while we are here just so we can wrap up everything that uh currently you know our data allows us to do we can't do views comments or likes because that requires a whole another database entity but we can do visibility we just don't have it yet so let's go ahead and do visibility in order to do that we're going to go inside of our schema and we have to create above the videos an enum so export cons to video
visib ility PG Anum video visibility and you have to import pgum from PG core like like this and then just open up an array and add private or public like this and now what we're going to do is we're going to add the visibility here which will be video visibility visibility default will be private and it will be required like this and if you want to for the duration you could also do the same thing you could set the default here to be zero and also make it not null so it's kind of always required
if you want to do it that way uh I would not recommend doing the same thing for thumbnail and preview URL simply because uh we are using local relative paths here right we are using SL placeholder.svg so if you were to do inside of your schema here for example a default of that I'm not sure that's a good practice right I think you should aim at having it at least be hosted somewhere like this maybe but we don't even know what our domain is going to be or anything yet so I'm just not going to
do that and uh in my original source code I didn't do this either but you know if you want to explore for yourself maybe this is a solution you prefer rather than fall backing to zero here I think that's the solution I prefer as well so this is what I'm going to do I'm going to go ahead and see can I my dzal studio is not working all right so this is what I'm going to do I'm going to do bonex drizzle kit push and I probably will get some conflicts now because I have a
bunch of data here yeah it's throwing me some errors here uh the column duration of relation videos contains null values okay so this is what we're going to do then I'm not going to add the duration my apologies I'm not going to modify the duration uh I will just add the video visibility here but I think that might also throw some errors because my other video visibility does not exist oh no that's different because there are no conflicts right none of them have the video visibility so I think that all of them will now receive
uh video visibility let me just open my drizzle kit Studio nope like this so I think that all of my videos now should receive the video visibility let's see and all of them should be private there we go so all of them just got the visibility to be private so what I'm going to do now is the following I'm going to go ahead and select all of my videos and I'm going to delete them and uh you have to do this as well if you want to modify the duration if you're fine with you know
this solution okay you don't have to but let's go ahead and add a default of zero and not now so I pretty much always expect the duration uh to be something and let's go ahead and do this inside of our route for videos we also have to be careful then okay it's okay because we always fall back to zero in if something's wrong we will just fall back to zero that's fine uh it's just important that this can never be null because then this will error great so now let me just confirm inside of my
schema I have modified this to be not null so what I'm going to do is I'm going to do drizzle kit push so let's ensure that that will work there we go changes applied great and now let's go inside of the route and let's just wrap it up by adding uh some more events that we need to handle so a very simple one here will be if the video has errored so very similarly to case video asset ready after the break here we're going to have case uh my apologies here case video asset errored let
me just confirm did I do this correctly yeah okay we can also add space between this if that makes it easier for you to look at I really really prefer if Clauses over this all right so now we have the data element as well payload data as video asset errored and get the data inside uh we can go ahead and do the same thing that we did above if we have no upload ID we have no idea what video has actually errored so so no need to even try something and then let's just do a
way to database update the videos set max status to be data uh status here which will most likely be an error where equals videos upload Max upload ID is data upload ID and let's break great we have the video asset errored and now let's go ahead and let's do case video asset and I believe I did not add this one so video asset deleted web hook event so I'm going to go ahead and add this as well video asset deleted this is going to be particularly useful while you are in uh the free tier where
videos are deleted after 24 hours so you won't have mismatching data where you know one thing is deleted in MX but available in your database if something deleted if if after 24 hours the video gets deleted in MX because of the free tier it's also going to be deleted on your back end uh I mean your database so there are no weird States here so we can do the same thing as we just did above here we can I think I can copy this entire thing this will be not errored but it will be deleted
there we go and and then we can do await database delete uh what's the argument here videos where equals videos uh Max upload ID is equal to data upload ID it's not caml case it's Pascal case like this or snake case my apologies all right so that's if the video Acid has been deleted and I'd like to immediately try this one out I want to see if it's working so I'm going to do Bun Run uh Dev all here and I'm going to go ahead and have my assets here make sure you are inside of
the proper project and I really do want to clear them all out so what I'm going to do is I'm going to delete them one by one and since I recently deleted all the videos inside of my database I wouldn't be surprised if the web hooks are failing now because it should not be able to find any of these assets related right so let's see it looks like it did hit my web hook a lot of times no errors which is well I guess okay right because this basically just does its job it it cannot
find anything and that's it so let's see uh does this work or not I'm going to add a little conso log here console log deleting video upload ID will be data upload ID like this let's try out and let's add the equivalent uh creating video how we do that in the video asset created we're going to have creating video and we're going to have uh deleting video so let's go ahead here let's refresh this we should have no elements here because we clear them all out with our studio let me click create select files and
now I'm going to keep waiting here there we go creating video with this upload ID that's great I have my drizzle Studio open so I'm going to refresh the videos here and I think that now by default uh I should have yeah I should have refreshed sooner but uh the one thing we changed is that my duration was zero in the beginning but great we also have the visibility and the duration here the preview URL the thumbnail URL uh these two fields are still missing so we will do that as well but great we definitely
have this one video here and if I refresh I should see all the updated information here sometimes when the GIF is not loaded yet when you hover you're going to see like an invisible image because it's still loading down that gif so now let's go ahead inside of the assets here let's refresh as always ensure you're in the proper project and let's try and delete a video so this is something that should happen after 24 hours and I'm hoping that it will also you know uh uh fire a web hook as you can see this
works deleting video by upload ID and I think that if I refresh here there we go no video here perfect so we fully synchronized our assets on uh the MX and our assets for our database excellent that works and I'm seeing a little uh warning here the requested source which is our animated gift is an animated image so it will not be optimized consider adding the unoptimized property to image right away let's go ahead inside our video thumbnail this is the place where we use the preview URL and we're just going to add an optimized
and we can in fact only add it if we don't have the preview preview URL because otherwise it's just going to be a placeholder so if we don't uh have if we have the preview URL we will have mark this as unoptimized great and then we should no longer be receiving this warning here uh great that works and now uh the last thing we have to do is the track errors so we handle this we handle this we handle this we handle this these two are left now here's the trick this will only fire if
if uh you have audio inside of your uh inside of your videos and also if you enabled subtitles in the first place so we're going to try this now I'm going to go ahead and I'm going to I forgot a break here and I'm going to add video asset track ready so that's what I'm going to add uh I never know how to write this cases okay let's go ahead and get the data here there we go this will be video asset track ready web hook event uh we should do what we always do which
is check for the upload ID by oh the upload ID does not exist on type track okay so by this by the time the track is ready I'm pretty sure that the video asset has already been completed which which means that we have the asset ID stored so we no longer have to rely on the upload ID I think we can use the asset ID uh let's see what do I have here uh I'm going to have to take a peek at my source code to remind myself uh what is the proper way of solving
this okay so the asset ID comes from the following data asset ID but uh here's the trick it says that the asset ID does not exist on type track and I can see in my original source code I added a little comment saying that that's not true asset ID exists in here but for some reason uh they put a typescript object here which says that it doesn't exist so what you can do is the following you can add and asset string and you can add a little comment here typescript incorrectly says that asset ID does
not exist like this and let's also assign our track ID which will be data. ID let's also get our new status which will be data. status and then what we're going to do is in case we're missing the asset ID we're just going to throw an error missing asset ID uh great and now what we can do is we can go ahead uh and simply uh update the video so await database. update videos set max track ID will be our new track ID Max track status will be our status here like this where equals videos
Max asset ID matches the asset ID and break all right so now we should also handle this and I'm now going to add a console log here uh track ready the reason I'm adding this here is because I want to see this track fire but I'm 90% certain that it's not going to fire now I'm going to add some space here and I will upload a video here demo. MP4 so this video has audio but I think that since I've never enabled transcription it will never uh have the asset track ready keep in mind that
uh the asset track ready does take some time to fire it it's not as fast as our uh other uh web hooks so I'm going to give it some time but it's a 10-second video I'm pretty sure that already it should have been here so this is what we're going to do now uh if you actually go inside of your videos here and just look at your new asset you will see uh that it doesn't have the subtitles function here at all right so you have to you can re retroactively add subtitles but the proper
way of doing it from the start would be going back inside of our videos module our procedures here right here in the upload in the new asset settings so what we have to do here is we have to add below the playback policy add an input like this generated subtitles language code will be English and the name will be English so that's why I told you if you can find you know any English audio because then you will be able to have generated subtitles so now what happened is that my track uh ready event never
fired but I'm pretty sure that now when I add another video here it's going to transcribe the video so I'm going to have it uploaded and let's wait so it created the video and let's just wait for some time hopefully I did this correctly and there we go now we have track ready what does that mean well now if you go into your assets and select your proper one and go into the newest one and if you go ahead and click play this time you can see that you now have subtitles right I'm just going
to mute this so you don't have any audio feedback but you can see the subtitles here clear as day which means that the track is ready which in turn should mean that inside of my drizzle Studio I should now have two records one which doesn't have the track and the other one which has the track there we go one has the track status as ready and the other one does not have it at all perfect so that is it for the web hooks uh I will come back a bit to this track status I mean
this track ID this is something that we will need but the track status it's not exactly something that we need other than displaying to the user like hey uh the captions will come in a moment they are still being processed but if you upload a video uh which has no subtitles in that case I'm pretty sure that this event will never fight right so we will explore if we can improve the behavior on that part so let's just wrap it up by properly displaying the visibility since both of them now have their visibility so we
can go back to our videos section for that and let's find the visibility table cell let's add a div with a class name flex and items Center If Video visibility is private in that case we're going to render lock icon from Lucid react and give it a class name of size 4 andmr of two otherwise we're going to render a globe 2 icon from Lucid react with the same class name and then below that we can use Snak case to title video visibility even though we know there's only two options but why don't we reuse
our util here make sure you have added the lock icon and the globe icon from react here so now there we go you should see this saying private or uh if it's public it will be uh have a globe icon and it will say uh uh public great so we now have the nice preview we even have subtitles which is not something we can see yet we can see the duration of the video we have completely synchronized uh what's going on you know with our uh 24-hour deletion uh basically everything is now synchronized in a
nice way but still keep in mind you know you might forget that the videos are deleted after 24 hours so always have a small number of videos like one or two so you can easily detect that don't have like 50 videos and then you try and demo it to someone and everything's broken because the videos have been deleted but keep in mind you can if you have the ability to add a credit card you can just click upgrade make sure you select the pay as you go here uh and you will be able to finish
this tutorial completely for free you know I didn't manage to spend a single scent and you will have full benefits right you will have everything unlocked I think that's simpler uh if this is causing any problems for you great I think we can now finally check two of this as well great we finished all the max web hooks that we need and what we can do in the next part is we can create a proper uh form and some video player so we can actually see this video in our app great great job in this
chapter our goal is to create a video form we're going to use this video form to edit the title and the description of a video and later on also modify the category visibility thumbnail and a use AI to generate the description or the title we will also have this little drop down here which will be used uh when we want to delete a video right and inside of this place right here we're going to have a video player let's start by adding the skeleton to our previously created videos section simply so we don't forget that
because right now we only have this loading State here so I'm going to go back inside of my videos section it's actually right here inside of the studio module and I'm going to go ahead and create videos section skeleton component this skeleton component will have a fragment and then a div inside of here I'm going to add border Y and then I'm going to copy the beginning of my table which means up to the table body so I will copy it like this and then I have to add a closing tag for uh the table
body and for the table itself and now I should have uh no errors in here I believe let's see if this is correct uh I have to indent this all right so basically uh what I want to do now is I want to add this to my suspense for back now when I do a hard refresh I should at least see as you can see the the table here matching and now let's just add some simple skeletons for the data here so for the data we're going to go inside of the table body and we're
simply going to use the array from length five or you know whatever you think looks good skip the first argument get the index and then let's add table row here we're going to use the index as the key let's add a table cell here with a class name PL of six let's add a div here with a class name of flex it Center and GAP four and then let's add a skeleton component you can import the skeleton component from components UI skeleton like this and let's give this skeleton here a class name of height 20
and width of 36 so now there we go you can see how we have a nice skeleton which kind of represents uh our our thumbnail here uh great and now what I want to do is I want to add additional items here so below this skeleton let's add a div with a class name here Flex Flex column and GAP two and inside we're just going to add two skeletons like this one with a height of four one with a height of three and some different widths for them so now they will represent as you can
see the title uh and the description here uh great and if you want you know you can leave it uh like this but let's go ahead and let's just add some dummy skeletons for the rest of our table cells so open a new table cell here and let's just see so we have video visibility status date views comments likes so let's add visibility here then after visibility we have status then we have the date views comments and likes so three more views comments and likes okay so now we filled all the available uh properties here
and now we can just add some skeletons inside so for this one I will add a skeleton with a class name height four and the width of 20 for the second one the status I'm just going to add another random one with the width of 16 then for the date one I'm going to add one with a width of 24 uh then for My Views I'm just going to add a random one with ML AO and I'm also going to give this table cell text right and I will copy that and add it to the
last two here uh and you can actually copy the skeleton as well for the comment and the likes here there we go so now you should have a nicer skeleton let's go ahead and just add these elements text right uh to my actual elements here which I believe are views comments and likes so all of these should have whoops class name of text right and let's give them text small as well there we go so all of them should be kind of uh in the end here except the last one which will uh have PR
of six here as well there we go like that uh so how about we add pr6 here as well pr6 I'm just trying to match my skeleton as much as possible for so I'm choosing the last place here which is the likes right and I think I'm missing P6 uh in this place right here so how about we go and find the table cell and give the table cell a class name of pl6 and now there we go this matches exactly uh what we have there uh great and we already did pl6 I believe here
in the table cell for the skeleton perfect so now we're going to go ahead and develop the page which opens once you actually click on one of the videos so that's SL Studio slash videos and then the video ID if you click right now you will get a 404 so let's go ahead inside of source inside of the app folder studio studio let's go ahead and create our videos and then a dynamic video ID like this and inside a page. DS X let's return this page video ID like this now when you refresh you should
see video ID text here and every time you click on one of these videos uh you should see video ID great so in order uh to actually develop this page uh we are going to have to create a proper uh prefetch method here so let's go back inside of Studio proc procedures here and we're going to develop a get one method so we have get many protected procedure and now we're going to call get one why am I adding this inside of uh Studio router procedures why not inside of video procedures simply for the reason
that usually inside of my videos procedures uh anyone will be able to visit a video right but this one I want it only to be visible by the outro video so then we have separate routers and in one place we can show much more info than we would in another so that's why I decided to use it here uh so the input here is very simply going to be an ID of the video which is you know I put uu ID here simply because I know that in my schema I use video uu ID in
your case you can just put Z do string it's going to work just fine depends on how precise you want to be uh so we have z. object set here and then let's add the query so now let me just collapse this elements here input and then the query and this will be an asynchronous method from here we can D structure the context and the input like this and let's go ahead and obtain from context. user which is our database user if you remember ID user ID and from the input we can the structure uh
the ID there we go so let's go ahead and let's fetch our video which will be await database do select and passing from videos inner join uh my apologies no need for an inner joint here we're just going to pass in where and then we're going to add end because we need two things to happen the first one in our end Clause here will be that the videos. ID are equal to the to the ID which we have from the input here and the second one will be that the video's user ID is equal to
the current user ID so that way uh this video that we load must belong to the user let's return back the record uh my apolog is the video in case there is no video we can go ahead and throw new prpc error which we can import from the server passing the code not found there we go so we now have our get one procedure here so how about go we go inside of the page here and we prefetch that so let's mark this as an asynchronous page page let's not forget to expert con Dynamic Force
dynamic because uh we don't actually await anything we just prefetch so no nextjs doesn't know that we are actually uh doing some fetch calls here which cannot be static let's create the props so we can add the proper param params from here like this page props params let's go ahead and let's await video ID from await params and now inside of here get one we can simply pass in the ID to be video ID and there we go we successfully uh prefetched our video here great so what we have to do now is we have
to hydrate the clients so let's add hydrate client from the RPC server like this and then we have to create a video view like this and we have to pass in the video ID to be video ID from the par great let's go ahead and let's create the video uh view so I'm going to go ahead inside of my modules here and I'm going to say Studio UI view oh this should be called views so we are going to resolve that but let's first create the video view. DSX and inside of here let's just add
page props Here video ID will be a string export const video view video ID page props so these will already be awaited so no need to do it here again and let's just return a div with a class name PX off four padding top of 2.5 and a maximum screen length of uh LG and we are not going to add MX AO usually we add that but this time I don't want it to work like that and let's just render the video ID inside then uh let's go ahead and go back inside of our page
in the app folder here and let's import the video view from modulus Studio UI view video view so now when refresh you should see the ID uh of each of the uh content that you press on great so that works now uh let's go ahead and let's continue developing but I just want to rename my UI folder view into views and if it asks you to updated the Imports you can just say yes and let me see what did it update now all right I'm trying to find some places oh okay so this is the
place it updated instead of source app studio studio uh page. DSX where you import the studio view just modified to be views in case you have the same mistake as me right the place where you prefetch for Studio get many uh great so we now have that now let's go ahead and let's continue developing inside of the actual uh video my apologies views video view here so we're going to create a new section which will be called form section. TSX and inste of this form section we're going to mark it as use client expert const
form section the form section will have uh page props right you can call it form section props however you want it really doesn't matter so we only need the video ID here and basically what we're going to do is what we always do we're going to go ahead and get this one video by using trpc from the client following the studio get one use suspense query and pass in the video ID my apologies ID video ID there we go and let's go ahead and return div Json string ify video there we go so now I'm
going to go ahead and render the V the form view my apologies for Section component from dash dash sections and passing the video ID here there we go so now you should be able to see some more information inside of a Json uh in the page here so now let's go ahead and do what we always do inside of our sections where we fetch something and that is to not export this and rename this to suspense and then export con form section which will call the suspense and error boundary form react error boundary and then
form section suspense here and we have to do the prop passing so let's go ahead and do that and passing the video ID video ID the full back here will be an error and the F back here will be form section skeleton let's just fix this typo here like this and con form section skeleton for now can just be a paragraph loading and let's not forget to return this there we go so that should now be working just fine uh now when I refresh it says loading for a brief second and then it shows this
but we seem to be having an error switch to client side rendering because the server rendering uh errored unauthorized this would indicate that we made a mistake in our prefetching rules it would mean that uh we are fetching something incorrectly let's go ahead and confirm so we are calling tipc Studio get one we are calling use expense query with the ID of video ID and inside of our page here in the studio videos video ID page TPC Studio get one oh yes we are not prefetching there we go so now we have no errors now
we are doing this exactly as we are supposed to do perfect so that was the issue you can see how careful you have to be that's why again you know I keep bringing us back to this steer pieces setup uh where I told you that we have to be careful you know with how we prefetch and everything and I showed you that example uh of what the creators of react query have planned to kind of give you some strict rules about that that's why it will be that useful when it comes out all right so
just make sure you're actually doing the prefetch here and then you will not have any errors in this page perfect so now that we have this let's go ahead and let's create a little div here with a class name of flex items Center justify between and margin bottom of six and inside of here I'm going to add another div which will simply hold my Heading Video details and it will hold my description manage your video details and I will change this from an H1 element to a paragraph there we go video details and manage your
video details let's give the heading element a class name of text to Excel and font bold and let's give the paragraph a text extra small and text muted foreground there we go that looks nice now let's go ahead uh outside of this div and add a new div with a class name Flex items Center and GAP X of two and let's go ahead and add a button from components UI button so make sure you have added that which will say save this will be a type of submit and let's go ahead and give it a
disabled of false simply so we remember later on that this should probably be controlled by something great now we have a save button here and here's how that's going to look on WID screen we are not going to center it we're just going to leave it in this part this is the same solution that the actual uh us YouTube uh uses YouTube Studio uses great and now what I want to do is I want to add a little drop- down menu right next to our button so let's just uh import everything we need from the
drop down menu drop down menu the content item and the trigger so now we're going to go next to this button here and we're going to go ahead and render the drop- down menu so drop down menu drop down menu trigger as child and let's give it the button the as child basically means that this component will become its child right otherwise this will be a button and then this will be another button inside which breaks the hydration rules give this a variant of ghost and a size of Icon and add more vertical icon here
you can import this from Lucid react I will move it here now that we have this let's go outside of the drop down menu trigger and add a drop down menu content here with AN line of start and add a drop- down menu item and add inside a trash icon from Lucid react with a class name of size 4 and Mr of two and then simply add delete there we go so now next to this you should have a little uh drop down here and let me just see can we maybe push it so it's
here to the side we added an align start how would I add side left maybe okay so that works like that not exactly what I imagined align end maybe and leave the side alone there we go this is what I wanted I I don't want it to be in this place perfect so we have that ready and now let's go ahead and let's actually add our form uh so that we can actually load the details of our video inside of a form so in order to do that we are first going to have to uh
import from react hook form so let's go ahead and add use form from react hook form we already have this installed thanks to shat CN UI uh in case you don't let me show you my package Json here react hook for then let's go ahead let's add Zod resolver which is again the same thing that we got when we installed everything from shaten because when you add form component from shaten it will install all of these things for you excellent so we have that uh and now we are going to need uh an input from
components UI input in order to display our text we will need a text area in order to display description and we are going to need all these other elements from form so from components UI form form form control field label message and item and we are also going to need everything from select select select content item trigger and value great now let's go ahead and let's properly develop this so we're going to start by establishing our form inside of a constant here so const form will be useful form like this and we are going to
pass in the default values to be video like this now the issue is that this form is currently not strictly typed exactly so it does use use form return but it has no idea what we will actually be expecting uh to send to our create request or in this case our uh video update request so usually what we would do is would create a form schema using Zod but if you remember we actually have our schema here so what we can do is we can leverage something called drizzle Zod and then we can uh extend
an export from this schema let's go ahead and do that so I'm going to do BN add drizzle Zod you don't have to immediately type it out Simply so you can see the version there we go so this is my version so bun add drizzle Zod at 0.7.0 if you want the exact same version and let's go ahead and let's import this instead of our schema. TS here at the top I'm going to import from drizzle Zod create insert schema create select schema and create update schema and then I'm going to go ahead and find
my videos here and at the end of the videos I'm going to go ahead uh and just write out the insert all of the schemas right so export con video insert schema will be create insert schema and pass in the videos export cons video update schema will be create update schema from my videos and we will have export con video select schema just in case we ever need it right it doesn't hurt to have one and passing the videos so that's how we can reuse our schema all the way here uh from the schema place
and then let's go back inside of our form section here and we still have to import Zod but we will not use it to build any form schema from scratch instead we're just going to use it to infer so let's go ahead and give this a type of z. infer type of video update schema which you can get from database schema I will just move it here there we go so video update schema perfect and then you have to add a resolver to be Zod resolver we added an import for that and again add video
update schema here there we go no more errors in here now this perfectly matches the type of video update schema now let's add our onsubmit method to be an asynchronous method with data of z. infer type of video update schema and conso log uh the data here there we go so now our data will be a proper type which will be expected in our trpc create method and it will also validate the form great so now that we have this let's go ahead and actually build the UI elements so we're going to have to wrap
this entire thing inside of a form element which we've added an import for uh a few minutes ago and inside of this form element we have to spread the form which we have created right here like this and then we have to wrap the entire thing again but instead of a native HTML form element so wrap the entire thing and indent like this and now this type submit makes sense because it will call this form but we still have to give this form a un submit in this case form handle submit and then on submit
handle submit will take care of validation and once the validation passes only then will it call on submit with our proper data inside so we can safely pass it to our server so this is going to be be first class uh client side validation as well as uh back inside validation great so we now have this uh let's go ahead and let's add our first uh well our first um element right so this is how we're going to do that we're going to create a div and we're going to create a grid on mobile this
will simply be a single column but on large devices we're going to turn this into group calls five and let's give it a gap of six like this then we're going to have a First Column which will have space y of eight and on LG a call span of three basically what we're doing is the following we're separating these two into columns this part right here oh it's this color okay this part right here and this part right here this will be column number one and this will be column number two right that's what we
doing on mobile they will just go one below another great so let's go ahead and continue developing the First Column which will actually hold our Fields so let's add form field which we've recently imported it's a self- closing tag which takes in control of form. control it takes in a name which basically tells you what uh field this is going to control so it's going to be title and then we need the mandatory render method inside of this you can immediately uh destructure the field and then you can go ahead and return a form item
the form item will have a form label and inside of here we're going to go ahead and simply write title for now and I'm going to add Tod do add AI generate button below the form label let's go ahead and let's add form control like this and let's add an input which is a self closing tag and simply spread everything from field here the way this works is that this field uh automatically has all the necessary events let me find them uh is it here yes onchange on blur value disabl name so all of those
things will automatically be passed as props here so that's why it's very useful build forms in this way let's give this a placeholder of add a title to your video and let's give it a class name of pr10 actually no we don't need this class name great and let's add a form message here so we can properly render out any UI errors from here and already you should be seeing a loaded name of your video uh right now all of them are Untitled if you want to you can go ahead and go inside of your
drizzle key kit Studio make sure you refresh so you see most up to-date data and change one to hello and click save change and then go back to your content refresh and one of them should say hello in the title and the other one should say Untitled great so we now have this now let's do the same thing but for description so I'm going to copy this entire form field here and I will paste it below this form field will control the descript destion and it will have a form label of description like this and
instead of using an input we will be using a text area component and we're going to go ahead and overwrite the value so we are going to check if value exists then render the value otherwise an empty string because description is not required we're going to limit the number of rows to 10 so it doesn't look too big nor too small and we're going to give it a class name of resize none and PR of 10 and we're going to change the placeholder to be add a description to your video and by default both of
these should be empty because none of our videos actually have any description great so now what we're going to do is we're going to create uh a different type of form which will be our select form so let's go ahead and copy the form field again and this is what I'm going to do before I paste I'm going to have to do add thumbnail uh field here we don't we're not going to develop that in this chapter we're going to develop it some other time but yeah in between uh these two it's going to have
a thumbnail field so now I just pasted this copied description form field and I will rename this one to category ID like this this one will not have the special AI button and the label will be category it's not going to have the text area instead this is how it's going to work we're going to wrap our entire form control uh within select like this the select itself will have an onal change to call field on change and default value will be field value or undefined then inside of form control we're going to have select
trigger with select value with a placeholder select a category it's a self- closing tag and then outside of form control we're going to have select content and then select item and for example let's say uh something and let's give this a value of something and now you should have the category right here but one thing that we don't have are the actual categories we forgot to load them and one thing we can do is we can prefetch them alongside our user so I believe that you already have everything necessary if I go inside of my
modules categories here we have get many that's it that's all we need so let's go ahead and do this inside of your app studio uh videos video ID page alongside prefetching the video let's go ahead and prefetch categories there we go so now let's go head back inside of our uh individual form section and we're just going to do the same thing so we're going to go ahead and prefetch the categories from trpc categories get many use suspense query and nothing inside now there you Pro noticing something our entire loading skeleton will now wait until
both of this are loaded so technically if you want to you can separate this entire form field for the category into its own component and then add its own suspense and error boundary if you want to because yes technically If Video is already loaded we will still wait for this to be loaded so not perfect but I think as long as I give give you an explanation you know to what's going on you can decide for yourself if you want to modify it later but for the sake of Simplicity I'm just going to continue developing
like this now so now I'm going to modify this uh to actually iterate over the categories so category. matat we're going to get the individual category and we're going to render the select item and then we're going to go ahead and pass in the key to be category ID and the value to be the same and inside of here category name there we go so now you should have the ability to choose different categories for this video as well great uh so let's go ahead and stop here simply so we can go ahead and actually
submit the form and see some changes so in order to submit the form we have to go ahead uh and find and develop our update procedure so let's go instead of source modules videos you uh server procedures we're going to put it here uh because we have the create method here so let's go ahead and add the update method here protected procedure and let's go ahead and let's add an input here the input will be video update schema from database schema so we can share that as well and we are calling a mutation so you
can see how we have fully synchronized our front end uh form validation with our backend form validation I really like how easy it is to do that uh with trpc and with use form and now let's go ahead and let's distractor the user ID from Context user in case we are actually we don't need to check for that uh this is fine let's go ahead and do the following let's first find the video by using await database actually we can just do this updated video await database select uh update set of course passing the videos
here and we're going to Simply set inside uh we could just spread you know the entire uh input but I want to be a bit specific with what I want to allow the user to update because this is an API endpoint but I want to control what's possible to update so I will allow the title I will allow the description I will allow the category ID and for the visibility I'm not going to allow the well I will allow the user to change this but only uh if video max status well yeah H let's go
ahead and let's allow the visibility as well I I I I was thinking about you know only allowing the user to change the visibility to public if the video has finished processing but now remember YouTube doesn't do that YouTube allows you to publish a video even if it's processing and then the viewers will simply see a text saying hey this video is processing so let's do it that way no need to complicate in that case and we have to add new date for our updated at and now let's just add aware it has to use
end we're going to have two equals here looks like we have to import both and from drizzle orm and equals from drizzle orm so I will move that to the top here and the first one will be checking if our videos ID are matching the input ID and the second one will be if video's user ID match the user ID which we got from this protected procedure and let's get back returning in case we don't have uh updated video we're going to throw new trpc error from trpc server code not found like this and it
seems like I have an error in my update procedure here input ID uh seems to be able to be undefined well that's not exactly true right because I mean I'm not sure I think that we are able to like modify this and to tell it that ID should be required but for now let's just do if we are missing input ID throw new trpc error code bad request like this there we go no more errors now perfect so we now have a working update procedure so we can go back inside of our form section here
and inside of our onsubmit method we can now go ahead and uh add that so first let's just add our update here H update is trpc videos update use mutation like this and then instead of here what you can do is you can first of all uh just await update mutate asynchronous and passing the data uh of course you don't even have to make this asynchronous you can just pass in mutate normally but then uh you will lose the fact that the form field has form State and you can read is submitting from here right
so if you want to use that field you have to mark this as asynchronous so depending on how you want to do it right if you want it you can use it like this and then you can simply use update is pending right basically something to just disable the submit button I actually prefer using the update is pending over this one so I'm going to do it like this uh great this should now work fine let's go ahead and let's just disable uh this button if update is pending like this and there are some things
that we should do you know after uh we successfully update our video so let's go ahead and do the following in here on success what I'm going to do is I'm going to use the utils on utils uh TPC sorry use utils TPC utils use utils okay so I'm going to add prpc mys but the one I want to invalidate is Studio get many do invalidate uh Studio why is it not working uh Studio get man this expression oh I should not call it so I just want to invalidate it and utils Studio get one
invalidate specifically this one right so no need need to lose cash for all the other ones we already loaded on error here you can decide what you want to do if you want to display the error message I think I've shown you how in the previous one of the previous chapters but I'm just going to bring back a generic error message instead um you know sometimes it's best not to show the user all the information that's coming from your back end especially if they're malicious but uh yeah with the RPC you know you can rely
on the error here so you can use error uh message if you want to great so we have the this here so how about I change hello to hello 1 to3 and click save uh and go back here there we go hello one two three perfect how about I do this and click save and refresh and go back looks like it's saving great and I will just copy this here tost success and this one will say uh video updated great so now I should also be able to change the category I believe there we go
video updated uh perfect so all of this seems to be working just fine and now we have to build the second column here uh which will display our our video player right so we can see the current thumbnail and also give us a form to change the visibility so let's go ahead back and now let's build this second column so I'm going to go down where we have I believe the last element here I think this uh is the div where the next column starts sorry I have to look at my source code because it's
a bit hard I sometimes lose focus where I'm at it's a lot of lines here I'm pretty sure this is where it ends so you can go to the bottom of your file find where the main form ends when this ends and then before you end this div but after you end this div start a new column with a class name of flex Flex column Gap y of eight and on large call span two so now if I add a little paragraph here hello it should appear right next to my form but if I go
into Mobile mode uh it should just break down great so you should also see it right next here here so let's go ahead and add a div here with a class name Flex Flex column gap for background of F9 F9 F9 so kind of a gray color rounded extra large overflow hidden and height of fit and inside of here class name aspect video overflow hidden and relative and inside of here a video player video player does not exist yet but we are going to create it in a moment it's going to have two props playback
ID which is video MOX playback ID and poster URL which is video thumbnail URL like this actually we and rename this the thumbnail URL why wouldn't we right it's our component let's go ahead and create the video player component so I'm going to create that inside of the video video videos module so inside of UI components I'm going to have a video player. TSX like this and I'm going to start by creating an interface for video player props the playback ID will be optional string null or undefined same thing for not poster URL but thumbnail
URL we will also have outop play so that we can decide whether we want to do that or not and we are also going to have uh this onplay method uh here because later on that's how we're going to count the views after the video plays not just by a website visit so let's go ahead and Export const video player let's go ahead and use this props here and let's go ahead and get all of them then I just have to properly name my poster URL to thumbnail URL and in case we don't have the
playback ID we can just return null I believe the video won't even be able to load uh in that case so what we have to install now is we have to install Max Max player so let's go ahead and do that bun add at maxm maxplayer react you're going to see my version in a second so if you want to you can wait to see the exact version that I have in that case that's 3.2.4 so you could do something uh like this BN add Max MOX player at 3.2.4 great now that we have the
video player let's go ahead and let's import the video player there we go let's go ahead and Mark this as use client and let's go ahead and simply return the MOX player MOX player it's a self-closing tag let's give it the playback ID of playback ID poster of thumbnail URL or/ placeholder SVG player init time zero this helps with hydration if you don't set this you will get some hydration errors autop playay simply to match our autoplay Boolean thumbnail Time Zero class name full width full height and object contain accent color uh for this one
uh you can basically choose what your like progress bar will be the color of so for example how about we do this one ff20 uh 56 and let's pass in on play to be on Play There we go so now let's go inside of the form section and let's import the video player from modules video UI components video player and let's refresh and let's see if we have any errors or was this just you know a single thing that happened here looks like it's taking some time uh let me try and speed it up by
closing this and running B run Dev all perhaps that will uh be able to make it load faster on this refresh and there we go we have now have a very nice uh video player here I will mute myself I mean mute the video just so we don't have double audio but we can finally preview our player here we have captions we have everything here looks very very nice uh great so now what we have to do is we have to continue developing this section uh and we have to add some more information like the
video link uh the status of some things uh and uh we have to add the form to change the visibility so we are back in the form section let's go all the way down to where we rendered the video player and now what we're going to do is below the video player outside of its div we're going to add a new div with a class name PX4 and py4 so basically just padding four yeah Flex Flex column and get y of six now we're going to go ahead and add another div so we are just
doing proper positioning now so justify between item Center and GAP X2 and let's go ahead and have another div here with a class name Flex Flex column and GAP y1 and finally a paragraph video link and let's give this a class name of text muted foreground and text extra small so now we should have a video link button I mean text here and then inside of here we're going to add a div with a class name Flex item Center and GAP X of two and this will use an actual link from next link so just
make sure you've added an import for that here there we go so an actual link from next link with an HRA which will go to slash videos slide. like this and inside a paragraph and for now we can just say you know uh HTTP Local Host 3001 123 let's give this a class name of line clamp one text small and text blue 500 there we go so we can now see our mock URL link here and what I want to do is I want to add a little uh button next to this link which will
have the copy icon from lucd react make sure you have added this and it's going to be a type of button this is very important otherwise is going to default to submitting it's going to have a variant of ghost a size of Icon a class name of shrink Z on click will be an m key Arrow function for now and disabled will be false for now so there we go you now have a nice little uh copy button here and now let's actually uh enable it so this is what we're going to do now let's
go before we return here and let's do const full URL is going to go ahead and open backx and we're going to use either process. environment. verell URL or HTTP whoops HTTP Local Host 3000 and then we're going to go to slash videos and then video ID so that's the full URL I'm going to add a little uh to-do here change if deploying outside of versel so for this tutorial we will be deploying on ourselves so this will work but you know add a note here just to use a different environment variable uh like next
public app URL which you're then going to change in your environment variables later on all right and now let's go ahead and let's add a little State here from use state from react so is copied and set is copied and now let's go ahead and actually uh use them so I'm going to create const on copy like this and I will simply do await actually let's just do Navigator do clipboard write text and pass in full URL let's make it asynchronous simply so we can do this whoops this is actually a promise so you can
await it set is copied to be true and then set timeout set is copied to be false and do that after two seconds and now let's go back to our button here add that on copy if is copied disabled it and then render icons dynamically if is copied we can render copy check icon otherwise the normal copy icon make sure you have added the copy check icon so now when you click this you will get confirmation that it's copied there we go and you can replace uh this hardcoded with actual full URL there we go
so now I can click copy here and when I paste you can see that it's copied great so we have that feature uh now let's go ahead and let's actually uh develop the rest which is showing us uh the video status right so after this button outside of this div outside of this div and outside of this div right here we're going to add a new div and just say hello test there we go so it should be you know underneath here and let's give this a class name Flex justify between items Center another div
side with a class name of flex Flex column Gap y of one a parag graph saying video status and a class name text muted foreground and text extra small and then a paragraph here snake case to title video max status Max status can be empty so let's default to Preparing and let's give this a class name of text small import snake case to title from our lib utils and I will just move it here so now you should see the video status here as well and then if you want to you can do the same
thing uh so just copy this entire thing and you can choose track status right or subtitles status and just use track status here uh Max track status and you can default to no audio like this so by by default you know it's going to say no audio or no subtitles maybe better like this so some other video for example uh will say no subtitles in case you made this video before you enabled the uh subtitle generation or before you actually uh or if the video doesn't have any audio right so this will be our default
State and then then the web hook will simply update the status to ready great so we've actually wrapped that part up uh what we can do now is we can go ahead and add the last element here it's going to be easier for us to know where it goes if we go from the back so where the form ends where the inner form ends where this div ends where this div ends and then just in between those two right so let's add another form field here and what I'm going to do is I'm just going
to copy the last form field which we've created here for the category ID I'm just going to copy the entire thing because it will be similar so let me just copy that and add it here so now you should have category here as well we're going to change this to uh control our visibility like that and we're going to change the label to be visibility as well select visibility and we're very simply uh not going to iterate over anything because we know that there are only two values here so the first value here will be
uh public and we're just going to say public and the second one will be private and the value will be private and if you want to you can add a globe to icon from Lucid react with a class name size 4 Mr of two and in here you can add a lock icon so just make sure uh you added all of these imports from Lucid react so there we go looks like it's broken okay let's go down and let's just fix it so I believe that both of these need a div with a class name
Flex items Center and we have to do the same thing here and just end the div there we go perfect so by default all videos will be private and then you can change this to public and click save and it should work if I go back here there we go this one is public this one has changed description and the title uh perfect and let's go ahead and try out our status if they work uh so yeah how you know click this and go inside you can see how it's going to look for a video
that's obviously you know broken so this is how it's going to look like and you can try out what happens if you like remove this if you do this I'm not sure if we get an error yeah so this happens so if you want to you can fall back to this if you think it's better than an empty space looks like like there are no errors in the console so I think it's okay to do that yeah if you want you can comment this out and instead fall back this because then it will always render
the video player it will just mean that the video uh is not actually something we can load right great uh so this now works and let's just go ahead and add uh the delete functionality and I also want to do one more thing which is uh I want to go back to my studio upload model we have an on success which we never actually uh use so how about we go ahead and use it now so on success the following will happen const on success first things first we're going to check if we don't have
data video ID and we can simply break this method looks like it cannot find data uh H how can it not find data oh I mean create. data video ID my apologies that's what I U meant in that case let's go ahead and let's reinv validate some things so Studio get many actually it's already doing that here so no need to re invalidate get many but what we can do uh is uh actually no no need to do any of those yeah let's just call reset here sorry create. reset and then let's do router which
we have to add use router from next navigation to go to slash push slash Studio videos create data video. ID basically our newly created form route right once the video has been uploaded that's where we're going to redirect the user to so let's try out now if I click create I have a model I will upload this video and I'm redirected here and it says waiting and no subtitles and if I refresh there we go now it says ready but still no subtitles but if I refresh again subtitles are now ready so we can see
how web hooks are coming one after another uh perfect so that works I think we finished our form obviously there are some more things to do we have to enable thumbnail uploads and then we have to enable uh background jobs which we are going to use to generate the title and the description of the video based on our subtitles so those are only going to be available if we have subtitles uh great amazing amazing job and I think we can use you know this chapter just to wrap up the delete method because it's very easy
I don't want to leave it for something else this is a long chapter it's been over an hour already but let's go ahead and go inside of modules let's go inside of videos UI a mology server procedures and we're just going to add delete here again protected procedure input and inside of here we actually only need one thing which will be the ID z. string uid let's import Z from Zod now let's add a query it's going to be an asynchronous query we can immediately destructure the context and the input let's go ahead and get
the user the ID user ID from Context user so we know which user is trying to do this and then let's do HST deleted uh yeah here's the thing I don't like using delete here simply because delete is a reserved keyword so I will be using remove so this will be removed video await database delete videos where and then we will have to use our end and two equals inside first one we'll check if the video's ID equals the input ID and the second one if the video's user ID equals the user ID which we
have and let's add returning here if there is no removed video that means that we can throw new trpc error here with code not found otherwise we can return back the uh removed video Simply so the API uh and user know knows which video was removed and we can do the same thing in the updated procedure return back the updated video there we go great so we now have the remove procedure and it's protected to only allow us to remove a video which we own so let's go inside of our form section let's go ahead
and let's add it the same way we added update so I will just copy this this will be called remove drpc videos remove on success no need to well we can invalidate yeah let's not invalidate this one it's just going to say video removed we are going to invalidate many and then uh we are going to redirect the user away from here so let's get our router import this from next navigation like this we have the router and we're just going to do router. push the user back to Studio on error something went wrong great
uh use mutation oh procedures did I do something wrong oh I did do query it should be mutation there we go so now use mutation should have no error and now let's use this inside of our drop down here on click remove uh remove. new date and we have the pass in the video the ID to be video ID there we go so now it should work just fine if I go ahead and decide to delete this there we go video has been removed and I should be able to delete this there we go perfect
so I think we did a very good job in this chapter let's get our hand the check mark here we added the skeleton created the form page created the video player and added the ability to update the title description the category and the visibility and even more than that amazing amazing job in this chapter our goal is to create video thumbnails which we can actually upload so far we've only had thumbnails which were generated using the playback ID coming from MX web hook we're going to use upload thing as our upload service and this is
the UI component which we are going to develop so this component will be right here above the category and once clicked it will give the user options to either upload a thumbnail or to restore the thumbnail to its initial uh initial uh uh state which was the Max uh web hook one and we're also then going to refactor our thumbnail fields in the schema so that we can have a proper upload thing cleanup let's start by integrating upload thing so head to upload thing.com and go ahead and create an account once your account has been
created let's create our first application here I'm going to call this application new tube like this and I'm going to select the free tier I'm not going to fill any of these options I will just click create app there we go now let's go ahead and let's click on API Keys here so it looks like we only have one key upload thing token so I'm going to go ahead and click copy here I'm then going to go ahead inside of my environment. loal and I will add it here as always don't share sh this with
anyone now let's go ahead and let's click on uh the docks here so we can see the proper way to add this to our project I'm going to select nextjs app router and I'm going to follow the instructions for bun so we have to add upload thing and upload thing react I'm going to go ahead and add these two and then I'm going to show you the versions so 7.4.4 for upload thing and 7.1.5 for upload thing/ react we already added the environment variable so that's fine now let's go ahead and let's set up a
file router so we're going to go ahead and create inside of the API folder upload thing and then core.ts let's go ahead inside of source app folder API new folder up upload thing and then inside core.ts and paste the file inside inside of here you will have a very basic mock authentication method as well as the basic image uploader router here we will later modify this entire thing but we can leave it like this for now and you can ignore the type error here after you've established the core.ts file let's go ahead and create a
route. DS so I'm going to go inside of upload thing and add route. DS and add it here inside inside of here we are importing our file router from core and we are assigning it right here let's save this file and now let's go ahead and let's create the upload thing components so I'm going to go ahead and copy this and you can choose where you want to put this let's put it inside of source utils upload thing simply so we stay true to the documentation you can of course move it to your place of
preference later let's go ahead and add upload thing. TS instead of our lib folder and let's put it here we now have to modify this to use an add sign because that's the Alias that we use there we go we now have upload the button and upload Drop Zone which has strict types to our file router here so later when we Define thumbnail uploader we will know exactly what we have to pass from the front end here and I think that that's it all we have to do now is add this wrapper around our Tailwind
config and I can see that they have actually added Tailwind version 4 here which is interesting because Tailwind version 4 just came out they are very fast so in our case we are using still uh Tailwind three I'm not sure if can we see that anywhere um Tailwind yes Tailwind CSS 3 so we have the config file here so this is what we have to do we have to go inside of Tailwind do config dods we have to add with uh upload thing and then we have to export default with upload thing and wrap the
entire configuration into that so go to the end and end the wrap here and this should still satisfy the config and yes we still have this require error but again it will not uh interfere with our app at all what we have to do now is we have to mount a button and upload well we can already try that but uh we can stop here I mean this is everything we need for now if you want you can continue reading but what I'm going to do is I'm going to go back inside of my dashboard
so that I can actually uh see my files being uploaded here so what we're going to do now is we're going to go ahead and start working on this uh component right here so let's go ahead inside of our form section here and what we have to do now is we have to create uh those components which I was talking about right here to do add a thumbnail field here so that's going to be a form field in itself and now let's go ahead and give the form field a name of thumbnail URL let's give
it control of form. control and let's go ahead and give it render and let me just move render here so I'm going to D structure uh actually we don't need any field here because well technically yeah this is a form uh field but we're not going to use it exactly the same way we used our previous ones in a sense that it's not going to need any kind of form control because all that this is going to do is it's going to open up a model and display the current items so let's add form control
here let's add a div class name padding 05 border border dashed border neutral 400 relative height of 80 84 pixels width of 153 pixels and group like this so I think I'm missing a little space here maybe there we go yeah make sure you put space between width and the height and then inside of here you're going to use an image from next image so just make sure you have added an import for that and we're going to give this fill alt will be thumbnail and we're going to have a source which will either be
video. thumbnail URL or/ placeholder SVG and let's also give this a class name of object hover there we go so now you can see my current thumbnail right here one thing I want to do before we move forward is standardize this we are using it in three files in four places so what I'm going to do is I'm going to go inside of videos and I'm going to create types. TS here actually not types I'm going to do constants DS in the videos and I'm going to export const thumbnail fullback and I'm going to add
slash placeholder SVG here and then I'm going to search for placeholder SVG again starting with our form section I'm going to go ahead and fullback thumbnail for fullback here which was Auto imported from modules videos here I'm going to go ahead and continue searching next one is inside of videos UI components video player so let's go ahead and use the thumbnail pullback here from uh do do do constants and the last one is in the video thumbnail component again inside of the videos module UI components video thumbnail so two places here where we are going
to modify to use thumbnail pullback from dot do/ do do constants and if you want to you can also you know be be consistent whether you want to use uh double pipe or double question marks so let me just see all the places where I use it instead of form section I'm also going to replace it here I think there's no need for double uh question marks here there we go now I'm consistent in all of the places where I'm using this great let's focus back on our form section here nothing should change as of
now this should still work as expected but what we're going to do now is just below the image we're going to add a drop down menu I believe we already have the drop down menu because we have used it to generate our delete button inside of here let's add the drop down menu trigger and give it an as child property let's add a button component which will have more more vertical icon we already have all of these imported it seems let's give this a size four and text white actually size 4 is not required explicitly
let's go ahead and give the button a type of button this is very important a size of Icon and let's go ahead and give it a class name of background black 50 on Hover background black 50 as well and let's also go ahead and do the following confirm that inside of this div you've added a relative class so then then we can safely give this button an absolute class and position it like this and now you should see uh little three dots here it's kind of hard to see how this looks like because the background
is actually black as well but uh it looks well it looks okay not sure where I was going with that okay now let's go ahead and do the following let's make it fully rounded and let's give it an opacity 100 on mobile but when we hit the medium break point by default this button will not be visible only when we do a hover will it be visible again and the group will be this div as well so this div needs to have group and relative in order for this button to work I mean you don't
have to have all these Styles if you don't want to and let's go ahead and give this a duration of 300 and a size of seven there we go so now only when I hover does it actually uh show the little more vertical Dots here great so now let's go ahead and let's add the drop- down menu content give it an a line of start and the side of right open up the drop down menu item and in here we're just going to have change with image plus icon give this a class name of size
4 and Mr of one and make sure you have imported the image plus icon from Lucid react so now when you click here you should have the opt to change a thumbnail and this will actually open uh the upload model which we are going to create so what I want to do now is I want to copy this drop down menu item two more times so we should have three options for the thumbnail the second one will be AI generated and the last one will be restore so we should have these three options and now
let's add respective icons for each of them the AI generated will use the good old Sparkles icon you are probably used to seeing this icon Everywhere by now when it comes to AI related things and the last one will be the rotate CCW icon from Lucid react of course there we go change AI generated and restore uh so now today I mean in this chapter we're going to implement the change and the restore one and we're going to leave the AI generation for later because we will do all AI generation in one chapter which will
also be for description and for the title so let's go ahead and focus on the change one in order to do that we first have to implement the thumbnail upload model so let's go ahead inside of modules Studio components and create the thumbnail upload model. TSX like this thumbnail upload model and and let me just I have so many things open I will just close all of them and go inside of thumbnail modu open let's go ahead and let's import responsive model from components responsive model and let me just copy the interface so you don't
have to see me write it out basically we need a video ID for the uh thumbnail for the video which thumbnail we going to change and then your normal open and an open change States let's export cons thumbnail upload model and then let's go ahead and let's destructure the props so video ID open and on open change and inside of here what we're going to do is we're going to open a responsive model I'm going to add a paragraph say hello and in here we are going to give this a title of upload a thumbnail
and open prop as well as on open change prop like this and now let's go ahead and actually use the thumbnail uh upload model so let's go back inside of our form section here let's go to the beginning here and let's encapsulate the entire thing inside of a fragment like this and you can indent the whole thing inside and then we're going to render the thumbnail upload model which is going to come from do/ components thumbnail upload model so now let's go ahead and let's prepare some states for this I'm going to add that here
so we're going to have thumbnail uh model open and set thumbnail model open to use a state and false by default and how about I move okay now this can stay here it's fine let's let's go ahead and use those now so we scroll here to where we render this we give it an open of thumbnail model open and on open change will be set thumbnail model open and video ID will be our video ID like this and then let's go ahead and find our drop down here this one with the image plus icon and
change and let's give it an onclick of set thumbnail mode open to true there we go so now when I click on change I have an upload uh a thumbnail model so what we're going to do now is we're going to go inside of thumbnail upload model and we are going to revisit our upload thing here so our upload thing lib and in here we have the upload drop zone so let's go ahead and render it inside upload Drop Zone not from the package itself from our abstraction over it in the lib like this it's
going to be a self-closing tag and it needs an endpoint and it's going to give you the option for the only endpoint it knows which is defined inside of our API upload thing core right here so similarly to trpc uh they have very good endtoend type safety that makes sense because the author uh of upload thing is also the author of The T3 stack which uses trpc uh great so now there we go we have a nice little upload a thumbnail uh Drop Zone here now obviously if you go ahead and try and upload something
here so I just have prepared some random file feel free to use any image for this it really doesn't matter and if you go ahead and try now let's see what's going on upload completed for user ID fake ID and we have the file URL so this should mean that now in here if I refresh let's see there we go instead of my files I have a thumbnail.png right here excellent so what we have to do now is we have to go inside of core.ts because remember we have this video ID but we have no
idea for which video are we actually uploading a thumbnail so this is what we are going to do now we're going to go ahead and remove this fake out from here and we can remove all of these comments here let's go ahead and do this no need for any of this so we are simplifying this by a lot and let's change this to not be an image uploader but instead a thumbnail uploader we can leave the same rules for the image 4 megabytes a maximum file of one that's fine and now similarly to trpc procedures
we can use Zod make sure you just import it we can use Zod to add any input here so object required video ID there we go and now inside of here we're not going to need to destructure the request but instead we can uh destructure the input like this and what we're going to do is we're also going to do a weit out but our out here will simply come from clerk nextjs server like this and this will just have user ID here if we don't have user ID we can throw new upload thing error
on authorized and then in here we can return back the user ID which we just got from out function and also we can just spread the input like this and then in the on upload complete we can go ahead and do the following we can do await database which you can import from sdb do update videos which you can import from the database schema and simply set the new thumbnail URL to be file. URL where each equals videos ID metadata video ID like this and import equals from drizzle orm one thing that's missing here and
which we can already fix is by importing end from drizzle orm and let's ensure our usual two queries when it comes to updating a record so first what needs to match is this metadata video ID the second is video's user ID metadata user ID but I can already see this is not going to work let's go ahead and be more specific here this is clerk user ID so let's go ahead and check it like this we're going to pass the clerk user ID here and this is what we can do we can do const database
user or just call it user and do a wait database select my apologies just select from users make sure you have users imported from schema here where equals users ID my apologies clerk ID is equal to clerk user ID if we can't find this user in our database it means that uh well there's a there's something wrong with our web hook synchronization right if clerk user exists but not the user in our database something's wrong we can't really do much with this user at that point so we're going to stop that here and we're not
not going to uh bring back clerk user ID we'll just uh bring back the entire user here how about we do that instead uh great and now we have user here so we can use metadata. user. ID there we go and you can return whatever you want I think we can return back the file can we looks like we cannot all right so I'm just going to return back uploaded by metadata user. ID right so we have to check if user ID is the user ID from the database not just the clerk user ID that's
why we need to do this first and only then allow someone to change the thumbnail URL there we go so now let's change this to be thumbnail uploader and then we need input here passing the video ID there we go and then we can decide what to do on upload complete on client upload complete right and what we're going to do here is the following we will get the utils from trpc from the client make sure you don't accidentally import from the server trpc do use utils and then we're going to develop const on upload
complete we're going to close the model by calling on open change and set it manually to false let me just fix this and then utils Studio get one. invalidate for the ID video ID like this and let's go ahead and pass that here if you want to you can also uh reinv validate Studio get many in order to immediately uh you know call that uh in order to immediately see the new thumbnail in the list of your uh uh videos there right I mean eventually it will get reinv validated by itself but you know if
you wanted to immediately see it you can invalidate this as well let's go ahead and try it out now so I'm going to go ahead and collect select change here and I will upload my demo thumbnail I will click upload after this it should close and it looks like we have an error and that is because we are using a new image source and we did not add it to our uh host names so let's go ahead and confirm that the host name is utfs doio if yours is something else okay just you know you
have to read what it is here and then you have to do the same thing we had to do when we used a MOX image so go inside of next. config.sys and you can copy this object for Mox and simply replace with utfs doio this will now restart the entire server so you can hit refresh and you will wait uh for some seconds but then it should uh show you the new thumbnail hopefully there we go demo thumbnail is showing up right here perfect so we have that finished uh what I want to do now
is I want to implement the restore method and then we're going to have to talk about something and that is file cleanup right especially after restoring or after uploading a new file how do we get rid of the old ones and make sure they don't unnecessarily populate our storage in order to do that we're going to have to refactor some thumbnail Fields inside of our schema so let's go ahead and do the restore method so the restore method will be purely done inside of the videos module specifically inside of its procedures right here so what
we're going to do is we're going to add restore thumbnail procedure which will be a protected procedure I think that we can copy literally you know exactly as it was below this is going to be a synchronous and this will be an arrow function like this this and inside of here we can D structure the context and the input as always we can get the database user here using context and then we are just going to go ahead uh and reset the thumbnail URL and we can do that by first uh getting the video so
let's do existing video and let's do await database select from video where we're going to use end and then two equals inside the first one will be if videos. ID matches the input ID the second one video's user ID matches the user ID which would the structured above if we cannot find the existing video we're going to throw new trpc error with code not found and otherwise let's go ahead and let's create a new thumbnail URL which is very simply going to be the same thing that we did in our videos web hook so you
can go ahead and find video asset ready here and you can just copy this there we go so just go ahead and add this here and let's also do this how about we also check if existing video has no play Max playback ID in that case we can also throw new trpc error because there's nothing for us to fall uh fall back on so we will just throw a bad request here and then we can go ahead and use this inside of here there we go uh and then let's go ahead and do updated video
here to be await data always update videos set the new thumbnail URL here where let's go ahead and do double Equalization just in case so video's ID matches the input ID and our video's user ID matches our user ID and returning and then we can return the updated video there we go so that is our restore thumbnail procedure we can now go back inside of the form section here and we can uh now add it here so I'm going to go ahead and go find one of my remove procedures for example let me just copy
it here I will call this one restore thumbnail drpc videos restore thumbnail so what we're going to do here is we can uh invalidate many we can also uh invalidate one by passing in the video ID uh ID video ID like this there we go and in here we're going to say thumbnail restored and we can remove the router push here uh this can stay as something went wrong and now all we have to do is use this so let's go ahead and go find our dropdowns here there we go so restore let's give this
an un click mutate and pass in ID video ID there we go so now if I go ahead and click restore it restores the thumbnail back to its or original uh position right because we are using the exact same thumbnail URL uh string as we did uh in the web hook there we go perfect so we did the following let's go ahead and Mark it we integrated the upload thing let me increase the opacity we integrated upload thing we added upload functionality we added thumbnail restore functionality and now we have to take care of uh
proper upload thing cleanup because at the moment you can see that now I have even more you know even less reason to hold these two files they should have been cleaned up by now so you can clean up files by their keys right now if I click copy file key you can see that let's go ahead and test this out so it ends with Mr and if I go ahead and open this URL here and let me just enable this developer mode uh this part you can't see but this last part in the URL seems
to be equivalent to the file key right Mr but in case this changes in the future or the or you know the the URL structure changes in the future I don't want to rely on the URL so what we are going to do is the following we're going to go inside of our schema here and we're going to go ahead and alongside the thumbnail URL also add thumbnail key and make that a text and we can even do the same thing for the preview here so preview key you're going to see why I'm doing this
in a second so yes technically what we could have done is created another entity called file which would have its URL and key Fields but for the sake of Simplicity I kind of feel this is okay I feel like it will be premature optimization to work with a whole another relation in my database and it would just unnecessarily complicate things you can of course decide for yourself but I will choose this option so I will add the key here and the key here I will then do B X drizzle kit push let's just confirm that
I have everything I need here there we go changes applied and now things are going to be a little bit different starting uh with my uploader here the thumbnail uploader so what we're going to do is we are going to store alongside thumbnail URL thumbnail key like this but that's not all that we're going to do now that we know that we're going to have keys we can leverage server side API from upload thing so let's go ahead uh and do that I'm going to go ahead and up use from upload things server UT API
and then I'm going to go inside of my middleware here I'm not exactly sure if this is the correct place to do this um but I feel like we can I mean okay we can also do it on the on upload complete I would rather do it in the middleware because I don't even want the file to be uploaded if the old one is not already deleted so this is what we are going to do since we have the video ID in the input what we can do after we fetch the user is the following
we can check if we have that video which we are trying to update so await database is Select from videos I believe we already have the videos schema and let's pass in two equals here one will be videos ID and inside of here we're going to have input video ID the other one will be videos's user ID and this will be uh our user. ID because we do get the new user that from the database here and we can just select the thumbnail key like this uh is this how I do that I forgot how
you select things in drizzle just a second you you use the schema my apologies so uh videos thumbnail key that's how you select a single item so we only need this right I don't need any more information so what I'm going to do is first check if there is no existing video in that case I can immediately break the this whole method because we won't have anything to upload later on so this will be bad request actually not found let's call it like that and then what I'm going to do is I'm going to check
if I have existing video thumbnail key I'm going to Define UT API as new UT API and then I'm simply going to await UT a wait UT api. delete files and I'm going to pass in the existing video thumbnail key and only after this was this is finished I'm going to go ahead and clean up my uh video so update videos set thumbnail key will be set to null and thumbnail URL will be set to null as well and then let's add our equals here we can copy it from here there we go so now
we uh have proper cleanup before we do on upload complete right so inside of here we ensure that we first have to delete the existing files and reset our thumbnail key and thumbnail URL back to null so let's go ahead and try out if that is working I'm not going to remove everything from my database here I mean from my upload thing database let me go ahead and delete this now and I will refresh this as well and I'm going to start and start by adding a new file like this and I will click upload
I should now have this new file uploaded in a second there we go so we didn't break anything with our new middleware and if I go instead of upload things now I should have the new thumbnail so now now I'm going to find another image so go ahead and find some new image with a different name so it's easier to demonstrate so confirm that you have the thumbnail in here and now go ahead and change it to another thumbnail so I just uh have another thumbnail here and let's see what's going to happen so this
should still work just fine great I now have a new thumbnail and if I refresh here there we go you can see the old file was deleted and I only have the new file inside of here so our cleanup uh function works well great but what doesn't work is my restore method here so how about we go ahead and do that let's go inside of procedures here and we're going to have to do the same thing so we have existing video here how about we do uh the following I'm not sure if we should do
that before we throw this error or not um well yeah I think we can do that before we throw this error because you know this is a logical block when it comes to assigning the thumbnail URL from the playback ID so I'm going to leave that in this but I'm going to start the cleanup here so how about we do the following if existing video. thumbnail key in that case let's go ahead and do the same thing with it in core basically this is what we need to do let's go ahead and get the UT
API from upload thing server like this delete the files using the thumbnail URL and then update this in the database so let's just change this this is input. ID and this is pure user ID here there we go so just await these two elements and then continue with further updates and assigning of the new URL so now obviously this um this does bring one question though uh H yeah I'm thinking do we need to maybe upload this to uh upload thing as well because we can do it using UT API UT API has something called
uh upload files from URL so technically we could just pass this and then have consistent file storage across our project but we're going to see I think that right now everything should still work fine so right now I have only one file in here and if I go ahead and click restore this should revert everything to my original state and if I refresh here no project no problem at all right uh no files at all that was what I was searching for great so we have proper cleanup so I'm still discussing you know should I
um should I do this or not because now we have a state where we added the thumbnail URL but we didn't add the thumbnail key but then at the same time we don't really care right so I think I'm going to leave it like this for Simplicity sake so we're going to know that if we are missing a thumbnail key that means that's not something we can remove in the first place right so if you want to you can decide for yourself do you want to use the imag max.com or do you want to uh
upload this URL to one unified upload service like this right but I think we managed to do everything we uh had to do which is refactor the thumbnail Fe fields in the schema and have proper upload thing uh clean up right but now it doesn't really make sense that we changed our database schema preview key right we never use this I prepared this so that when we are inside of our web hook in the videos when we get the thumbnail URL and the preview URL so that we can actually upload them hm maybe we should
do that I think I would be more satisfied if all of my files were in one place and not just randomly generated like this let's do that I think that's a better course of action we're going to go uh do that and then we're going to wrap up this chapter so for the videos web hook here let's go ahead and let's import UT API here like this and then let's go inside of our video asset ready and then we're going to rename this to Temporary thumbnail url url and temporary temporary preview URL and then inside
of here we're going to Define UT API as new UT API and we will be able to do await UT API upload files from URL like this and pass in temporary thumbnail URL and temporary preview URL and inside of here we're going to have the uploaded thumbnail and uploaded review like this so what we are going to be able to do now is first of all check if any of these are missing so if we don't have uploaded thumbnail do data or if we don't have uploaded preview. data let's return new response here failed to
upload thumbnail or preview and return a status of 500 uh you know or actually I'm not sure if we really have to break the entire web hook just because we cannot generate a thumbnail I don't think that should be a web hook breaking event because users will be able to upload the vid upload the thumbnail anyway right so yeah let's see let's leave it like this for now I'll see what I'm going to do with that and now what we can do here let me just move the duration alongside this Generations here so then here
what I'm going to do is I'm going to from uploaded thumbnail na. dat I'm going to D structure key thumbnail key and URL thumbnail URL and from here I'm going to do uploaded preview. data I'm going to have preview key and I'm going to have preview URL here and then we're going to use those so uh oh we are already using them we're just not using uh the keys I think so yes let's add thumbnail key here and let's add preview key here so now we can always store them inside of our file storage and
remove them from our uh file storage as well so this should now work just fine but we also have to do the same thing uh for for our restore method so let's go ahead inside of our procedures for video server here and instead of calling this thumbnail URL let's call this temporary thumbnail URL and then we can break out UT API here we already have it perfect and we can simply uh get the data here UT API upload files from URL temporary thumbnail URL like this and I believe that this will just return uh let's
await it of course first this should return back just data there we go uh now I'm just not sure should I throw an error here or not well yeah we can we can throw an error here how about we call this uploaded thumbnail let's store this like this in case I have no uploaded thumbnail data I will throw new trpc error here code internal server error like this and let's go ahead and finally destructure the key thumbnail key and URL thumbnail URL from uploaded thumbnail DOD dat so we should have both of these now there
we go so now we are only using upload thing to store our files we should never really load this on our front end in the end we are simply using Max as a generation tool and then uploading it this is really good because we don't depend on MOX changing their API right imagine they change it without warning you and all of a sudden all of your thumbnails have been broken right I think this is a much better way of doing this let's try all of this now so I'm going to go ahead and do the
following I'm first going to go inside of upload thing and uh make sure that you have no files inside of here I'm then going to go ahead uh and run my drizzle kit Studio here and I'm going to remove all of my videos here so let's just go ahead and do that so I'm going to remove all of my videos and I will now go inside of content here and I'm going to Simply create a video Let's select files let's upload a demo video here let's wait a few seconds and I think we can already
start refreshing here in the upload thing and there we go I have thumbnail do JPEG and it says this is a server side upload basically meaning it came from our uh from our web hook and if I click here it is that very thumbnail which usually we just used uh the pure link to image. max.com come from but now we can just use um we can just use a upload thing which would also mean that we will never actually encounter this inside of nextjs image so you could remove it um yeah let's actually remove it
I think we need it I'm pretty confident we don't need it just make sure that you removed all videos from your database so none of them have my apologies I I punched the microphone so make sure that none of of your video entities have the old image. Max URL now so I'm just going to go ahead and refresh this and then we're just going to go ahead over our entire flow again so I now have this uh here and I also have animated. GIF yes we forgot about that I just I read it as GIF
I'm so sorry it's GIF uh so we also have this and now if I go ahead inside of my content when I hover it works there we go so both the GIF and the uh original thumbnail are now stored in the upload thing and if we ever want to we can properly remove the preview right because we have the preview key nowhere in our app are we actually ever going to remove that because we will never allow the user to upload their own preview uh but if we ever want to do that we will be
able to have to do that because we uh separately stored the preview key with don't rely on the URL at all great now let's go ahead and try doing the following let's change this image to my blue thumbnail here and let's see what will happen so the image right now should change there we go and let's refresh this one there we go the old one was deleted and only the new one is available now let's go ahead and try uploading another one and see if it will still work so we have to check all of
those places right the web hook we have to check the image uploader and we have to check the ReStore in the end so now we have a red one which means that when I refresh here there we go only this one is available so we have proper cleanup and one last thing to try which is the restore method so we are now restoring everything from scratch thumbnail restored there we go let's refresh and we have the original thumbnail.jpg back perfect so we have very powerful cleanup we are not wasting any storage and we are fully
utilizing upload thing in our project amazing amazing job in this chapter our goal is to add background jobs more specifically AI background jobs for our project so why do we even need background jobs there are many reason why a project would need those but in our case we specifically want to avoid timeout from long running tasks this is specifically problematic with AI Generations if you've ever watched any of my previous videos where I used AI I takeen two of the tutorials that I did timeout was the issue when we actually deployed the application to production
so load locally uh there is no timeout we can keep the task running for as long as we want but when we deployed a lot of comments came and said hey this is timing out and it doesn't work in production now there are other Solutions uh which are not background jobs those would be Edge functions which is a temporarily solution because they increase the timeout level uh and the other one is well a web hook which would accept a successful or finished result from an AI job that web hook procedure is more similar to what
we are going to do here which is a background job so besides avoiding time out from long running tasks one another useful thing we can use background jobs for is to ensure retries in case of a failure and after we Implement some basic background jobs you might even get uh an idea of your own to where to put a background job perhaps our uh storage cleanup from upload thing which we worked hard on in the previous uh chapter could be a candidate for a background job instead of blocking our web hook right so I hope
that after this chapter regardless of what we do with background jobs you will have a better idea of how to use them and the good news is we're going to use up stash workflow which is their new product for background jobs and we already have an upst account because in the beginning of the tutorial we used their rate limiting service one thing that unfortunately uh is required for this AI feature is a credit card I searched if they still offer any free tiers but they do not they do not offer any free tiers and they
do not offer any free credits regardless if you create a brand new account I tried just a moment before starting recording this and it's true they have have no free trial and no free credits the good news is as little as $5 will be enough for this entire tutorial and that's exactly how much I have added so let me just go ahead and write that here so $5 will be more than enough for you to finish this entire uh tutorial in regards to open AI queries now if you have no access to credit card at
all you can still follow along this chapter but you will not be able uh to properly finish these uh background jobs right but if you want to you can learn the code and how it would work unfortunately that's the only thing we can do now if you do fear a bit if you do feel a bit more uh adventurous you can find an alternative SDK you don't have to use open AI up stash workflow allows you to do pure post requests to any API that you know of so if you know any free SDK for
AI well you can use that one but for this tutorial I'm going to be using open AI it is extremely cheap $5 is I think affordable for most people and uh I know exactly how to use it and I know what to expect of it so let's go ahead and start with integrating up stash workflow right here so I'm going to ahead and go into my up stash dashboard and I'm going to click on their new workflow uh tab right here and if you want to you can click on the docs and then inside of
here you will have a nice documentation from Josh tried upst or you might know him from Josh tried coding so feel free to watch this video uh there that's going to give you a very good uh example of how it works what we can do is we can go onto quick starts nextjs and let's go ahead and do the following so the prerequisites for this are an are on up stash Q API Keys No jsn npm another package manager so we have all of these things yet but let's start by getting our Q stash API
key let me just see if we need that uh immediately we do so let's go ahead and get that I'm going to go here and I'm just going to copy my Q stash token I think that's uh the one I need in case case we need something more we can easily come back here and let me just so it's Q stash token like this as always don't share your tokens with anyone so I have added my Q stash token here and now let's go ahead and let's add bunad at upstair slw workflow now since this
is uh a relatively new product I would recommend you to use the exact verion that I'm using because it is possible that they change it fairly soon after this tutorial you can see that it's not even in 1.0 well I mean that doesn't really mean anything I don't know how they do their versioning uh but yeah if you're interested this is the exact version I'm using just in case you want to use uh the exact same one there we go so you would do it like this also while I'm here you might notice that I
have these errors here uh I believe these uh came from our 24hour deletion from Max so let me just remind you of that videos get deleted after 24 hours right so it looks like something works incorrectly with their Auto deltion method because when we delete an asset manually that web hook works but looks like when they fire their 24-hour deletion maybe that doesn't pass the proper Max uh signature to our web hook so I think theault is actually at their end so yeah in case you're looking at your app and you know something doesn't make
sense you have more or less videos than you would expect uh it could be because your videos were deleted right so what I'm going to do here is I'm just going to go ahead and delete all of my assets here make sure you are inside of your new tube development so I'm going to delete all of them for a very simple reason you know it's a new day and I want to make sure that none of my assets get deleted in the middle of development and I would recommend you do the same so when I
refresh here uh make sure again you're in YouTube development you should just see empty here and if my web hook works this is deleted as well in case yours wasn't deleted you can of course go to drizzle kit studio and just you know after you've cleaned up your assets simply go ahead and clean up your videos manually here so I'm I'm going to remind you this every now and then just because I don't want you to have any confusing experience hey what's going on why are my videos not working all right now let's focus back
on up stash so we just added bun add upes let's confirm that in our package.json I'm going to open my git changes here there we go up stash workflow is here we have added our environment. loc here uh so the upst workflow is powered by qash which is another technology of their own so they're wrapping their own technology that's quite good they can afford competitive prices to Alternatives like ingest and Trigger excellent so now that we have that we need to find a way to run the workflow these background jobs and they give us two
options One is using local qash server and the other one is using local tunnel since we already have local tunnel set up thanks to our Bun Run Dev all method which runs our enro URL we can use this option why would you want the first option well the first option is useful if you want to do some heavy heavy testing which might affect your billing basically the the logic behind this is that local qar server uh will not affect the billing whereas if you massively test via your local tunnel that will literally call the app
stash Q stash and workflow so it might affect your usage but as you can see I'm on a complete free plan I don't even have my credit card added here so I'm pretty confident that for this tutorial we're not going to overstep any limits at all great uh so we're going to choose option two local tunnel so copy the Q stash token from up stash console we already did that we can confirm that by going inside of environment. loc here Q stash token is here and now we need to add up stash workflow URL now
the upst workflow URL is going to be our static enro domain main so let's go ahead and get it here there we go like this I'm just going to go ahead and add an https prefix like this after that let's go ahead and see what else we have to do so this looks like it's working we already have the Q stash token and now let's go ahead and let's create a workflow endpoint so this is quite simple all we have to do is we have to create a route somewhere in our app so I'm going
to go inad of source app for API let's go inside of videos here and let's create workflows like this and then let's for example create title and then route. DS so inside of videos we're going to have workflows and then title and then route. DS so this is the equivalent to Local Host 3000 SL API videos workflows slash title basically a workflow for title inside of the video's uh API and let's just copy this entire thing from here so they are using a different example but ours makes more sense like this so import sir from
up stash workflow nextjs and inside of this serve they have prepared two steps for us to run and they have also uh overridden the post method uh I'm not sure if overridden is the correct uh term to use maybe they've created a post method for us thanks to this serve method here and this is actually it this is the background job right so here's what's important now to test this out the first important thing is that you have your web hook running right you should be able to visit this URL right here if you cannot
visit this URL it means your web Hook is not running and your testing of this will not work the other thing that you have to do is you have to try by uh posting this endpoint which we just created and once you post this endpoint this code inside of this steps will not run on your back end but instead it will be a background job in up stash which has much higher timeout limits it has proper retries in case of error and uh you're going to see exactly what happens with each step inside of the
usage here inside of events and in case some something fails after it tries you have this dead queries which then you can manually uh check out like what's going on why did they fail and then you can debug and you can refresh them if needed let's go ahead and let's try doing this so I believe that this is what they say to do next mpm run Dev which is our Bun Run Dev all and now all we have to do is we have to do a curl uh and post method to our API workflow so
what I'm going to do is I'm going to change this to go to slash videos workflows slash tile and let's see what's going on it looks like I am getting an error of course I'm getting an error uh I think that we have to do it uh on uh Ang or maybe we don't have to do it on angr maybe it is an actual issue here uh let's see what exactly is going on here the issue is that I used https there we go so don't use https my apologies I thought that the issue was
because I have to run the local tunnel but that's not true we should be able to query on our local host and then the local tunnel will be used to communicate uh from appstash back to our application so if you want to if you have the ability to use the curl command don't worry we're later going to test this using the usual interface but if you want you can try this make sure it's HTTP Local Host 3000 API videos workflows SL tile and you should get back the workflow run ID and now if you go
back inside of here there we go you have the job and you have all the steps which were Happening Here the initial step and the second step right here perfect so you can use these steps to do whatever you want but just these steps just by themselves don't seem too useful right uh the reason they don't seem too useful is because we don't really know what's inside of this context there there's no way for us um you know to run this in any other way than a curl post so what we have to understand next
is how to actually run these kinds of apps so they do have a step five deploying to production we're going to focus on this later when we actually deploy so there are a couple of things we have to do now first of all I believe we have to uh secure our end point basically we need to have the Q stash current assigning key and the Q stash next assigning key and there are two ways they allow securing uh you can either use Q stash Q stashes built-in request verification or you can develop custom header and
custom authorization mechanism in case you need that solution so let's go ahead and go back to Q stash and let's go ahead uh and click on signing keys and let's prepare qash current signing key so I'm going to open my environment. Lo here so q q stash current signing key and Q stash next signing key and let's go ahead and copy the first one add it here and the second one and add it here as as well both of them begin with Sig pretty sure short for signing or signature now that we have those let's
just confirm that we properly added them so Q stash current signing key and Q stash next signing key the names are important here because I believe that the uh Q stash we don't have q stash yet but uh I believe that when we actually create a lib or Q stash it will automatically look for these environment variables here and that's how it's going to secure the end point so now that we have added those uh in order for if we try this again I'm not sure I think uh maybe it will still work looks like
this doesn't work now there we go because uh it detected that we are using Q stash current signing key and Q stash next signing key inside of our application but this request which we just tried doesn't have those so it's no longer allowing anyone to just trigger a workflow from our application right only we who have these keys can query our endpoint and trigger a background job so this is now secure from any intrusions or any uh attacks which are aiming on overflowing our background jobs and causing massive usage or billing there we go so
we successfully secured uh our uh q/ background jobs now the question is how do we call this and as you can see you can uh have the they do support the edge cases in case the environment variables are not support imported then you can explicitly uh protect it via receiver like this but we don't have to do this step uh as you can see we don't have to add anything here because we support environment variables normally and of course they also offer the custom authorization method if you know if you are in an environment where
you need that so now that we know how to secure our run let's learn how to start a run and this is the the recommended way of doing it which is client trigger so we have up stash workflow so how about we go ahead and Define this I'm going to go ahead and go inside of lib here so we already have the redice and we have the rate limit and now I'm going to create Q stash here you can also name it workflow if you want to uh but I think Q stash may be a
bit more specific actually H let's call it workflow let's go ahead and let's import the client from up stash workflow and let's go ahead and Export it from here and I'm just going to call this workflow and inside of the token here we need to pass process. environment and we need to pass the qash token which was the first thing that we added and I will put a little exclamation point at the end there we go so we now have the workflow uh I'm still deciding on whether I should name this constant workflow because I'm
um I'm fearing that we might have conflicting names between the Imports right something else might be named the workflow so that's why I wasn't sure whether I should name this Q stash or workflow but let's leave it like this for now and this is how you would run it now so let's go ahead and let's try this now we're going to go ahead and do the following we're going to go uh and create a little procedure right so we immediately connect this to our code let's go inside of videos let's go inside of server and
procedures inside of here we already have the restore thumbnail how about we add generate thumbnail let's add a protected procedure here the input can be empty and let's add a mutation here let's actually remove the input in case it's going to actually be empty let's make sure this is asynchronous and I'm not going to do anything here besides uh just triggering a call so I'm going to go ahead and do a wait workflow from lib workflow and I will simply call trigger like this and inside of here what I have to do is I have
to pass in the URL which actually I'm not sure if we need to pass this explicitly I think that if we've added up stash workflow URL I think that it's actually going to read that URL so let's try without passing it and then we're going to see maybe URL is a required parameter maybe I am wrong let's just do workflow run ID here uh so this is oh my apologies I got confused about how this works we need the URL let's add process. environment and let's add up workflow URL and the way we choose which
workflow we are triggering is by going to slash API SLV videosw workflows slash tile so this is how we choose which workflow to trigger uh everything else is optional but if you want to you can pass a body I am a body or maybe more interesting it would be if we could D structure the context from here if we could get the ID user ID from context. user like this and then we can pass in inside of the body here user ID like this so we know which user triggered this background job and we would
have the workflow run ID here and we can just return that back so now we have the generate thumbnail protected mutation so let's go inside of our form section and we have this drop down menu item AI generated so how about we copy uh the restore thumbnail here change this to generate thumbnail use video stpc generate thumbnail um it really doesn't matter what we do here but yeah we will eventually actually since this will only trigger a background job we should not do any of those right instead what we are going to do here is
the following we can add a title uh background job started and description uh does it allow title uh let's see background job started and then maybe description it could be uh this may take some time like this and now let's use the generate thumbnail similarly to how we use restore thumbnail restore that restore thumbnail here so on click here generate thumbnail mutate and we don't pass anything inside for now so now what should happen is once I click on this let me just go inside and create a random video here I forgot that we removed
all of our videos so let me just go ahead and upload this demo again and just wait a few seconds for everything that needs to be generated uh around it get generated so I'm just going to refresh here it's loading it's loading uh looks like it's still preparing the video hopefully soon there we go ready and ready which means we have the thumbnail great so now if I click AI generated this should fire a new background job there we go background job started this may take some time and now if I go inside of Q
stash inside of workflow I should see API workflows title right here a few seconds ago and you can see it successfully passed the user ID here so now this background job all of a sudden uh in my workflows title has more sense all of a sudden because now we know that this context has the user ID which triggered this API call that's something we did not know until now right so let's go ahead and learn how to actually uh extract the body uh from uh a from this context right here what I like to do
is I like to create an interface input type here and I'm going to pass in the user ID string and how about we passing uh video ID string as well and then what I'm going to do is I'm going to go ahead and Define input to be context request payload as input type like this and then in here I will be able to destructure my video ID and my user ID from the input and then what we would be able to do is for example uh we would be able to dedicate one of these runs
to do a long running task for example this is not exactly a long running task but what we could do is uh find a video so this would be existing video and we would do context. run and we can call this uh get video like this and inside of here we would get the data which will be await database do select let's just import the database from at/ database here I will just reverse these two Imports so a wait database select everything from videos from our database schema here and let's go ahead and add where
let's add end and let's add two equals and inside of here we're first going to check if videos ID match our video ID and the second one if user ID matches our video's user ID actually we should reverse this to there we go and then let's go ahead and return [Music] data zero and inside of this step if we want to we can check if that exists or not so if data if the first element in the array does not exist uh we can throw new error here uh not found and this will break this
step and then when we have the existing uh video here we can continue using it we can conso log the existing video if we want or you know we can uh continue doing something in step two and the cool thing about the steps is it it doesn't make too much sense you know to just do a normal database fetch instead of a background job right that's not a long running task uh but you can already get the idea of what this could be useful for and we can separate all of these things in uh in
in these steps what I particularly think this is useful for our web hooks especially our videos web hook because we do so many things in each of these things we do thumbnail generation then we do uh thumbnail cleanup then we go ahead and update the video right those are three steps which we could delegate to a background job if we wanted proper uh proper retries and Order and everything right uh and especially for things like this right what if the thumbnail generation fails our entire web hook fails in that case case so that's something that
we can dedicate to a background job and then if it fails it's simply going to repeat this step and only then it's going to go to the next step so how about we try this again I'm going to go ahead and prepare my runs here at the moment this run I only have two runs so I'm going to go ahead and try this again I think that uh I first have to modify the input here I will just copy the one from restore thumbnail so let's also accept the ID let's go back inside of the
form section and we will just pass this here there we go so let's try clicking AI generated and now if I refresh here in a few moments we should be seeing uh the new background job let me try a hard refresh let's see maybe we've gotten an error here or something I don't believe it's that let me see uh oh looks like we do have an error error submitting steps qash in parel Step execution not found uh okay let's see what's going on maybe it was because the moment I saved and run the query I
didn't refresh so it never got uh the proper yes so the issue is that it never got video ID uh because I clicked save and I didn't wait for hot reload to update I believe or maybe my procedure doesn't even send it yes let's go ahead and let's extract video ID and use input. ID here uh input from here like this there we go and this is an accidental import yes so this one for example uh keeps failing right this get video step for example is now constantly failing and if it fails more than three
times which I think is um the default Maybe not maybe I'm incorrect uh nevertheless I could Define my retries here so I could say hey only try three times don't try more than that right and once that happens once it fails it will end up here in I'm not sure what this is short for maybe deadline query I'm not sure uh but it is descriptive uh and it will allow you to have a separate view of all of the steps or jobs which failed so that you can manually go here in take a look at
why they failed see the body that they had you know what's going on and then try and manually run them and if you have a task which you know is incorrect like this one you can always close it yourself so this will cancel a workflow so now that we are properly passing in the video ID from our input. ID let's go ahead and refresh this entire page so we ensure we have three of those and let's try running another one uh so this is some some unrelated issue maybe some hot reload yes I think this
is some something hot reload related uh what we're going to do here is we're going to try this again AI generated background job started there we go this one was cancelled exactly what we uh expected and in a few seconds we should be getting uh a new one uh but it looks like we do have some error here so let's go ahead and debug this one together this is an up workflow error thrown after a step executed it is expected to be raised make sure that you wait for each step uh perhaps I did something
wrong again here it's specifically here with existing videos so maybe I'm doing something wrong and I didn't even notice it here which is funny because all I'm doing is trying to demonstrate uh the the video fetching here let's see videos I ID video ID and video's user ID uh from user ID I think this should work fine and it looks like it does work fine there we go so get video right here uh and you can see that it get got the entire video so I'm not exactly sure what this error was here maybe this
video actually came from something from before right maybe that's what's going on here because this one looks like 200 yes I think this came from something else uh great so I think that we've now kind of established what the background jobs will be used for we've secured them and we learned how to trigger exactly the job we need to trigger uh so now let's go ahead uh and let's um well let's make this actually does what it needs to do let's go ahead and use the generate thumbnail one which I know sounds funny now but
we've already called it generate thumbnail let's still make it use workflows title and let's use it to update the title of a video how about we do that right so we're going to break the step here let's go ahead I'm going to call this video I'm going to ahead and do existing video here if we can't find the existing video throw an error and otherwise return the existing video that's going to be my first step get video now in my second step here what I want to do would be update video so I'm going to
go ahead and simply do await database select my apologies update videos set title updated from background job where and and let's just copy these two here and make sure this is asynchronous and we don't need the last step right so we now have the first step which gets our video so we could use yeah I mean we don't really need this I it's more of a for demonstrative purposes so we could you know just do this one if you want to you can still use this one and then maybe from here uh I don't know
you can use video. ID from above and video dot uh user ID from here uh I just have to await the context run to get the video user ID an example right so you don't no longer rely on the props here uh but you now rely on what you actually fetched so let's go ahead and try this now so now after I call my new background job this title should change for this video so I'm going to click AI generated this started background job and after that background job successfully runs it should change the title
of my video there we go this is my latest run here and we can see that we successfully got the video and it successfully updated the video uh so let's go ahead and refresh this page now there we go updated from background ground job and now I'm pretty sure you understand all the steps we need in order to use AI to do this step for us so let's go ahead and let's go uh to open AI platforms so make sure to go to the platform and not to their chat instance and go ahead and log
in so I've created a brand new uh account for this one just to confirm that unfortunately there are no free tiers you will have to add something so I've added $5 Here and Now what you have to do is you have to find your API keys let me go inside of API reference uh I'm not sure uh how to use this interface to be honest let's go into settings API keys and let's create new secret key I'm going to call this next uh new tube and my default project and I will select all for permissions
and I will copy this and now let's go ahead and let's store it inside of our environment so open AI API key and let's pass it here there we go so now what we have to do is we have to go uh inside of my apologies inside of the documentation here and you can find Integrations with open Ai and inside of here you can see how you can uh make an cont context API call specifically to open AI so there are two ways you can do it you can use their SDC context API open Ai
call or if you want to uh you can uh let's go ahead and see uh I'm not sure where maybe this is the example H let's see you can just do a normal HTTP request but I can't find uh an example now but they also support anthropic they support recent versel AI SDK but this is actually quite interesting I didn't think this was in the documentation when I was building this they've added an example to use deeps this is very interesting because maybe you can do it for free if you really have a problem with
adding a credit card you can go to apid deep seek.com and try and creating a free account I'm not sure if they have any limits I have no idea how their API works so can't guarantee you that it's free but you could try using that instead and you can still use open Ai call here it seems because they support the exact same API great but since I've already decided on open AI I'm going to continue with this so I'm going to go ahead and copy this I'm going to go inside of my route title here
and inside of my uh I'm going to add another step here which I'm going to Define const uh generated title and this will be a wait and then this basically I'm going to await context API open Ai call and I'm going to call this generate title right so this is the same as context. run uh the name of the step and then just the configuration so let's go ahead and change this to process. environment and let's go ahead and let's use open AI API key add an exclamation point at the end operation is chat completions
doc create uh let's see is this strictly typed it is so you should be able to see everything they have here there we go chat completions create uh models looks like models are not strictly typed so you have to be careful here make sure you type it like this and then inside of here uh you can uh uh give a role to the system and then what the user asks so let's for example uh try and do that so the role for the system here uh should be a prompt U it should be a prompt
which would basically tell this the open AI what they have to do and if you you if you have access to my source code you can simply uh find the title system prompt otherwise you can just use the public gist uh where I've added these logos and you can find the title system system prompt for example and let's add it here so title system prompt your task is to generate ACO CEO Focus titled for a YouTube video based on its transcript please follow these guidelines right here uh so just a simple system prompt here and
then that's what we're going to add to the system and then the user can say uh hi everyone we are basically mocking uh we are mocking um a transcript of a video hi everyone in this tutorial we will be building uh a YouTube clone let's try if this works right this is not a real transcript so I I'm not sure how well this is going to work but we can try uh and what this can do this open a call can do now it you can immediately destructure the body from here like this and then
you can update the video based on uh based on the result so for example let's get the title here to be body choices first one and then this will be optional so message optional actually content looks like it always exists there we go and then let's pass that as the new title here the issue is that this uh can be null so let's default this do we have our first we have the video here great this is actually useful now let's just fall back to our original title in case this generated one is null for
whatever reason uh so if you've added your open AI key this should now work let's try and see so right now it says updated from background job but if I go ahead and click AI generated and if I go ahead and just focus on this wait a few seconds we should see a new um title now there we go so finished a few seconds ago and looks like it successfully generated the title in this step as you can see so we can see the response I'm not sure how good it will be uh I'm not
sure where can I read the message here let's let's actually see what did it do did it do something useful there we go build a you clone stepbystep tutorial it's actually amazing that it generated such a good prompt just for the from this very simple transcription uh great so that is basically what we wanted to achieve with this background jobs I hope that now they make more sense right because this kind of thing uh is a potential long running task I think even this one is quite simple so it could be awaited uh but imagine
you're using this to generate a thumbnail imagine you're using this to generate parts of the video right any long running task that you can imagine is better off put inside of a background job like this one excellent so now that we have this let's go ahead and let's actually get our transcript right and the good news is we have the transcript you just don't know how to get it so uh in order to get the transcript we first have to get our video after we get our video Let's create the transcript and let's do that
in a background job as well so context run get transcript asynchronous and we're going to get the track URL from https stream. max.com video from above MOX playback id/ Tex next and then we would need Max track ID video max track id. dxt so this is the important thing right Max track ID gets generated inside of our videos web Hook when we get video asset track ready so only then we generate the track ID and we add it here and remember this web hook only fires if we enabled the subtitles which we do inside of
our video procedures here so we added this part so that's the first requirement for that web hook to fire the second requirement is that there is actual audio and I think in this case English audio in the video and only then will you get this part right here so in order to correctly test this I would highly recommend using a video with English audio uh if you remember in my previous chapters uh I'm not sure which one it is is it this one there we go you can visit tinyurl.com YouTube dclip where I gave you
a demo MP4 which I'm using for my uh jobs here so find that video or any other video and confirm you can confirm inside of your studio that you have a Max track ID so basically I want you to confirm these things so you don't get confused as to why my works and your doesn't right so be careful and confirm that whatever video You're testing this on has MOX track ID and you should also have MOX track status set to ready so if sub subtitles are ready you should be able to do what I'm doing
here which is generate a text file from the from well uh stream. max.com now we have to actually fetch this so let's do await fetch track URL and let's return response response do response this is typo. text there we go so we now have this right here so we can call this final transcript or let's just call it text like this if there is no text throw new [Music] error bad request otherwise return back the text which will ultimately be the transcript great and now inside of here you can pass the transcript like this there
we go uh and instead of you can move the title here I believe and you can do the same thing here if for whatever reason the title was not able to be generated you can throw new error here and simply say uh bad request there we go uh actually it might be better to do this outside of the step because throwing an error here will cause it to retry but it couldn't do anything new with the result it got so perhaps throwing it here would be a better idea right I'm not exactly sure uh how
this one gets retried but I think since they've built they have this built in if you return the body I think that it will if it errors I think that it will just retry in itself right so first thing that I want to confirm is that something like this works so let's go ahead and just go inside of MX here and I want to go inside of docs just to show you exactly where I found this URL so it's not just magic for you uh let's go ahead I think they actually have a section called
AI workflows automatic translation summarizing and tagging so go here and then inside of here they should teach you how to retrieve the transcript file so if you click here we've added this and now we should be able to retrieve the file there we go so this is how you retrieve it stream. max.com playback ID text and then track ID of course if it is a signed asset it will also require a token that's not the case for us so exactly what we did here in case you're wondering where did I get that from perfect let's
try it out so I know I have some audio here but honestly I don't even know what I talk about in the video but let's see what AI will generate I know it's funny we use the thumbnail button but it's because it's the only AI button we have right so let's go ahead and wait a few seconds for this new job to finish and see if our title gets uh updated there we go looks like it was a success and you can see how get transcript works this is what I was interested about this is
a short clip with English audio so we can demonstrate Max upload and subtitle generation p thank you so this is exactly what I say in the video I believe and let's see what's my new title now Max upload and subtitle demo I'm so impressed by this this is so cool the way we use transcript a background job all of that combined to generate a title I think that's super cool so what we have to do now is we have to add dedicated buttons here for AI here for AI and then generated AI thumbnail uh generation
so let's go ahead and do the title one so let's start by going back inside of the procedures here and inside of the videos procedures and let's find the generate thumbnail one and let's just copy and paste it above and call this one generate title like this and it's going to go to API Works flow title nothing else uh should be changed really and now let's go back inside of our form section component let's go ahead and copy this generate thumbnail and let's rename it to generate title and everything here can stay the same and
now we have to use this generate title somewhere so let's go ahead and find our first input which is the title there we go to do add AI generate button so I'm going to go ahead and add a div here remove the to-do and give this is a class name of flex item Center and GAP X of two and then below that I'm going to add a button with sparkless icon and in here I'm going to go ahead and give this a size icon a variant of outline a type of button very important class name
rounded full size six and then I'm going to lower the size of the SVG using this targeter and on click here I'm going to call generate title mutate and I'm going to pass the ID video ID and disabled if I have it's going to be disabled in case generate title is pending and then we can also create a nice little if generate title is pending in that case we're going to render a loader 2 icon with the class name of animate spin otherwise we can render the Sparkles icon there we go now we have a
nice AI button here so if I click here it says background job started this may take some time I'm not sure how different the new title will be because this one is already relevant to our transcription so now that we have this let's go ahead and copy the entire content inside of this form label here including the title text here and let's find the description and let's just do the same thing and I will just change this to description like this so now the description will have it as well let me refresh to see maybe
we got a new video we did uh upload and subtitle generation demo so it created a new one regardless uh great so we have this now let's go ahead and go inside of procedures and let's copy and paste generate title this one let's make it generate description and let's make it go to API videos workflows description description like this and now we have to create the description workflow but that's going to be quite easy because it is exactly the same so inside of videos workflows let's change this to description and let's change this uh well
you have to find the prompt for my assets here so description system prompt you can write your own if you want to there we go and simply use uh the description system prompt here and this will be generate description and then call this description and simply update description like this so that's our new background job with the new description system prompt and now we have to go inside of our form section our description button currently uses generate title let's change that by copying and pasting generate title and renaming this to to generate description there we
go and now let's go ahead and let's replace this so this is for the title that's correct but for my description these three should be generate description there we go let's go ahead and try it out so I'm going to click this now background job started perfect and now I'm just going to wait uh and see if my new job for description will work as well so this should show slash description in a few seconds here there we go run success on slash description here and let's go ahead and try it out and so if
I refresh this this video demonstrates how to upload video to Max and generate subtitles amazing exactly what we expected so the only thing that's left here is AI generated thumbnails but I will leave that to another chapter simply because it is a whole new component that we need and a prompt here that we need and this is already an hour long but I think you got the gist of how we're doing this we will of course finish this but just in the next chapter and one thing that I would highly recommend doing is also disabling
these buttons If Video itself does not have Max track ID like this so if it doesn't have Max track ID uh you can completely you know prevent it from even trying to generate something the only videos which are not going to have Max track ID are videos whose subtitle status is not ready for example that could be a video that you will just upload right so let's go ahead and select this demo. MP4 here and right now you can see it's disabled and we want it to be disabled because it has no subtitles yet if
I refresh now there we go since subtitles are ready I can already do that it looks like I already have my track ID which is interesting because uh I always I always expected the video status to be ready before the subtitles that is interesting the reason uh I say it's interesting because in my videos web hook in the track ready I depend on the asset ID but I only assigned the asset ID in my let's see when do I do it so in video asset created okay so let's hope that video asset created fires first
because if this if video asset track ready fires before my video asset created I never actually have the asset ID to look for in the video but looks like it's working right so if I go ahead and try both of this yeah I can just start both of them right that's no issue so if I run both of this now uh I should be able it should work but you saw how in the beginning both of them were disa because we did not have any uh there we go so title was generated and I believe
the description will be generated in a few moments as well we can of course keep track here there we go this is the one looks like it's still running it's generating the description let's refresh maybe it's done uh looks like it's still running all right I'm going to give it some time to run maybe there is an issue maybe we're doing something incorrectly here but I think everything is correct it has both video ID and the user ID so let's refresh looks like it's taking some time I'm going to pause the video and uh unpause
when it finishes so it's been two minutes and it's still running uh so I am going to debug why this is happening perhaps for you it works perfectly but you know looking at my uh logs here only title has been hit so far it could be that I did something wrong when I fired them simultaneously so I'm going to check out the documentation on that part but I will also leave it running perhaps the fact that we are on free tier and we run two queries at the same time caused this one to be uh
cued for after some time so I'm going to go ahead and end the chapter here and let's just see if we did everything we were supposed to do so we integrated up stash workflow we triggered a background job with set up open AIS SDK we've generated the title and the description but we will leave the thumbnail for the next chapter uh great great job in this chapter we're going to go ahead and wrap up our form section by implementing AI thumbnails basically this little button left in our thumbnail dropdown so the goal is to create
a thumbnail prompt model so that we can go ahead and enter what we want uh for AI to generate because it doesn't really make sense to use the transcription for image generation as that really uh doesn't work well in my experience we also have to create the proper generation workflow and wrap up form section by adding the skeleton to its loading State and just some update about my last run looks like it ran for five minutes I'm not sure why it took that long but the good news is it works it definitely worked it took
its time but it absolutely did what it was supposed to do and that's one cool thing about background jobs is that they are very reliable right so even if they don't seem to be working right away uh you can know for sure that they will at least retry if they fail and you will of course have all the logs and analytics you need um to discover why some things happened or didn't happen and here's the new description so let's go ahead and create the thumbnail upload uh the thumbnail generate model we're going to go ahead
inside of our modules Studio UI components and let's copy the thumbnail upload model let's rename it to thumbnail generate model let's go ahead and change this to generate we are going to have the video ID open and on open change all of that is fine and we're also going to have form schema here using Zod so import Zod and we're just going to add prompt and let's go ahead and make it have a minimum of 10 characters so that it at least it's at least something descriptive to tell the AI to do right I'm just
going to move this to the top and what I'm going to do now is I'm going to go ahead and change this to not use an upload Drop Zone instead we're going to go ahead and open our form from components UI form so while we are here let's go ahead and add everything else we need uh from our form here so that's form control form field form item form label and form message we can remove the upload Drop Zone from here and let's also add text area that's going to be our component and we're also
going to need use form and Zod resolver and let's wrap up our Imports by also adding toast from sonor one more component is the button so we can submit things there we go so we have the form schema and we have prepared the form here looks like it says cannot find form oh I forgot that I removed a form okay now let's go ahead and let's create U the form constant here so use form z. infer type of form schema resolver Z resolver form schema and default values prompt empty string like this there we go
so now inside of here we can go ahead and spread the form and inside of here we can open up a native HTML form element and we can pass in on submit to be form handle submit pass in on submit and we can turn this into an on submit method here so we can leave it like this for now we are going to modify what's inside in a moment and let's also give this native HTML form a class name of flex Flex column and GAP four now let's add the form field which is a single
self closing uh tag here let's give it a form control let's give it a name of prompt which is the only possible option and let's go ahead and give it a render inside of here a form item a form label saying prompt a form control and text area let's give this everything necessary from the field let's give it the class name of resize none columns 30 rows five and let's go ahead and give it a placeholder uh so for examp example let's we can write anything we want here you know a descriptive uh a description
of wanted thumbnail like this and let's go ahead and passing form message here as well and still inside of the form element here add a div with a class name Plex justify end and render the button which will say generate and give it a type of submit so before we move forward let's go ahead and render the thumbnail generate model component inside of form section here so I'm going to render it right next to our thumbnail upload model so thumbnail generate model and let's import this from do/ components thumbnail generate model let's copy this state
and this will be uh thumbnail generate model open and thumbnail generate model open set thumbnail generate model open great so now in here where I render it I'm going to modify the thumbnail generate model to use thumbnail generate model open and set thumbnail generate model open so just be careful make sure you don't mix and match uh invalid States here right video ID can stay the same and now we have to reuse the set thumbnail generate model inside of our Sparkles icon ai-generated drop down menu here so instead of this what's going to happen it's
going to set this to true there we go let's see what kind of error I have now uh so I have generate thumbnail here uh we can remove this we are not going to need it here but we will need it inside of the thumbnail generate mod so you can add it here if you want to generate thumbnail and let's see do I have trpc we have it from the client so add it here generate thumbnail PPC videos generate thumbnail there we go uh and on submit we're going to call the generate thumbnail that's fine
let me just remove uh oh did I okay it should it should be here not in the form section yes okay now I believe if I go ahead and click AI generated it will open upload a thumbnail perfect so let's go ahead and just finish up our onsubmit method to accept the values of z. infer type of form schema and what we're going to do here is simply call generate thumbnail mutate and passing the prompt to be values prompt and ID video ID now we are getting some errors here because we have to modify our
generate thumbnail procedure uh and what we're also going to do well we don't have to do anything we don't need to use the utils at all but we just have to fix this prompt thing so let's go ahead and go inside of the trpc videos generate thumbnail protected procedure so whenever you find this you can press command or control and click here and it should lead you to that exact procedure it's Loc cre inside of videos servers procedures so besides ID uh we are also going to accept a prompt which will be a z string
with a minimum of 10 so it's the same thing that we said here great we now have this so besides user ID and video ID let's also pass the prompt uh from input. prompt I'm not sure why uh it didn't right when I wrote it like this it looked like prompt exists it looks like prompt is a reserved function so that's why there was no error so be careful yeah you have to destructure the prompt from the input I did not know this so maybe prompt isn't the best name to use but make sure that
you pass input. prompt so when you hover over here it should be string and not that function well yes prompt is a pretty popular method but I uh I have not used it in ages great so we now have generate thumbnail and let's change the workflow to go to thumbnail because that's the workflow we care about and now it's up to us to create that so let's go inside of source app folder API videos workflows and let's copy one for example let's copy title and paste it here and rename this to thumbnail let's go inside
of the thumbnail one and now it's time to build this route so first things first besides having this we're also going to have a prompt so let's add that we can remove this default prompt because the user will type their own in here we can also destructure the prompt now and let's start as we usually start which is by getting the video so in case we can get the video we're going to stop here we're not going to run any other steps right now we are not going to need any transcript at this point we
can all already generate but we're going to generate a little bit differently so open AI SDK from upsh at least in my version does not uh have it for anything other than chat completions create so what we're going to do is the following let's go ahead and delete this and let's go ahead and do context. call generate thumbnail sorry for the popup generate thumbnail like this and then pass in the second argument and in here we can go ahead and pass in uh the API open.com version one images generation so I cannot exactly guarantee you
that in the future if you're watching this a year or two from now this endpoint still works and still uh uses the same API so if you want you can go uh to open AI documentation let me just load the platform here and if I click on image generation images API uh there we go API open.com we want images Generations so find in the open platform images and in here you should be able you know to see if this still exists or not so you can copy it from here and also add it right here
there we go and then you can follow the prompt the model model right let's go ahead and pass in uh the body from here so let's assign the method to be post let's assign the body to have our prompt like this and then you can go ahead and pass in some other things so n will be one I'm not sure what this actually means the number of images to generate okay so we are going to default to one uh and now for the size let's go ahead and see so so looks like we can modify
the size in my original source code I was using uh this one but this is not not the greatest resolution to use uh for thumbnails so perhaps if we change the model let's see it looks like if we use D E2 it's only Square images but if we use D E3 we can use this type so let's try that how about we give this a model model and now let's give it a size we're going to try both of this so how about we try this one first like this and now we also have to
passing the headers here authorization make sure you don't misspell this because this is not strictly typed passing the bearer and then process environment and you have to add from your dot environment open AI API key like this there we go perfect so after this if you go ahead and uh go you will have the body here and what you can do is you can get the temporary image temporary thumbnail URL like this and you can extract it from body data. URL at least this is how it works here so body data and then first in
the array URL right uh so I'm not sure how we can set you know can I go ahead and give this a type of body which is going to be uh this URL string and then an array will that work let's see did I do this correctly body okay so maybe just this maybe just URL inside my apologies how about we do oh like this body data doesn't exist on type let me try and strictly type this oh yeah my apologies so we just need data and data needs to be an array of this right
there we go so if you want to you can add your own types for what the return is going to be and if you don't like this return you this array you can also do this yeah that's the same thing great so we now have this temporary thumbnail URL in case we don't have it we can immediately throw this as bad request and now what we have to do is upload it to upload thing right so we are going to do the same thing that we did in our is it uh web hook in here
when we do asset ready we upload from the temporary thumbnail URL so we're going to do the same thing here the reason we did it in here is so we don't have to rely on MX changing their API in the future and then all of our images get lost right and to own our images but the reason we are doing it here is because this URL which open AI generates will only last for some time it won't last forever they are not a storage company right so we shouldn't expect them to store these URLs you
should always treat them as temporary thumbnail URLs so now let's go ahead and do the following we're now going to create uploaded thumbnail URL and let's do await context run upload image upload thumbnail let's do that asynchronous like this and let's define UT API to be new UT API from upload thing SLS server like this and then let's go ahead and destructure data from A8 UT api. upload files from URL and pass in the temporary thumb nail URL and just return back data there we go and in case we have an error we can just
throw new error can I pass that here I can't error do message maybe we can do that there we go or simply if we don't have data we can go ahead and say bad request there we go so now this will uh be the uploaded file data so maybe we should just call it uploaded thumbnail and then what we have to do is let's see uh so in here we confirmed the existing video so we have that here what we are supposed to do now is we are supposed to do the cleanup so let's do
the cleanup now await context run and this this will be uh Delete or let's call it thumbnail cleanup or clean up thumbnail so we stay consistent with our names here asynchronous and what we're going to do here is Define another UT API perhaps we can just move this you know here in the beginning because we're going to use it throughout so we don't have to Define it many times so what we're going to do is we're going to check if video has thumbnail key in that case await UT API delete files by video thumbnail key
that's what we're going to do and then await database update videos set thumbnail key to null and thumbnail URL to null like this where and and pass in our to equals here we can just copy this there we go and after that this should be fine and one thing I actually want to do I want to move this step before we upload the thumbnail that's what I want to do so I first want to clean up our old ones in just in case you know this never succeeds and uh then we're left or all or
you know this succeeds we upload uh basically I'm failing to explain what I mean uh I first want to do the clean up right before I upload anything new I want to delete the old that's it that's what I wanted to say so only then are we going to go ahead and upload new things here and finally update video this can stay the same and this time we're changing the thumbnail key to be uploaded thumbnail dokey and thumbnail URL uploaded thumbnailurl uh there we go that's the workflow perfect so just confirm that it's called thumbnail
and confirm inside of your procedures here in the video that you go to workflows SL thumbnail so this will be quite interesting I'm not sure which is the better resolution this one or this one I I never know what's the height and what's the width of these things let's try one right so I'm going to go ahead and just try a very very simple one an image of Swiss Alps let's try that background job started this may take some time so we also need to like close this thumbnail let's work on that while we wait
for our thing to happen so thumbnail generate model here we're going to go ahead and do the following let's do form reset here and let's do an open change false there we go that's going to happen in the future and let me just see there we go so run has started it got the video and now it's generating a thumbnail I'm very interested in this because this is not like my source code I'm using a different model here so I'm not sure if this will work uh if it doesn't I will just show you exactly
what I have in my source code so we're going to see what's going on here I'm now going to pause and wait for this to finish so it seems to be working let's go ahead and see what it created I will refresh this and let's see if we have a thumbnail or not looks like we have a new thumbnail and it's so cool amazing look like that was the correct uh resolution perfect so we can now use AI to generate thumbnails as well and here's what I'm interested in it's my upload thing dashboard that I'm
interested in is it getting Pro properly cleaned up so I'm going to go ahead and go inside of files here uh and looks like I have some things left here but I'm not sure if this were in this might have been left because of the 24hour deletion thing because remember I did delete some videos from directly from my database which doesn't trigger the VB hook and then things get left inside so this is how I'm going to test this one more more time I should have done this in the beginning what I'm going to do
is I'm going to remove all of my videos from my database and I need to have my drizzle kid Studio running here so let me just refresh this I want to test it out because I want to make sure we are not doing any mistakes here so I'm going to remove all of these videos here I'm going to go inside of upload thing and I will remove all of my content here I'm going to go inside of Max and I will to remove all assets here simply so everything that I have is synchronized and this
one as well and now let's go ahead and try this again so into my content which is now completely empty I'm going to go ahead and I will simply upload a video what should happen now is that in a few moments an image should be generated and up loaded here there we go animated.gif and thumbnail JPEG and if I now refresh this I should see them here as well and now let's go ahead and let's try to generate another one so an image of aquarium maybe we can try that and let's click generate and one
thing that we forgot to do in the thumbnail generate model is disable the button while this is going on so disabled it will be generate thumbnail do is pending like this there we go and now if we've done this correctly I should have another run started here and after generate thumbnail we have the the cleanup or the cleanup already happened no so it's working on generate thumbnail now uh yeah it's first generating it and then it's cleaning up okay so it's working on that now what I expect to see is that the thumbnail that jpeg
gets deleted that's what I expect to here uh to see and get replaced with my new one so let me refresh again looks like it takes around 34 seconds so this is a pretty good example of a long running task right to generate a thumbnail it seems to take 34 seconds I'm pretty sure this is outside any time out limit uh on some of the more popular hosting providers so this function would fail on majority of them but thanks to our background jobs you won't have to worry about that let's see if we've done this
correctly or not there we go thumbnail has been replaced and now we have this newly created uh AI aquarium image right here amazing so in order to wrap up this chapter and in order to wrap up uh form section in general let's go ahead and let's build a skeleton component so that when we load here it doesn't have this you know loading Dots here so I'm going to go ahead and close everything here and go inside of form section and we're going to go ahead and build the proper skeleton here so just go ahead and
import the skeleton of course if this doesn't matter to you you don't have to do this you know I want to give you a proper project I want to give you exact thing I've built but if you think that uh you want to focus on some other things you can just speed this part up or just copy it from the source code so what I'm doing now basically is I'm just looking at what I did how I built the original one right and then I'm just kind of mocking this elements with skeletons here's a hint
AI is extremely useful when it comes to creating these things so feel free to ask AI to use it so I'm adding a space to and this is like the title and the description and then I'm just going to add one big skeleton below that so let's see how this looks for now there we go not below that next to it so I'm just representing the button no need for both the button and the little drop down now what I'm going to do is I'm just going to copy like my grid structure here grid grid
calls one and on large grid calls five Gap six then I'm going to open My First Column here then I'm going to close it and then I'm just going to go ahead and add the first element of the form so this would represent the title and its input and then below that I'm going to represent oops let me just add it here I'm going to represent the description and it's Tex area there we go and you know you can choose how granular you want to go with here so now I added a new one and
in here I'm representing like the thumbnail and in the last one here I'm representing the category selection tool there we go so that part is finished so if you want you can leave it like this or you can go ahead uh and create uh another side so I'm going to go ahead and do that here and I'm going to go ahead and open like this gray box that we have which renders our video right so now when I refresh uh the Box should appear here I hope I did everything correct here I think it's just
not visible because there's nothing inside so now I'm just going to add a skeleton with an aspect of video and this will represent like this video here here there we go that works fine and then below that I'm going to go ahead and open a div where I'm going to put all of this statuses right so this part will represent the video link like this then below that we're going to do the video status and below that we can do the track status which are exactly the same and all that's left is the visibility component
so we can do that uh one two three here like this there we go a very very detailed skeleton you don't have to build them like this of course but yeah try out AI uh it can help you with this kind of things it can speed up this process very very fast uh great so you've learned all about background jobs now and we've used up Stash's workflow which is their new product which which I really really like everything up stash builds is always a very good developer experience we know that already from the rate limiting
feature from the reddest feature and I'm sure this will only continue to go in that direction uh I advise you to also know explore uh more about background jobs I think they're a very interesting solution and I also advise you to maybe try for a challenge and change something for example inside of my what's the name web hook inside of my videos web hook right maybe change this this entire thing where I uh upload where I upload you know and extract the new thumbnail key and the thumbnail URL maybe move all of this to a
new workflow and leave it to be a background job right uh inside of here you would simply do await workflow do trigger and then you know just passing the URL to of course our process environment enrock blah blah blah slash API and then maybe slash videos and you can do I don't know what however you want to name this uh route it can be initial thumbnail or something like that right you can do that if you want to for challenge other than that we are finished with the form section and what we're going to work
on building next uh is this the video link so we're finally going to go ahead and see this video and then we can start building views likes comments and slowly wrapping up with the search results page and the playlist and I think that one more thing that I have here which I did not show you uh is that I keep this button disabled unless you change something in case that's what that's something that interests here you can go ahead and find that button and disable it if we are pending or if not form form state
is dirty right so when you initially come here you cannot save anything but if you change something then you can save it in case you think that's a cool thing to do uh great amazing amazing job and see you in the next chapter so just to wrap this up I love doing this part we've created the thumbnail prompt the generation workflow and we've added the skeleton and we' finished the entire form section great great job
Related Videos
Build a YouTube Clone with Next.js 15: React, Tailwind, Drizzle, tRPC (Part 2/2)
11:59:06
Build a YouTube Clone with Next.js 15: Rea...
Code With Antonio
23,504 views
Next.js 15 Crash Course | Build and Deploy a Production-Ready Full Stack App
5:23:11
Next.js 15 Crash Course | Build and Deploy...
JavaScript Mastery
995,048 views
lofi hip hop radio 📚 beats to relax/study to
lofi hip hop radio 📚 beats to relax/study to
Lofi Girl
Build and Deploy a Multi-Vendor E-Commerce Platform with Nextjs, React & Stripe Connect (2025)
11:16:34
Build and Deploy a Multi-Vendor E-Commerce...
Code With Antonio
54,115 views
Build a Complete Project Management Application | Next.js, Prisma, Auth0, Tailwind.css
6:43:02
Build a Complete Project Management Applic...
EvandroDotCom
7,558 views
Trump Causes Uproar at Pope’s Funeral, Approval Rating Lowest in 80 Years & Santos Going to Prison
14:37
Trump Causes Uproar at Pope’s Funeral, App...
Jimmy Kimmel Live
812,910 views
Build and Deploy a Full Stack AI SaaS with Next.js 15, React, Stripe, Tailwind | Full Course (2025)
9:20:29
Build and Deploy a Full Stack AI SaaS with...
Ankita Kulkarni
57,354 views
Musk demoted as he loses over $100 BILLION in 100 Days: Ari Melber’s report on Trump slump
20:08
Musk demoted as he loses over $100 BILLION...
MSNBC
741,839 views
Build and Deploy a Multi Vendor E Commerce With Nextjs, React & Stripe Connect (Part 2/2)
8:39:05
Build and Deploy a Multi Vendor E Commerce...
Code With Antonio
9,782 views
Strapi 5 and Next.js 15 Full Stack Project Course
6:22:15
Strapi 5 and Next.js 15 Full Stack Project...
freeCodeCamp.org
50,006 views
Next.js 15 Full Tutorial - Beginner to Advanced
6:54:09
Next.js 15 Full Tutorial - Beginner to Adv...
Codevolution
96,374 views
Build a Real-Time Google Docs Clone With Next 15, React, Tailwind (2024)
10:00:12
Build a Real-Time Google Docs Clone With N...
Code With Antonio
312,429 views
My Biggest Tutorial Ever (Build A FULL Google Drive Clone with React, Next, TypeScript and more)
3:27:34
My Biggest Tutorial Ever (Build A FULL Goo...
Theo - t3․gg
163,320 views
Build a Complete SaaS With Next.js, Tailwind, React, Hono.js (2024)
12:00:00
Build a Complete SaaS With Next.js, Tailwi...
Josh tried coding
111,154 views
Build an Enterprise Nextjs Rental App | AWS, EC2, Cognito, Shadcn, RDS, S3, Node, React
12:19:35
Build an Enterprise Nextjs Rental App | AW...
EdRoh
105,655 views
Build A Course Platform LMS With Next.js 15, React 19, Stripe, Drizzle, Shadcn, Postgres
8:36:12
Build A Course Platform LMS With Next.js 1...
Web Dev Simplified
77,423 views
The Most Dangerous Building in Manhattan
33:39
The Most Dangerous Building in Manhattan
Veritasium
6,647,377 views
Next Auth V5 - Advanced Guide (2024)
8:00:57
Next Auth V5 - Advanced Guide (2024)
Code With Antonio
988,086 views
I Built a Working PC using only Broken Parts
29:34
I Built a Working PC using only Broken Parts
Linus Tech Tips
648,867 views
AI SAAS Realtime Video Sharing + Desktop App: AWS, Cloudfront, Nextjs, Electron, Express, Socket.io
16:53:10
AI SAAS Realtime Video Sharing + Desktop A...
Web Prodigies
207,692 views
Copyright © 2025. Made with ♥ in London by YTScribe.com