Build a Fullstack App with Authentication & Rate-limiting in NextJs 14

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
we're going to build an AI blog generator using open Ai gp4 and Del 3 models for text generation and also image generation we're going to use superbase to store our blog posts but also as a storage to upload our images we're going to obviously use NEX js14 for our front-end application we're going to add authentication and user management using clerk with some drop in built-in components and at the end we're going to expose an API Gateway so other developers can also use our apis now let me show you how this works and then we're going to jump into the code and build this from scratch step by step now I've already generated some blog posts with images based on simple prompts here for example as you can see some of which they're actually really valid blogs as you read through them uh the importance of exercise for example very good points here uh but let's actually create a a Blog here on this spot together we need to be signed in so I'm going to click here here this opens up a model for us to sign in this is a drop in component from clerk I'm going to select my account I'm going to be redirected back to my app this time signed in I have this user button which is going to give me user management we're going to see this in action uh later on in the video step by step but let's actually provide a prompt here let's say Tesla uh trucks I've never tried this before so let's see how this this turns out now what this is going to do is going to send this prompt to open AI gp4 to generate a blog post it's going to then provide that block post to the Del 3 Model to generate an image that suits this block posts it's going to then upload this image to superbase storage get a URL back and save this block post to our superbase database and then once all of this is done it's going to redirect us back uh to that specific blog post page page where we can read the blog and see the image that's through nextjs server actions which we're going to dive together and see how it's done and we have our block post ready here with the image that was generated and a 200 about 200 W block post over here now when we go back we should also revalidate the path or the index to see this block post that was just generated here inside of our homepage we're going to start with a high level architecture of our application and the different pieces ofch technologies that we have we're going to then move to the backend set up our superbase database we're going to then move to the nextjs front end and connect the two together we're going to add authentication and then at the end we're going to add the API Gateway through zlo that allows us to also expose this functionality to other developers so let's just start from the architecture uh from a high level we're going to have superbase as our back end for database to store our blog post but also as a storage so we can upload images that that is coming back from open AI uh into superbase and get a URL back now our front-end application is also nextjs we're going to first create a server action that talks to open AI directly to generate these text and images and then save this up into superbase now at the end we're going to add an API Gateway using zupo that allows us to expose this API to other developers so they can sign up get an API key to authenticate and send a prompt to get a blog post with an image back so instead of having to write all of that logic inside nextjs server side or server actions we going to move that logic to our API Gateway or the API layer to talk to open AI to generate these block post talk to superbase and then return return the block post to whoever it's calling actually our API it may be our own front end NEX application or other developers so with that out of the way let's just move to the back end I created this list so I can keep track of everything that we need to talk about so I I'm not going to miss anything so the first step in creating our back end which is going to be a super base is for you to create a superbase account if you don't have already create a project and we're going to then create a blogs table with the following column so every blog post is going to have an ID a created at time uh which we can use to then sort this blog posts a title this is going to be the prompt that the user have submitted now the content is going to be what's returned from gp4 text generation the image is going to be what's returned from Del 3 and the user ID is the authenticated user inside of our front-end application actually making this request so let's go to super base I've already created this but let me just show you how it works so once you sign into your account you can go ahead and create a new project here I've already done so once you're inside of your project on the side table here you can see this table editor you can go ahead and create a new table give it a name I've named mine blogs and then down here you can add your keys the ID and created at fields are already added here for you can just go ahead and add the title the content the image and the user ID all of each out of type text or string and then once you had done that you could just click save and then uh your table would be ready I've already created this blogs table with the same field and I already have some content over here so that's the first step of creating our table now we need to create a new storage bucket and make it public this is going to be for us to upload our images that are returned from open AI so going back to our super base account if we can go down here to storage you can go ahead and create a new bucket give it a name and make it public making your buckets public would allow other users to read objects from your bucket without any authentication but they would still need to authenticate if they wanted to upload or delete any image now this is going to help other developers who are using our API through our gateway to be able to use the image URLs that we are returning from our superbase storage I've already gone ahead and created a blogs storage as well and I have a couple of images that was generated from Del already and stored inside of my database with that out of the way let's go back so we have already completed this so let's close our back end now let's jump into the front end nextjs application so you can go ahead and create a new nextjs application or use the starter template that I've included in the description so this is going to be an xjs starter template which is going to take you to this repository you could just click on this use this template it's basically using typescript Sal CSS and some prier plugins I do have a video on the channel where I talk about how I set up my nexj applications I'm going to include that also in uh the description so you can have a watch there now once we have done this let's go back to our list so we have created our nextjs app as I mentioned I've already done this I have my nextjs app running on Local Host so let's open this up and all I had done is to replace the content of our homepage to this next AI blogger nothing else has been added and we're going to build this from scratch together so we have created our nextjs app now next step we're going to use chat CN UI library for some components so let's add this to our project inside of the docs you can go to installation nextjs and then you can run this command if you're using npm or npx you could use it or copy whatever package manager you're using I'm using pmpm so I'm going to copy that code I'm going to then stop the dev server copy this code and then initialize shaten this is going to install radx UI which is what chaten uses behind the scene and some other dependencies it's going to also uh help us set up our application so it's asking for this style I'm going to choose default style CSS variables we're not going to use CSS variables or theming for this application you can go ahead and use it if you are actually using theming in the application okay it seems to be done so let me just restart my Dev server close this page and go back now as you can see running the script has modified my tailin config by adding some animations and key frames to it it has also added this components Json which tells shaten the structure of our application inside of this lip we have this CN function that uses telin merge and clsx package to sort and merge our telin utility classes when we using shatan component we also have this component folder where we're going to add our UI component as we move on so let's go back to our list we have added chat to our project now let's create a superbase client so we can talk to our database now to be able to do that we have to set some environment variables so let's just close this off go back and create a env. local and now here we need a superbase URL so I'm going to copy this here and then we need a superbase service key roll so let's also copy this here now I'm going to also create an example file here so let's go ahead and create a n. example where I'm going to show everything that we have to create over here as well now to get these values we need to go to our super base account inside of our settings we can go to API here we have the pro project URL so you can copy this over here that's our superbase URL and then down here we can see this service roll key this is a secret key you should not expose this to your front-end application so you can copy this and bring it to yourn okay I've copied my secret key let's go back to the next step so we have set our environment variables now we need to create a client and add some functions that talks to our database so I'm going to create a file inside of our lip folder I'm going to name this superbase TTS and let's copy some code and I'm going to explain what we're doing here now before I go ahead we need to install the superbase JS package so let me just stop the def server here and then pmpm add at superbase Super BJs this is going to allow us to create a superbase client and talk to our database let's restart the server come back here now here if you're using this create client function from the superbase package passing in our credentials to create a superbase client that allows us to talk to our database I then created two simple functions to get blog by ID and get all blogs the get blogs by ID gets an ID of type number it then connects to superbase using this client and selects from our blogs table row that has the ID equal to this ID that I'm passing in and Returns the single or returns that specific resource and then they get all blogs function is going to use superbase to connect or fetch all of the resources inside of our blogs table and it also sorts them or orders them based on the created ad field so these are the functions that we need inside of our application so let's move to the next step which is our application shell basically adding a header component so let's go back I'm going to close this go back to our application open up our app router inside of our layout this is where our root layout lives we want to add a header component so let's go ahead inside of our components I'm going to create a header component. TSX so let me copy some code here and explain what we're doing so I'm creating this header component and a div with a container and flex going to render a logo and a signin button now we need to create this button from shaten so let's go to our shaten SC scrolling down to the button here can just go ahead and run this command to install or copy the code for the button so let me just go ahead and do this I'm going to stop the dev server again and then copy this code let me restart the dev server you also need to create a logo component so let me just go ahead and inside of our component create this logo. TSX which is basically some SVG logo component that we just rendering here now we can go back to our layout and bring in this header component let me close this up inside of here so I can have my Heather component and then I can wrap the rest of our app inside of this main tag so let me just cut this out and bring it here and that's all I need for now let's save this up go back to our application let's refresh the page and we should be able to see the header component up top there you have it we have our logo and also our signning button pretty straightforward so let's go back to the next step now we want to work on our homepage so let's close everything here and go to our homepage so here we want to add a form component that's going to allow us to take a prompt or an input from the user to then talk to open AI so let's go ahead and create a form component inside of our component I'm going to add a form so let me copy this code over here and see what we're doing so we're using the card component and input just rendering a section and inside of a card component you're going to have a Heather so we have a title for our card and some description and then instead of the card content again this is a shat and component if we going to look at it in a second I'm rendering a form with an input that takes in a prompt or a text input from the user and then a submit button to actually submit this to our backend now let's go to shaten and install the input and the card component so down here we have the card component this is going to render a simple nice card go ahead and copy this command from here let me just stop the this Dev server I'm going to copy this code while that's done let's also go to our input component and bring in the code for in installing our input so I'm going to select the pmpm code again here too just clear this out and restart our Dev server now going back to our application we should be able to see that form popping up here now we haven't brought the form back inside of our page so let's go ahead and actually import it here so we're going to say form and now if I save and refresh we should see this form popping up here now let's go back to our form down here now to handle this form submission we going to use a server action which are now stable in next ch 14 so instead of the traditional way of creating an API endpoint and then adding an event handler to this form to submit the data to our API where we can talk to open AI models and our database we're going to accomplish all of that with use of server actions without having to create that API endpoint so I'm going to create a client side action from where I can then call my server side action this is going to allow me to handle errors so let's go ahead and create an async function I'm going to call this action this is going to receive form data by react this is of type form data and then from inside of here we're going to say prompt equals to calling this form data. get and I'm going to pass in the prompt this is going to map to the name attribute that we have set on our input let's add a simple validation here so if there is no prompt past uh let's just show a toast notification so let's go ahead and bringing the toast component from shat CN that's using soner I've actually talked about this before on the channel so this is a very nice notification that you can use all you need to do is to run this command which is basically going to add soner under the hood then you need to add this toaster provider to your layout so you can just call this toast function later on to show this toast notification so let's just go ahead and add this one too I'm going to pause this and then we're going to add that component so let me clear this up and restart the dev server and now we have to add this component inside of our layout so I'm going to go to my layout let's just import the component up top there and now down here I can just go ahead and copy this toaster component or provider inside of my body tag I've already passed in some options so I position top right this is going to show the notification on the top right corner I have a theme for my notific ification and I'm using the rich colors you can go through the docks uh there's actually a link to uh the original docs from this component from shaten that you can see other modifications that you can make to your toast component now this is going to allow me to call the toast function to actually show the toast notification let me also import this toast function from soner up top now that we have this basic simple client side action let's pass this to our form so inside of our form we're going to pass this to an action prop we're going to set this to this client side action and then from here if we do have a prompt the idea is to call our server action which only runs server site where we can safely connect to our database or talk to open AI models so let's create This Server action next so let's go inside of our app router I'm going to create an actions. TS file where we're going to use the use server directive this is going to to instruct nextjs to only run this code on the server where we're going to export an async function I'm going to call this create completion this is going to expect a prompt off type string let me just close this off so we have more room over here now again I'm going to check to see if I have no prompt so a simple again validation here I'm going to R return an object with an error property that says prompt is required now as a side note you can return any serializable object from your server actions and that's how I'm handling errors in this case so now that we have this create completion server action all kind of it's still incomplete but let's go back inside of our form and actually call this function here we're going to destructure the error from it it's an a synchronous function so I'm going to evate and I'm going to call this create completion that we just created inside of our actions and it does require a prompt okay so we get this prompt that we created here and pass it down here now this prompt is coming in from our form data and to make typescript happy I'm going to pass as a string here because this prompt technically can also be a file again from the form data inside of your file you can have an input of type file uh this is to say we know this is going to be a string now from here if we do have any errors I want to show a toast notification again so toast. error and I'm going to just pass this error out so let's just save this up and if I remove this check over here so we're not checking inside of our client side action whether or not we have this prompt we're going to call our create completion server action this is going to take that prompt and check to see if you have it and if we don't it's going to return that error so with this going back to our application if I go ahead and refresh the app and submit this form without any prompt I'm going to again get this TOS notification that says prompt is required but this time this is coming in from this error message that we returned from our server action okay great so let's continue moving on this is where we would want to actually generate a blog post using open a I and then we would also want to generate an image again using open AI we then need to upload the image uh to superbase storage and from here we're going to create a new block Post in superbase using the information that's returned from open AI so let's go ahead and open the open AI documentation there's an introduction and a quick start guide where you can understand how to use the SDK to talk to different endpoints or models in openai you can also come down to this text generation tab where it explains different models that you can use in open aai to generate text and there's a little code snippet here for using the open AI SD K which is a wrapper around their API or rest endpoint for example here we can use this chat completion endpoint uh to create or generate text based on this messages or prompts that we pass in and the specific model that we want to use so let's go ahead and install open AI STK so I'm going to stop the development server here and pmpm add open AI I clear this out restart the Dev server close this up let's create our open AI client here so we're importing this open AI from the package we just installed and using it we're going to create a new client and we have to pass in an API key and we're going to use this client now to talk to different endpoints in API now to get your API key you can just go back to the documentation here on the settings tab or on this sidebar you can see this API Keys you have to go there create a new API key if you you don't have to and then bring it back to your n.l and this is what I'm going to do here so I'm going to open this up going to the example that we have so this is another API key that we need to provide okay so I've copied my API Keys now we can use this open AI client to generate some text so let me copy some code and explain what we're doing here if you're using our client to hit this chat completions end point we need to pass in an array of messages here that have a role of whatever the role is it can be a user or the assistant or the system when we are sending messages it's either the user or the system the system is used if you wanted to give some instruction to uh the model in this case gp4 and then the user is actually the prompt that the user plugged in and the assistant role will be the response that comes back from open Ai and if we have any completion and the content inside of it it's going to be stored in this content if not we're going to again similarly return an airor object with this error now let's also go to the image generation you can see here we can use the same client that we created and this time we're going to hit the create image end point to generate some images using the D E3 model so let me just copy that code over here as well so in the next step you're creating an image and the prompt this time is to generate an image for a blog post about the same prompt that we are getting in from the user same prompt that we use to generate a blog post we're specifying the model and is this number of images you want to the model to create it's one you can pass in a size and there are two different response formats you can get back from open AI as I just briefly mentioned in the beginning you can get a URL back which expires after I think an hour or you can get the raw data image uh which then you need to upload to your own storage so it just stays there forever and then you would store the URL to your own storage in your database so I'm getting the image data out of this base 64 Json response and if there is no image data again I'm returning an error object so now that we have the image data let's actually go ahead and upload this to superbase so I'm going to use superbase storage again we're going to use the superbase client we created earlier so let me just export this from our library you can actually upload a file in two different ways one way would be the file that you get back from a form submission let's say you have an input type file inside of a form you get that form data back you can just pass that file from your form data to the upload function of your superb storage the other way is to pass in an array buffer and this is what we're doing here so we're using this decode function you're going to install a package that does this for us which decodes a base 64 raw image in to an array buffer which we can then use to upload our image to our superbase now going to superbase documentation for a second here if you scroll down to client libraries this JavaScript library is the one that we are using here scrolling down you would get to the storage and here you can upload a file now you can use a super basis storage as I mentioned to upload a file that's coming back from a form submission as you can see here event. target. files with a specific destination and a name but you can also use the this upload functionality to pass in an array buffer which you're going to decode out of the base 64 raw data that you're getting uh back from open Ai and then pass this into that specific folder and this name or destination so that's exactly what we're doing inside of our code we just need to install this base 64 to array buffer package that helps us just do this decoding so let's stop the def server and pmpm add the package let me clear this out restart the def server I'm going to copy this line and bring it all the way up here so I have access to this decode function okay so if we've gone ahead decoded our image the raw data that we got back from our open AI endpoint we're going to upload that to our super basis storage and we're going to get the data now this data is going to have the path where we have actually stored our image inside of our superbase storage so from the data we're going to get the p path and we're going to reconstruct the image URL where this image actually lives uh in our storage so we can access it or read it from this URL so we're getting the superbase URL this is our project URL and your images would live at storage version B object public blogs is the name of my bucket and then that specific path which refers to this image we just uploaded here now that we have this data we can actually go ahead and insert a new block post into our super base table called blogs we're going to insert a new object or a new resource the title that's the prompt the content that's returned from uh gp4 the image URL is what we constructed here coming back from our storage and then the user ID cuz remember our table is supposed to have a user ID but at this point we don't have any authentication so I'm just going to pass uh string of 1 to three here so we have the required Fields next I'm going to call this select function so it returns this very insert inserted row back to me so I have the blog data here or an error again similarly if I have an error I'm returning this object and if you go back to our application and test this out let's refresh the page everything should work let me also open the terminal so we can see the response coming back so I'm going to say let's say generative Ai and see what this says so I've gone ahead and submitted the form right now there's no indication for us to know if this has been submitted or not we just have to wait for this to go ahead and lock something inside of our uh console here so we got the response back here as you can see I have this block post inserted with an ID created at and the content in the format of markdown I have a title a user ID and then the image URL that points into our storage in super base I'm also getting this error here that they're trying to destructure error property of something that doesn't have any value so our server action is not returning anything once it is completed I'm just logging in here uh but inside here I'm just destructuring error out out of an undefined response so maybe we can just name this result and then down here we can check to see if result exists and if there is any error on it then we can actually log that or show that inside of our toast notification so we won't have this error but we also want to have want to improve this user experience where we would just add something here for them to know that they have submitted their form so let me just close this off so I have more space down here so going back inside of our form uh down here where I have the submit button I'm going to extract this button into its own separate component where I can use this use form status Hook from react Dom use form status and use form state are two new hooks you could use with your server actions and you're handling your form submissions one is to handle the form State and the other one is to handle handle the loading status or the pending state of your parent form that's why you need to have this inside of a separate component because this Hook is going to tap into the parent form I have a video on the channel where I explain use form status and use form State hook and actually tie that in with several actions in xjs so I'm going to link it somewhere in the description or above in the cart so I can watch that video if you don't know this hook but it basically is going to give us a pending state this is when our form is being submitted let me just also import this CN function and all I'm doing here is if we are in a pending state or if this server action is still working we haven't had any response back I'm showing a different text but I'm also using T and CSS to animate this button so the user at least knows that the form has been submitted and we working on generating this block post so now I'm going to just Swap this button with this submit and now my form is going to have some some loading State now going back to our action once we get this blog back ideally we want to send the user to that specific blog ID so let's get the blog ID here let me just comment this out or delete it all together so we're going to get the blog ID out of this blog object that comes back from superbase we're going to extract the ID out of it and we want to redirect the user to uh for/ blog and that specific blog ID so they can see the block post that was generated for them now let me also import this redirect function from next navigation and now we have to create this Dynamic blog uh page so let's create a blog and inside of it we're going to have a dynamic ID and then inside of it we're going to have a page. TSX let me copy some code and explain what I'm doing here now inside of this Dynamic Blog Page we are receiving the prams object from nextjs these are going to be the dynamic parameters in the URL we're expecting an ID because we named this Dynamic route an ID we receiving that ID we passing it to the get blog by ID function we created in the beginning of tutorial which we are using a super base function to just select a specific ID or a specific RO with this ID so if you're getting that specific block we're destructuring the content and the image URL the content is what came back from our gbt 4 model same for the image and we're using this markdown component to render this content because we asked for a markdown content back so I'm using react markdown which we need to also install uh to turn this into something that react can render and then we're using the image tag to render our image URL this is the URL to the image that lives on on our own storage and we have a link here to just go back to the index page or or to our homepage so let's go ahead and install the react markdown let me just stop this and then pmpm add the package let me clear the deck and restart the dev server here let me close this up and if we now go to our superbase database inside of our table we see this last row that was the generative AI title is created here and it has an ID of 28 so going back to our app if I now move to for/ blog for/ 28 I should be able to see this post generated here okay that resulted in an error we have to Define this host name which is our superbase project URL inside of our next config for next image to work properly so let's go back inside of our project inside of our next config we're going to add a remote pattern to our images there are different options you can pass in here but basically this is going to Define this remote pattern safe for nextjs to fetch or serve our images so let me just go ahead and stop the development server and restart it for this to take effect if I now refresh this page we should be able to see our blog post and here we go we have the image and also the content of our blog post now the content is not styled properly because I have to install the tailin typography package just to show you what I'm doing let's open up tail vind you can go to the docs and SE search for typography this is a you can install that allows you to pass in this Pros class to any parent element for example whenever you're are having a markdown file that you don't have access to style directly you could just wrap it inside of a parent with this Pros class this is going to automatically apply some T CSS to your uh markup so let's go ahead and actually install this package so let me stop the def server here pmpm ad and then as a development dependency if you're installing that and once you do that you have to also pass it into your Telvin components or Telvin plugins so let's go to our Telvin config over here and we can also pass this typography restart the def server let me close this back and go back to our application and uh once I refresh the page here we should be able to see Telvin styles appli to our markup there we have it now sometimes the content that we're getting back from our GPT model actually has some front matter because I have asked it in the prompt that I want the cont content of my blog post back in markdown format it actually goes ahead and provides some front matter to it too you can be more precise and play around with the prompt so therefore it doesn't have any front matter or you can scrape this out of your content before passing it down to this markdown component uh but for now I'm just going to leave it as is now going back to our homepage let's actually add a recent blog section where we're going to fetch all of the generated blogs and show it down here so let's go back to to our homepage and inside of this section I'm going to add this div where I am going to fetch the blogs using the function we created in the beginning and some card components to show our recently generated blogs so let's go ahead and import what we need here so I'm going to import the card component uh from shaten for the blogs let's actually go ahead and fetch our blogs over here so I'm going to say const blogs equal to await calling the get all blog function that we created in the beginning of the tutorial I'm going to turn my function into an async function again sever components can be turned into async functions you might have already known that by now uh so that you can just fit inside of your react server component and now I need to have the next link I'm going to explain what we're doing here in a second let me just import this component from next image and now this format date is a function that I'm going to create that formats the date that was create this blog post was created so inside of our lip I'm going to inside of this utils I'm going to add a function that just takes in a date string and formats it in a nice way so let's close this up going back here I can now import this format date from our lip let's save this up so what we're doing here is inside of our page component we calling this get all blocks function getting all the block posts and now down here I have this recent block section where I'm getting all the blocks mapping over them and for each one I'm rendering this card component from chaten I'm including a link that takes me to that specific blog blog ID individual or dynamic page I'm showing the block image and then uh the title of the block and also the date it was created so as you can see here this is populated here this is our most recent generative AI one with the date and also uh the rest of the blog post that I have generated before so let's test this end to end one more time let's generate a block blog about let's say JavaScript now immediately this turned into working on it and with the animation that's using the use form status hook we hook into the parents forms loading or pending State this improves uh the user experience because then they now know that we have received the request we're working on generating the block post once the block post is generated we are now redirected back to that individual blog post where we have our content as well as our image but watch what happens when I go back to our homepage now back in our homepage we don't see this uh recently generated block this is because inside of our homepage where we are fetching this block we're using a server component this is cached by default so we are serving up the cached version of this page and we do not have that most recently generated block post now to get around this or to fix this inside of our action when we actually creating this block post before redirecting we're going to use the revalidate path function from nextjs and revalidate the cache for the path which is our index our homepage so therefore it ref fetches our blog posts from superbase and rerenders this page so we always have uh the most recently generated blog post so let's let's save this up and with this if I just refresh the page uh we should be able to see that lastly generated blog post there we have it now let's test this one more time I'm going to say typescript this time okay great so we see our blog post created here with an image also with a title and some text content and now if I hit back and go back to our homepage we should be able to see that typescript blog also created here because we revalidated the cache for our index route this is going to rerun the page component which is then going to call this function again fetch the blogs from superbase and render this page so we have the functionality of our app working but right now we don't have any authentication so anyone can come in and hit this endpoint and start generating these blog posts now what I want to do is I want to protect this form submission behind authentication I still want users to be able to come to homepage or go to one individual blog post but if they want to go ahead and generate a block I want to protect this behind authentication and for this we're going to to use clerk now clerk is an authentication provider for react and nextjs applications it has built-in dropping components that you can just drop into your app and easily hook your uh application with clerk backend so I'm going to sign into my account once you create an account here you can go ahead and create a new project I've already done so but once you create a new project you're going to be landing in a similar page like this you can go ahead and copy your credentials to your environment bar Ables in your nextjs app to be able to connect to Clerk and from here you can just click on this button if you're selecting nextjs up top as a library you can continue to the docs where it walks you through simple steps for you to be able to connect your nexj app to clerk let me make this a bit bigger so if you need to install the clerk nextjs package let me go ahead and select pmpm and I'm going to copy this coming back to my application I'm going to stop the div server and paste this code in now the next step is to set your environment variables if you have logged into clerk this is going to populate down here as well so you can copy it from here to your local environment variables in nextjs I've already done that so the next step is to wrap your app in a clerk provider component this is a context provider so clerk hooks can access the session data so what you need to do inside of the app router you can just bring in this clerk provider component so let's go back to our app component inside of here we have the root layout and now here I can import this component and all we need to do now is to wrap our entire app with this clerk provider so let's come down here what I'm going to do is I'm going to come inside of here and say clerk provider I'm going to close this off and wrap my body with the clerk provider now one thing I want to mention let me just close this off now typically with this provider patterns when we are sharing react context inside of the app router we provide this provider client component where we can use react context because it's a react hook you would need a client component for that matter and then we bring in that provider into this file and wrap our application or root layout with that providers now for clerk they've already done so so this is internally a client component that's using react context that now wraps your app so you don't need to have the workaround of creating your own client component because this is already done so you just need to import it directly into your server component into your root layout and wrap your whole application your whole HTML or in this case uh the body tag with this clerk provider now the next step is to add a middle ver to then require authentication and pass in what paths you want to be protected or not so you can add in a middle ver to your nextjs app using this off middle ver function that comes back from clerk you can then decide which paths you want it to be authenticated so let's go ahead head and inside of our root layout I'm going to create a file called middleware TTS I'm going to copy some code here let me explain what we're doing here now this off middle ver by default if you don't pass it any option is going to protect all of the routes and pages in your app but this is not the case for our app I want users to be able to go on the homepage I want them to be able to also visit individual blogs that have been generated by others I just want to protect that form submission where a user wants to generate a block to be protected so therefore I'm passing in this option where I'm saying my public routes needs to be basically everything now you can go to this middle ver reference page to see what options you can pass in here so the default Behavior as I mentioned is to protect all of the pages but you can uh exclude some pages as public routes from this off middle you can pass in an array of paths for example here it says the homepage and the contact page should be public it still runs this middle ver on all of the pages so you have access uh to the session object and whether or not the user is logged in on all of the pages but it's not going to protect these two pages against or behind authentication you can also pass in local so you can read through this for more information I do also have another video on the channel where I dive deeper into clerk if you want to understand different options going to link that one also in the cart so you can dive deeper into clerk but you can see down here we also have this example where we're looking at the request URL to see whether or not it includes the word or for/ dashboard if it does we are returning false over here which just makes this path protected and anything else public but in my case I wanted to make everything public so I've just returned through from this public route there different options or different ways you can do it this is just what I came up with the config matcher this is for Middle in xjs generally speaking you can decide or match the paths you want this middle ver to run on this is going to run on every path uh besides your static Pages your underscore next uh and it it is going to also run on the API trpc routes so you can pass in different types of matches different regular Expressions here to instruct nexts for what routes you want this middle ver to run okay so let's go back to the previous page we have added our middle ver now the next is to actually provide some of this drop in built-in component from clerk such as this user button or signin button that gives the user way to sign in and sign out and actually access their profile so let's go back to our application let me close everything off and now I'm going to go to my header and I'm going to actually replace this with some clerk components so now I'm using this component from clerk to render a signin button if the user is signed out and a user button if they're signed in now let me explain these two components we have this signed in components and signed out components this is called control components in clerk it just renders the content of whatever you pass to it in this case if the user is signed in and here if the user is signed out now going back to the clerk documentation under this fifth step we have a link to a set of pre-built components these are dropped components from clerk you could use in your application there's UI specific components such as the sign in and sign up components this is useful if you wanted to create your own custom signin and sign up page actually if you go back to the same guide and scroll down you can see another guide that walks you through creating custom signin and sign up pages and basically you need to create these different routes and drop in this sign up and signing component and now you have a custom signning and sign up page now going back to our pre-built components again we have some control components such as this signed in and signed out this is to conditionally render different components depending on whether or not the user is logged in we have the sign in button which allows the users to sign in and we have some user specific components down here such as the user button or user profile the user button is going to allow the user to manage their profile or sign out of our account and all we're doing here set of our app is you're rendering a user button if they're logged in and a sign in button if they're actually signed out now this sign-in button by default is going to uh redirect the users to your signin page but you can also pass in a mode of model which is going to open up a model instead of redirecting to a different page and again by default it's going to render a button but uh you can pass in any children that you want to this sign in button here I'm just passing my shat CN button component so that um it looks the same as uh other components in our app or that I can have control over how I want to insty it now if I save this up and go back to our application we should be able to see this signin button there but this time our signin button is actually hooked up with clerk so if I click on it it's going to open up a model for me to be able to log in and if I go ahead and log into our service it's going to redirect me back to the application and show this user button this is this show user button that we are showing here conditionally with this signed in control component if I click on this I'll be able to manage my account this is all done with clerk it's a beautiful UI for users to manage their profile we can also sign out of our account now the last step in our authentication is to protect this form submission from unauthenticated user so let me close this off and I'm going to go back to my form where I'm rendering this submit button now instead of having a submit button all the time what I want do is I want to use clerk components so let me just uh also import this signin button from Clerk and sign out control component from Clerk and also this one so let me save this up now if you're signed in I'm going to render a regular button of type submit that allows you to submit this form but if you're signed out using this control component I'm actually going to render a signin button from clerk so this is not of type submit is of type button and this is important to set because if you don't set it by default any button would be type submit and then it is going to submit the form instead of opening up our uh authentication model so if you're signed out I'm just showing uh Clerk's signin button so let me just go ahead and log out here now this button sign in to start is going to open up the same model so it's not going to submit our form so it's going to protect this form against un authenticated users but what we can do to further protect our endpoint against unauthenticated users is to go inside of our app and our action and actually check to see if the user is authenticated inside of our server action so I'm going to import this o function from Clerk and I'm going to use this o function down here inside of my server action to return if the user is not logged in now this o function is going to return an authentication state that has the user ID on it so we can check to see if there is a user ID they're authenticated if there is no user ID we're going to return this error that says user needs to be logged in now we can then use this user ID to pass it down where we were creating our blog post here so we no longer need to pass in one to three now we can read the user ID from the authenticated user so let's save this up and refresh our page now our endpoint is fully protected behind authentication on the client side we swapped this button with clerk signin button so it opens up a model if the user is not logged in instead of submitting the form and on the server side inside of our server action we checked whether or not the user is logged in using this off function from clerk okay so now let's go back to our list and see what we have done so we've created the homepage the form component client action server action we have installed open AI we have made the call and added the block ID the redirect the revalidation submit button toast component recent block so we've done all of that let me just close this up inside of our authentication we've completed all of these tasks now in the last step we want to add an API layer that exposes this functionality to other developers to use our API for example to hit our endpoint provide a prompt and get a Blog back now the functionality we have here is rather simple it's just generating a Blog but you can imagine that you have more you can add more or have more complex AI features that you can then expose via an API for other developers to use now going back to our architecture diagrams from the beginning of the video what we want to do from a high level is to add this API Gateway that sits like a middle ver in between our super base backend and any frontend consumer of our apis now we're going to move the logic of talking to open AI models and talking to the database to this API Gateway that's deployed as a middle ver to the edge now this is going to be the only authenticated party to talk to superbase and talk to our open AI account and all the other frontend consumers such as our own nextjs app or any other developer for that matter they can sign up get an API key authenticate against this Gateway and use our apis now as I mentioned we're going to use zupo to implement this so let's open up zupo zupo basically gives you an easy way to add API gateways it's very easy to add Authentication rate limiting with just a click of a button you can add policies to it I do have a video on the channel where I dive deeper into how you would set zupo up so let me log in to my account so I can show you the dashboard and specifically this project that I have created before I'm not going to start this project from scratch again if you want to learn this or if this of any interest to you you can just watch that video I'm going to link it in the card somewhere where I walk over what zupo is and how you would go about implementing it I'm going to show you what we need need to do here to just deploy an API Gateway that sits in between our front end consumers and our superbase backend so basically once you create a zupo project you're going to have a an endpoint instantly for example here this is my endpoint that anyone can use now to access my apis and the functionality that I'm exposing through this apis and I do also have a developer portal automatically created for me so let's just visit this together now this this is all generated automatically for you when you define your routes and I'm going to show you how we can Define our routes specifically in this case we're going to define a post route for developers to submit a post request to our endpoint pass in a prompt and get a blog post back but you can see here I have a create a blog AI endpoint let me just make this a bit bigger if you can see down here this is a post request to for slv1 blogs and I do have my end point the base URL up there it also tells them that you need to be authorized you need to pass in a request body it has to have a prompt and it has to have a user ID it's going to tell you what the example body would look like or an example response so all of this is created automatically for you once you create a route in zupo now the way you create routes in zupo is inside this route. o. Json this is based on open API specifications so here I have created a route it's a post route let me just make this a bit smaller so that you can see the full um page so inside of this I have created a summary this is the path V1 blogs uh the method is post you can Define different course policies now down here to handle the request you have different functionalities defined you can forward rewrite and redirect to different URLs or you can run a function this is what I'm doing here so I am defining this generate blog module and any request that comes into this endpoint I'm just returning or calling executing this function so let's see what this function does so inside of this modules folder I've defined a couple of modules this generate block is basically our server action so what I'm getting is a prompt and a user ID I'm going to call the chat completion endpoint from open aai and I'm getting the open AI from this openai module that I've created which is going to call this openai client and pass in my environment variables or my credentials and then I'm going to do the same thing with the image generation so same thing that as we had done inside of our server action now we are doing it inside here which is deployed to the edge the same run time as your middle vers run in nextjs and once I have the data returned back from my GPT models I'm going to store again it's a bit small I apologize let me make this a bit smaller it just goes off a screen so let me just do this again once I have my image back I'm going to upload this to superbase and then once I have the image uploaded I'm going to go ahead and actually create this block and all I'm doing here is to return this saved block post back to the color of this endp point so let's go back here so this was our generate block again the open AI is to create our open AI client the superbase module is to create our superbase client and this decode is the same decode function we had to run to convert a base 64 image to an array of buffer so we can upload it to um our superbase storage so going back to our routes one more time let me just go here so a request comes in I'm taking in a prompt and user ID in the request body generating this block post uploading it to my own back end and then sending the block back to the color of this ID now you might think well anyone now can call this endpoint it's not really authenticated or protected Against Behind authentication well this is where zup's power comes into play in this policies tab you can just easily add policies for example I have added this rate limiting policy or API key so if you go to add policy there are different policies you can add here for example you can add API key authentication this requires the incoming request to have a valid API key or the other one that I have here is rate limiting so I'm going to move this above that so I'm going to authen icate the user first and then I'm going to rate limit them so for example in this case I'm allowing only two requests per 1 minute so it's very easy to add policies to your requests and you can do whatever you want down here with handling this request now a quick note on this file also this is your open API specification this is how you define standard apis and this is what also Powers our documentation so the examples as you can see here I have uh defined that inside the request body of a post request that we have here we require a prompt and a user ID and I provided the examples I provided the required fields and also a response example that maps to this schema that I have created inside of my modules again in this schema I have this blog this is just basically defining the shape of our blog post so we can provide it to our documentation so the users of our API canot understand what they can expect now developers can come in and sign in here for an account they're going to have an API key which they can then use to talk to this API endpoint so let's test this API endpoint let me go back to our route over here let me make this smaller so I'm going to save the changes that I have made now once this finishes running it's now deployed and I can go ahead and test my API endpoint so as you can see here this is giving me an example prompt and a user ID well we are not passing any user ID here so we're just going to test it with this example one 2 3 now I'm hitting this endpoint and the endpoint is also there too but I'm not passing in any authentication so this should fail so let's test this out and as you can see this is 401 unauthorized because we are not passing in any authorization header Now to create an API key so we can authenticate against this endpoint you can go to your settings and go to your API key consumers I've created a next app user and an API key so I can copy this going back to our files to our route to this specific post where we can test I can just pass in an authorization header with a bearer token and I'm going to paste in my bear token now you can do this test from any API client such as Postman it's easy that you get this test functionality right inside of your panel so if I go ahead and now test this should pass the test go ahead and call the open AI generate the block post save it to our database and then return back to me the block post so I'm using this API as an independent developer to generate some block posts using Ai and get the block back you can see we have the 200 okay result here and we're getting our blog post back actually if we go back to our application we should see this blog post generated as well because you're hosting everything inside of our super base now this is the curvature of Earth we just created this and once we click on it we should be able to see the image and the content of this block post and this is how easy you can add an API layer or Gateway in your application to expose whatever functionality you are having to other developers you can furthermore add stripe through this API so you can charge developers per basis or per per usage but now that we have this API set up let's just go back to our application and actually update our server action to use our API Gateway instead of directly calling open AI or uh our super base so inside of our application I'm going to go in the app folder and create a gateway. TS file here I'm going to copy some code and I'm going to explain what I'm doing now I'm exporting the same create completion server action that requires a prompt it's checking to see whether or not there is a prompt and there is a user up until this point everything is the same but instead of calling open AI or super base directly all I'm doing here is getting the gate the URL this is the same URL that it's exposed to you uh from here from your zupo account or in the getting started tab you can see your gateway URL I'm going to get that I'm going to generate the endpoint which is for/ V1 blogs and then I'm going to send a fetch request to that endpoint with a method of post I'm going to pass an authorization header because we need to be authenticated against this endpoint I'm going to use the same API key I created earlier earlier inside of the settings tab the API consumer that we created I'm going to bring this into my next app I'm going to then pass in my prompt and user ID to my request body this is what my endpoint requires and this is going to give me a block back if I don't have the block or if there is any error I'm going to return this eror object from my server action just as I did in our previous server action and once I have the block I'm going to revalidate my index route or revalidate the cache and I'm going to redirect the user to the generated blog post now let's go back to our form and instead of getting this create completion from our actions I'm going to import it from our Gateway file this is just exposing another server action that instead of talking directly to superbase and open AI it's just now talking to our open API Gateway and with this if I go back to our application and go back to our homepage and refresh let's go ahead and sign in and let's go ahead and generate a new new block post about API Gateway this time through our own API Gateway deployed from zupo and here we go we have the block post generated with the image and the content but this time through our API Gateway so going back to our homepage let's review what we have done over here let me close all of the components here going back to our application so we created this app with the layout and the page inside of our page we had our form we're going to dive into the form for a second but we are fetching blog posts from our super base backend and showing the cards down here inside of our form what we had done here is we can bring this back in to check whether or not they have a prompt so this was a client action so from a high level again we have this form component with an input that takes in a prompt from the user we protected this input or this form submission with our cler authentication using this signed in and signed out functionality or control component so if they're signed in they can go ahead and submit the form if they're not it's just going to prompt them to sign in and create an account if they don't have and once they submit this form with an input we're going to call this client side action the client side action is going to check to see whether or not they have a prompt we use this toast notification to show errors and then once we do have the prompt we're going to call this create completion function this time we are importing this from our Gateway inside of here we're checking to see if we have a prompt and we have an authenticated user then we're going to call our API Gateway endpoint with a post request passing in some authorization Heathers and the prompt and user ID we're going to get a Blog back revalidate the cach for the index rra so that we can show that recently generated blog post and then redirect the user to that individual blog post now a critical point I want to mention here is that running AI generation for text and image is time consuming so when you go ahead and deploy your app to platforms like verell there is a limited time that these functions would run before they time out so for the free account on verel for example this is 10 seconds so if your server action or your API endpoints don't respond in 10 seconds it's going to time out now to solve this if you are on versel you need to uh upgrade to a Pro Plan where you can actually pass in another Max duration more than 10 seconds so let's go to the docs here if we search for this Max duration you can see there's this route config option you can you can export from your layouts Pages or routes that changes the max duration of this specific server function when it's running but another thing that you might notice is that well this is a route config segment option which you can only export from your layouts pages and routes and you might think well I'm just calling this function inside of a server action so what do I do if I were to increase the max duration of a server action which is an API endpoint but we don't have access to that route Handler directly to set this config option on that route so for Server actions you need to export this Max duration from the page that's actually calling This Server action so inside of our homage this is where we have this form and from inside of this form it's where we calling the server action so you can just go ahead here and inside of this homepage export a const called Max duration and set this to however many seconds that you want and that's a wrap for this video folks it was a longer project where we learned about open AI models we used superbase as a backend and also as a storage we used clerk as an authentication or user management solution we then created an API layer that sits in between our backend and any front-end consumer of our API if you want to see more projects like this AI related or not related again hit me up down in the comments now some suggestions as as to how you can extend this application we can also create a get endpoint where developers or any API consumer can hit to get their own generated blog posts you can add row level security in your super base so when you are talking to superbase you can generate your own blog posts and only uh get back your own generated blog post instead of getting everybody else's and lastly you can monetize your apis by adding stripe to the mix where you can charge your API consumers on a pay per usage basis I might create a video on this in a future video so stay tuned and I'll see you in the next one bye-bye
Info
Channel: Hamed Bahram
Views: 4,508
Rating: undefined out of 5
Keywords:
Id: n5dsBONfwjE
Channel Id: undefined
Length: 67min 35sec (4055 seconds)
Published: Sat Jan 20 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.