Build a Search Bar with Next.js and Prisma (Search API endpoint)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what is up you guys today I'm going to show you how you can build search functionality in to your applications using next.js and Prisma this is one of the most commonly asked questions that I get a lot of people want to know how to actually build the full stack search feature so they can actually have a text input that a user can type a search query into and send that query to the back end and actually query the database using the search term and then return entities that match this search term so we're not just going to be looking at the front end we're actually going to be building an API endpoint that takes in the search string and queries our database so I'm using next.js and Prisma however this functionality is techstack agnostic obviously the implementation will change a bit depending on what stack you're using but the governing logic is the same so to illustrate what we're going to be building um I am just on twitter.com here you can pretty much find a text search input on any popular website but Twitter has a good example so they have this search input here and I can literally type anything I want into this input and when I press enter you can see that we are returned entities that match that search input so I typed Elon Musk for showing tweets and users and anything that matches that search term so what happened though at the code level is they took this search string and you can see that they placed it in the URL here it says search and then they have a search query that says Elon Musk it's URI encoded Twitter probably has some search API endpoint that took this query in search the database their search algorithm is probably very sophisticated but then it essentially takes the search term finds the matching entities and returns that to the client and then renders it on the browser so that's what we're going to be building so what I have on the left here is just a Bare Bones in next JS project setup I have Prisma installed and configured as well um this video I'm not going to be covering how to set up the project it's more of just like an implementation of search I will be I'll provide the starter code in the description down below there will be a GitHub repo so if you want to clone the the rebuild and follow along feel free to do that but essentially what we have here is an empty next project with Tailwind as well I'm just going to use Tailwind to style it so it's not super ugly um and then we're going to have a schema that looks like this so I have defined the schema this Prisma schema where we have posts and users and there is a one-to-many relationship between uh post and user so a user can have many posts um so this is what we're going to do we're going to have an input that takes in a search term place that in the URL uh just like Twitter's doing here and then send that search term to an API endpoint that we Define and we're going to search across posts and users that match the search term so let's get right into it let's jump over to the app here right now it's just an empty application and our app is going to be very very simple I'll show you what we're going to be building actually we're essentially just going to have this text input um this is very similar to Twitter and then um I just have Crea I've created this dummy data and I'll provide you the in the starter code the the dummy data as well so if you want to see your database and then you'll be be able to essentially just search in here and you can see that that's placed in the URL and entities that match the search term are returned so that's what we're going to be working on very simple app we basically just have two components the input and then the parent of the post here and then the post item so so I'm just in the root layout file here and I'm just going to add some CSS using Tailwind to make our app look a little bit better 500 is the text is going to be zinc 200 um okay and then we're just going to have this div I'm going to place the the search input actually is going to be here it's going to be a global component so I'm going to put it in the layout component and we're essentially just going to have this other div that wraps the input as well as the the children which represents the rest of the application so let's just add some styling here not to the input sorry to this wrapper container we're just going to make this Flex Flex column Gap 10. item Center and P 6 for padding I'm not really talking about tail end in this video so I'm just kind of blasting through this this input we're going to create a separate component for this but I want to show you that that's where it's going to be it's going to be a global component cool and then on this div here that's wrapping our children which is going to be the rest of our app let's just Place some basic styling in here item Center and a full width okay so let's save that and now let's define this search input so this is going to be in a new file so let's create the file search input Dot TSX okay so inside of our search input file let's create this component search input and we will default export search input and let's just return an input for now and then in our layout file let's bring this component in as I was saying before so we'll say search input import that and now we can see that input at the top there however it is very ugly so let's make it look look nice I'm kind of using like Spotify inspired styling for for this build here so inside of our search input um I'm going to copy and paste the placeholder as well as the tail and styles here so that is what that is going to do the styling here so inside of this input the idea is to allow users to type in obviously and then put that search term in the URL and then our other component on our home page is going to take the search query from the URL and actually send an API request to our search API endpoint which we will Define shortly inside of the search input we're going to need to create some state that is going to represent this typed search query that the user puts in um so let's read some State we'll call it search query set search query use State and we will initialize it as an empty string and the value of this input is going to be the search query and then on change when the user types a key we want to update the search query to be the event .target.value okay so now we have the controlled input and we can type into our input there so next she has this complaining here it is saying that we cannot use hooks in a server component and is telling us to make this a client component so um if you're not super familiar with next 13 they have introduced this Paradigm of client and server components and you can only use hooks in client components and our search input is staple we need the use datehook therefore we should make it a client component now we're going to cover the actual like how do we communicate with the URL and how do we get this string into the URL right I want to type something like this is a search when I press enter I want it to appear in the URL very similar to not very similar actually exactly how Twitter has it with this search with the query encoded into the URL so how do we do that let's create a function called on search and so um we're actually going to do is wrap this input in a form because we want to you know have the user submit this form so they can press enter I'm just going to add some styling here we're going to make this Flex justify Center and we're going to give this a width of two-thirds okay and then we're going to give this an unsubmit function and on when we submit this form we want to call our on search function okay now inside of our own search function we want to what we want to do we want to take this search query the current value of the search query and essentially put it into the URL okay so we're going to say event well I guess this uh thing this this on search function has to take in the event and this is going to be a react form event that is the type of this event and we just want to prevent the default behavior of this event which is refresh we don't want to refresh the page so we're just going to call it event dot prevent default uh and now what we want to do is essentially um take the search term and add this URI encoding you'll notice that like the URI encode of the space character is percent 20. and that is how we can um that is what we're going to need to do to put it in the URL okay so on search if I just log the value of the current query so if I say search query let's open up the console here if I press enter you can see in the console that this is a search is there and so now we want to essentially URI encode this and add the percent 20 character into all of these space values here so that we can actually put it in the URL because we can't just really put this in url as it is that's not valid URL string um okay so what we can do is use a JavaScript native function called encode URI and pass it this string value and it will return to us this encoded URI so let's see how that works we can say const encoded string sorry encoded search query is equal to encode URI I can't type today in code URI and we want to pass this the value of search query okay perfect then what we want to do is take this search query well before we do this let's actually look at what this looks like let's let's take a look here so now if I press enter you can see that it added the percent 20 to all of the spaces there so now this is valid URL string and we can put this in the search query of our URL of our URL that's kind of a tongue twister um okay so we can do that using the next router um from nextgs so we're going to need to use the user router Hook from next so we can say cons router is equal to use router now you're going to want to make sure this imports from next navigation not next router navigation is their new what's the word for it it's like their new folder that has the updated router functionality um the router from next router is different from the new navigation one and the navigation one is sort of the new routing Paradigm that they're introducing so we're going to use the next navigation one um then we want to essentially just take this search string and push it to our URL so we can just say um we can't just say well we don't want to push encoded search query what we want to do is basically exactly what Twitter's doing here they're they're pushing search and then with this uh Q query parameter and then the value of the query parameter is the search term so the thing we want to push to our URL is going to be the search route and we want to add a search query parameter q and the value of Q is going to be our encoded search query all right so let's see what happens when we do this if I press or if I say hey person it's going to push that to our URL so now we can see we have this search and the question mark Q is equal to hey a person our encoded searching now our app broke because we don't have a page or a route corresponding to this this URL we don't have a search page to find so we're going to have to do that so the way routing works again I'm not going to get too deep into next.js and how the routing system works but we kind of have to here inside of our app folder we can create a new folder called search the names of these folders correspond to your page route so we are going to have to create a search page and inside of our search folder we can create a page.tsx and inside of here um we can create a new react component called search page and then we will export default the search page in here that's just for now so I can show you that this is working create a div that says search page okay I don't know if I have to rebuild the app if I save this okay there we go so we are now on the search page and our input is still there because our input has been placed in the global layout folder so it's going to be everywhere in our app okay so this is our search page and this is where we are going to actually take this value from the URI or the URL and query our database or send it to our API and our API is going to query our database um is there anything else we need to do in in here I'm just going to remove this console log I think that's pretty much it for this this component here our search input is done and we can just close this and start working on this search page here okay so how do we get this string from the URL now and use it in JavaScript inside of our component nexjs has a hook called use search params that allows us to do this so we're going to do is create a variable called search and we're going to say this is equal to use search params just like this and this is going to come from next navigation and this is going to return essentially the search parameters in the URL so let's see what this looks like search params log the value of search again we're getting that same client component error because we cannot use hooks in a server component so let's make this page a client side page um and so if I log search params which is showing up here you can see it's a it's a it says read-only URL search params so the URL search params is a JavaScript native object um that has these properties on it you can set the value of the search params you can get them what we want to do here is get the value of our Q search string because we need this hey percent 20 person thing because that's what we're sending to our API so we are telling xjs that we want this this this search um search params object and from this search programs object object we want to get the value of Q so how do we do that Q stands for query by the way if I haven't mentioned that already or if it wasn't obvious Q is for query is what I'm guessing um so let's now see how we can do that so we can say consearch query is going to be equal to search dot get and we want to get the value of Q just like this okay now we're getting typescript is automatically option um optional chaining this because search may not be defined it could be null because the use search prams hook could technically return null as you can see here so what we're going to do is I'm just going to say I'm out of ternary operator here and say if search is truthy we're going to get the value of Q if it's not true the we're just going to set it as null okay now if I log search query this is going to be our URI encoded search query hello person okay so search okay these logs are getting confusing yes so search params is hello person so it's actually not encoded anymore you can see that it doesn't have the percent 20 thing again you can see that you can see that it does not have the encoded percent 20 for the space character so the URL we're actually going to send to our endpoint is going to be the encoded URL so we're going to need to encode the URI again so we can do is we can say const encoded search query is equal to encode URI whoops and pass in our search query now this is my get mad because why isn't it why isn't typescript getting mad here this expects a string um and this could be null because we said it could be so what I'm going to do is just add a or statement here that says if search query is truthy you can encode the search query if not we'll just pass an empty string when I was coding the demo typescript was freaking out when I was passing in just search query without this or thing so yeah not sure why it's not doing it here okay and now we can log the value of this and you can see that it now has the hello percent 20 percent this is a search term cool so now how do we send this to our API what we're going to do is create a search API endpoint and essentially make a get request to this endpoint and this get request is going to contain the value of our encoded search query in the URL so how do we create a API endpoint so inside of this Pages folder here we when you create a new next project it gives you this sort of like hello example API endpoint um we're going to change this to search because that's the endpoint we're going to be querying is search I think actually in 13.2 next.js if you just install a new application and you use the app directory it will Define your API routes in the app directory I was yeah I don't really like honestly I don't like the way it basically just changed the patterns a bit of like these two requests and response objects and I don't want to get too deep in into into that but essentially I I prefer this pattern here again this implementation of search is certain is a tech stack agnostic so I don't want to get too deep into like how to do it with a certain tool it's more just like a create an API endpoint get the query to the endpoint and then query your database that's that's the point we're trying to cover here so uh if you are creating a new app using our new nexjs app using the app directory you will probably see this API folder in your app directory and you will not see a Pages directory you can do either uh do what you see fit I just created this Pages folder at the root of the project and then I put the API in there and currently we just have this example hey dude thing that I that they gave us I I changed it to hey dude uh they didn't they didn't do that that'd be kind of cool if they did but to show you how this works um I have Postman open here and so let's actually change the name of our API endpoint to search again if you don't know how API routes work in xjs any file name that you give in this API folder is going to be an API wrote and the API route we're creating is search so we're going to name it search okay um so just to show you that we are reaching this API endpoint let's add a log here that says add the API and using Postman I'm going to send a get request to this this API endpoint so if I say API search for making a get request here you can click Send and you can see the response is hey dude so we're actually hitting this endpoint and if I go to the console you'll see at the API being logged there so this is where we can actually take in that search term and do something with it and in that case that something is going to be querying our database all right so maybe you're wondering well we have this search term in our in our URL how do we actually actually get that when we make the get request so what we can do is um grab the query parameter from the request object so we can do that is we can say we can destructure the request query object and we are going to be expecting a value of Q on the request query because that is what we named it here so we can I'm just going to Alias this as query and so now if I block the query and I go back to postman here um if I click Send the query is currently undefined because we don't have a search query in this URI however if I do this structure here like in the browser if I copy and paste this and actually I just want this search part search slash API search so now you can see Postman is putting the query parameter queue into the in the value of the search term and it's our URI encoded string so now if I send this you can see it now it's like at the API and the query is being logged as this is a search term and it's already decoded so now we have like this string and it's successfully at our API endpoint and now we can just simply do our database search okay so I have pre-populated my database with fake data um I have this seed file here that literally just sees the database with fake posts that I created I actually use chat GPT to generate these posts which is kind of cool I just have a bunch of fake data in here and it's randomly just creating data it picks random values in these rays and it just creates fake data um I just wanted to have some data that is actually searchable rather than just and so I can show you a few examples so in the starter project the seed and the data files will be available so if you clone the project um they will be there and you can see the database if you wish so right now yeah we're just using Postman to get this get request and the search term to our to our back end we currently don't have a way for our react application to make this get request so that is what we're going to do now so to do this I'm going to install a library called SWR it's just a data fetching library that is popular in use with next.js um again not going to be going too much into that you don't have to use SWR you can just use the native fetch Library if you would like you can use whatever you want you can use react query anything uh this is more of just making the get request that's all we need to cover so I'm just going to install SWR so now inside of this uh search page here I'm going to import the use SWR Hook from SWR and I'm going to define a function called Fetch posts that is going to take in the search string or it's going to take in the entire URL inside of here what we're going to do is essentially just use this is just use SWR as essentially just a wrapper of the Native fetch API in JavaScript so we can say const response is equal to a weight fetch so we're using the native fetch API URL and this needs to be an asynchronous function if we want to use the await keyword and I'm just going to add a quick check here that if the response is not okay we can check the status we can say Throw new error um and we can just put a message in here failed to fetch post okay then the fetch post function we want to actually return the data that this uh response receives from our API and speaking return response.json okay so now that we have that defined this is the function we're going to pass to our use SWR hook the use SWR Hook is going to call this function when we tell it to so to use the use SWR hook we can say use SWR and the first argument to this Hook is going to be the actual API endpoint that is going to be passed to this function here the API endpoint we are going to be querying is going to be slash API slash search and then we're going to add our Q query string so question mark Q is equal to now the value of Q is going to be this search query and actually sorry it's not the search query we're going to have to place this underneath because it's going to be our encoded search query encoded search query just like this and this is the exact same endpoint that we have in Postman here the SWR hook automatically adds the domain to uh to or it prepends the domain to the to the search to the API endpoint so we don't have to explicitly type localhost 3000 um like we're doing in Postman here but you can see that this completely matches this that is what we're doing so we're making a get request to this endpoint now the use SWR hook returns a few things it returns data and the error message if it occurs at loading and a few other things the things we're interested in right now for this is is data and is loading is loading is going to be a Boolean that is true if it's currently in the process of being fetched the second argument we need to pass to our use svr Hook is going to be the actual function that is responsible for fetching this data and that is going to be our fetch post function so fetch posts okay so now SWR actually has a way to communicate with our API via fetch posts okay so you can see now that data in the browser is showing message hey dude so that is actually coming from our API endpoint right if I change this to just a hard-coded array of like posts if I save this and then I refresh this page you can see that here is data is this empty array of posts cool so now you're kind of seeing this front-end back-end communication with within xjs here we're sending a search term to our API we're not doing anything with it right now currently so let's actually use this search query to search the database among the we're going to search this dummy data that I have created um and we will actually rather than just return this empty array we want to return posts that match this search term so what I'm going to do is I'm just going to add an if statement here we're going to check first that this request is actually a get request that's sort of the pattern to use in in the next.js API so you're just making sure to get requests and only if it's a get request we're going to try a few things the search API endpoint is not going to support other HTTP methods other than than a get request so I'm gonna so I added this try catch block here so that we can handle the error if one occurs so now what we want to do is take our query string and actually search our post entities in our database and as I mentioned before uh I have seeded the database with a bunch of fake posts and our search algorithm is going to be simple for this app all we're going to do is search posts we're going to search the body of the post as well as the author name of the post so if you search Elon Musk we're going to search you know has someone in their post mentioned Elon Musk or is the user does the user string include the search Elon Musk so what I'm going to do is just import the Prisma client that I have created in the Prisma folder here again that will be in the project starter code if you're interested so what we can do is create a post variable and we're going to use our Prisma client to query our post collection so we can say prismath.post and we can call the find many function because we want to search all of the posts now inside of here is where we're actually going to query all of the post documents and tell Prisma what to return to us we're telling prismo we want to find all the posts that match some sort of set of criteria and the two set of criteria that we want here is you want to check does the post body contain the search query or does the author's name contain the search query so to tell prismo that that's what we want to do we can say where and because we have two conditions here we're using an or statement we can use the or keyword now this is going to be an array of our two where Clauses the first one is going to be our body so does our post body contain this query and we want to set the mode to be insensitive that we don't want to care about uh case sensitivity here this is complaining uh string okay yeah um so one check we're going to add to solve this typescript error here is we're just going to make sure that the type of our query is in fact a string if it's not a string we don't want to proceed with the querying we'll just say that it's an invalid request we'll throw an error so say if query or we'll say if type of query does not equal string we want to throw a new error then we just say invalid request now in our case this is always pretty much going to be a string this is more of just a typescript thing we have to uh implement the second or statement we want to add is we want to check does our does the Post author's name contain this query so you can say author name contain query again we want to make the mode in sensitive and I'm also going to include the author of this post so every post that's returned we want to also tell Prisma that we want the associated author and this is a this is a relational query because if we look at our database we have two separate models here post and user however there is a relationship between them so that we know who the post author is cool now again I'm sure Twitter or Spotify or whoever search algorithms are much more sophisticated than this but this is the point of this video is not to show you it's not to go too deep into into search as an algorithm more of just like a how to implement this in your in your application so feel free to enhance this if you want to search other entities feel free um yeah the the sophistication of search could you know expand infinitely so for our purposes this is pretty pretty good okay now we actually have real posts to return so we can return the post inside the catch block here I'm just going to add a status response of 500 and we can just end the request so if we if we are throw an error for any case whether it's here or inside of Prisma it will be caught here and then we'll just tell the the client that uh something bad happened and we'll respond with the 500. so now we have our front end hooked up her back end and we're actually using the search query to query our database using Prisma so now if I press enter um we have this is a search term in the URL now none of the dummy posts match that that search term I don't think so that's why we're still getting an empty rate but what if I search for for Tesla okay so you can see that here is data it returned an array of 25 so 25 of these fake posts let's just open up this one um have that they satisfy that search so you can see that the body here of this particular post has Tesla in it um all of them have tests on it now again just to illustrate uh the data here I just I went to chat GPT and I typed in can you please generate me I think there's like 30 fake tweets that sound like they were written by Elon Musk um you might notice that I am a fan of Elon Musk I mentioned him quite a bit in my videos um anyway yeah so these are fake posts that I use so they include a bunch of Search terms so yeah feel free to use these again they'll be in the starter code so now we actually have posts being returned to us on our front end in our next JS app so now we actually want to render them on the UI so that's probably the most exciting part so let's go ahead and do that so what we want to do is inside of this div here we want to iterate through all of the posts so we can say data.post now typically when I'm using SWR I'll add a generic type here to to so I know the type of the data coming back for this purposes of this tutorial I'm not going to worry about that we we know that posts are going to exist on the data if the data is defined we could have undefined data as we're seeing in the console here because the data doesn't exist until that request is complete we'll handle that in a second though um well let's just do it now we can say if there is no data question mark dot posts optional chaining so that we don't get an undefined error we're just going to return null we don't want to actually go down here until the data is defined and that posts are in feed on this uh on this object here so I want to do is you want to iterate through all these posts you can see data.post.map and we can take each post and for each one we want to create some sort of HTML element uh for now let's see post dot body cool so you can actually see these showing up and actually you know what I'm just going to add a type here so the type of data is going to be an object with posts on it the the type of this posts parameter is going to be an array of posts and what I love about Prisma is that it generates typescript types when you create your schema so we have a type defined you can import that from the Prisma client when you generate the Prisma client it creates a typescript head for you which is which is amazing it's one of my favorite features of Prisma but you'll remember that when we queried the database for post we also included the author and so the structure of that that prism is going to return is going to be the author as well in addition to the post and the type of author is going to be user and again user can come from our Prisma client folder that Prisma generated cool okay so now now we actually have Auto completion here because we know the structure of our data so that is how you can Implement search like we've already finished the core concept of this application um what I'm going to do is just style this a bit in case you're you're interested but we pretty much completed all of the uh challenging parts of this of this app and now what I'm going to do is um just kind of post in the actual HTML body of this post just to make it look a lot nicer we're going to add the image the author image URL we can import image from next image save this so because my fake posts um have images from from Twitter these are this is the Twitter this is like the Twitter storage bucket for images next yes is freaking out because it's saying you have not actually added this this URL to your images configuration in next config this is next year specific thing the way to do that if you're curious is you can go to your next config file here and inside of this next config object you can add uh an images object and tell it where images might be coming from so you're saying you're allowing remote images to come from from these URLs I think I have to rebuild the app to activate those changes so restart the server there okay so now I should be able to save this refresh so I think the reason that this is not centered is because I just have this this outer parent of here which we actually don't need I'm just going to make this a fragment there we go so that's centered and one actually Improvement we can make to this is in our search input we're searching part search input rather than initialize the value of this to be an empty string we can check if the URL contains a search query and initialize that to be the the initial value or initialize the search query to be to be that so that it appears in our in our URL so the way we can do that is going to be very similar to how we essentially grabbed the query from the URL in our search page so okay so I'm just going to copy and paste these two things here and place them here like this we'll bring in our CU search params Hook from next navigation and we actually don't need this second search query variable here I'm just going to cut this and this will be the initial value of this state here to get rid of this okay and now we're getting that error I think I mentioned before how typescript was freaking out so here I'm just going to add a or statement like this same thing down here in our value just to satisfy time script okay so now this search term from the URL automatically appears in the uh in the input on page reload so if I if I visit this URL for the first time it's automatically placed in the in the input we're seeing posts that match the search term Tesla let's just do a few more examples here let's search for Alexander let's just search for Alex so if I search for for Alex what should happen cool we'll return posts that the author name or that the author string includes the search term you can play with this as much as you'd like uh if I search for new this these are all the things that include new let's do fast so that is how you can Implement search functionality into your next.js applications thank you guys so much for watching I hope you enjoyed this video let me know if you like these types of videos I'm trying to mix in kind of like shorter form content among these like giant build projects so let me know what you think or if you have any ideas or things you want to see join my Discord the link is down in the description thank you guys so much once again for watching I'll see you in the next one
Info
Channel: Shadee Merhi
Views: 14,280
Rating: undefined out of 5
Keywords:
Id: IYoZvxUbhUQ
Channel Id: undefined
Length: 40min 22sec (2422 seconds)
Published: Sun Feb 26 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.