Build a Full-Stack Search Engine with Next.js 14, Postgres, Upstash (2024)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey dude I'm joshh and in this video you and me are going to be building a beautifully designed fast and just smooth AF search engine together once we type in a search term like for example a winter jacket first off you're going to notice a lot of attention to detail pause the video you see this I mean dude I don't want to Hype up a loading State too much but yeah we're going to implement smooth loading States while we're waiting for our search results to come back from our back end and honestly nextjs makes that super enjoyable and after a few milliseconds we get a list of products from our back end and here's the coolest thing about the search engine not only do we get literal matches for the term winter jacket from our database like the first result right here but also those jackets that are similar in meaning but don't exactly match the search term like this result we got back from our search nowhere in the product description does it say winter jacket but or search engine somehow still found this relevant search result now you might be wondering hm then how did it find this product and the answer is that in this video we're going to be implementing a super cool technique called semantic search alongside the super powerful full text search capabilities of PG grass so when a product matches the search term directly then it's listed at the very top of the results as the most relevant result but using this semantic search technique that we're going to go through step by step in this video even if the the user searches for a term like summer jacket for example they are still going to get relevant results and that's super useful because otherwise imagine what would happen if Google only showed you the results that literally exactly match your search query you can probably imagine that in that case you would have a pretty horrible search experience now after finding and displaying the most relevant results your users will be able to click on an item and be taken to the product page now you might notice that this transition the URL of the page changed but the core layout of the page remained the exact same well that's because in this video we're also going to explore Advanced nexs routing patterns that allow us to keep our core logic in a way that literally does not require reenders throughout navigation which is both incredible for performance and also for user experience now once we're done reading through the product details enough of that we can go back to the search and this transition back to the search page if you notice happens instantly and that's because we're caching all search results we get for the best possible performance so as you might have noticed by now throughout this entire video we'll care a lot about fine details like accessibility beautiful design and of course performance all those are tiny details but they make up an amazing app now all resources you need to follow along with this video including the entire product catalog I've made for this video are provided to you and they're ready to be put into your project using a pre-made seeding script and I did this because after this video I don't want you to only be left with the entire app but I also want your app to look great with all the products and the descriptions and the images and everything there is already in there everything I just showed you we can do together in under 2 hours and I mean dude you're not stupid you can probably tell from the video length so you're going to learn a lot in not a lot of time that's my goal for this video and I highly encourage you to follow along with me that's how you learn the most it's going to be super fun following along is 100% free we're going to go through all Concepts step by step together and dude I just want to make this a really nice learning experience for you so all right turn on your PC open your code editor follow along with me and let's go so here's how we're going to go about things we're going to make a game plan and there's going to be three items on this game plan so first off that's going to be the homepage we're going to make things look good we're going to set up the styling system that's going to be the basis for everything for the entire app to look really nice afterwards we're going to create the search functionality so that's going to be for example the search bar and then finally we're going to implement the product preview so that we can see one product if we click on it and see it like large with a whole text and when we can also add it to the card that's going to be the way we're going to go about this and to get started we're going to go into our command line um I'm going to do this on my desktop but wherever you want the folder to live and we're going to say npx or actually we're going to use pnpm DLX this is the same thing as npx but it's going to use pnpm under the hood so we're going to say pnpm DLX and then create next app at latest or if you don't want to use um pnpm DLX you can use npx um instead of this same exact thing and we're going to hit enter now that's going to ask us a couple of questions for example what is your project name we're going to say search oops well I made a typo but you want to you might want to say search right there we're going to use typescript eslint Tailwind hell yeah we're going to say yes to the source directory yes to the app router and no to the um configuration of the import Alias because I like the default one with the ad slash and that's really good and one thing that's really weird is when you click into the command line while it installs everything it looks like it freezes but if we actually stop this I think it should have worked um let's CD into the Search and say code dot the code dot is going to open up this exact project where we currently are so the search folder inside of vs code it's going to do that on my second monitor here and we should see that all the dependencies are installed no they're not so we still need to install all the dependencies using pnpm install or npm install or yarn install if that's what you're using that's going to install all the packages we need to run our app and there it is then let's start up our development server and that's going to start up under Local Host 3000 to see if this actually worked right now we should see the default kind of next ja page and if we do then that means we did a good job of setting up the project sometimes this can take a while to load especially if you're doing it for the first time but there we are perfect okay so step number one what we're going to do is make things look good right now this looks like complete darkg water and nothing like a search app and we want to fix that and the way we do that well you can do that in a lot of ways but what we're going to do is use a UI library and alone the setup of this UI library is going to create a very nice looking default for our project so in our command line let's say npx and I'm going to zoom in so you can see this a bit easier there we go we're going to say npx Shad cn- UI at latest that just means the latest version of this shaty and UI package and then init and hit enter it's a UI library for react and next it's super nice and it should ask us a bunch of questions there we are so which style would you like to use let's go for the default one this is just it's kind of two different Shades of Gray so you can choose whichever you prefer I'm going to go with default and I'm going to go with slate as the base color Yes we would like CSS variables that's going to be handy later and already it's going to create some really nice looking defaults it's going to create a components. Json right here and it's also going to modify or global. css with some CSS variables that look great out of the box perfect so with that done let's start back up our development server so we can see what we're doing in real time let's restart this and while it does we can already proceed and we're going to proceed right here in the main page TSX that nextjs creates for us because in here we literally want nothing we're we're not going to need it we are going to do it a bit differently a bit nicer than putting stuff in our main page so I'm just going to put a fragment here that's not even going to render any kind of Dom note in the browser so the page is going to be literally completely empty it's just going to be some script tags that nextra s us is under the hood but there is nothing in our body and that's perfect because the main magic happens right here in our layout. TSX because if you remember the demo when we switched around the pages it still had the search bar on top and we do that right here in the layout it's a much nicer kind of uh way of going about things by default this is going to be super minimal for example it's going to initialize all font but there's not going to be any kind of style applied to you so let's do that for now we're going to get rid of the children that are right here in the body we're going to need them later 100% for now let's get rid of them so it's going to make the process right now a bit easier so in here we're going to create a div element and let's give this a class name of relative a a minimum height of screen there we go and isolate and if you're wondering what that does we can all oops ISO late there we go we can always hover over this if you've got the Tailwind CSS intelligence Plugin or addon installed for vs code uh uh uh where is it this one tail one CSS intelligence and if you have that we can simply hover over this and then always see what the individual class names mean in regular plain CSS all right let's also give this an overflow Das hidden a border db4 bottom a border gray 200 and also a BG of white a background of white perfect inside of here we want to make our homepage look nice with a little pattern in the background like a grid pattern pattern and the way we can do that is with an SVG element but we we don't have to type that ourself I prepared a little copy paste list for you don't worry there's not going to be anything important in here just the stuff we really don't want to type ourself like SVG elements like imagine we have to type this ourself hell no um you're not going to learn anything in doing that so we can just simply copy and paste these from the copy paste list that I'm going to link in the description by the way it's going to be up there in the description you can simply grab the first homepage SVG element right here the homepage SVG and paste it in right here once we save that we can see our page has a beautiful grid pattern in the background that's exactly what the SVG dust that that we just um pasted in here and that just makes our page look a lot nicer and that's it all right we're going to proceed below the SVG right here with another div element and this is going to get a class name of MX Auto Auto there we go a maximum width of 7 XL and if we H over this this is just uh 1,280 pixels or atrm same thing it's just a nice syntax in Tailwind a padding X of six a padding bottom of 24 padding top of 10 on small devices and upwards because this is mobile first we're going to create a padding bottom of 32 on large devices we're going to make this a flex x a gap of 16 on large devices a padding X of eight and also on large devices a padding y of 24 now I know lots of class names lots of paddings and styles I promise that's going to be one of the no that's straight up the longest class name we're going to have in this layout uh don't worry but it's going to make things look good all right inside of here let's create one more div element with a class name of height f a width oops a width of full a flex a flex Das call an items Das Center and also give this a gap of four so we nicely going to space out the elements that are going to be inside of here by 16 pixels and inside of here we can by the way already kind of see what this looks like but since there is nothing in the div yet no text or whatever we're not going to see anything just yet but in here let's create an H1 a heading one element and this is going to get a class name of tracking Das tight which is going to move the letters closer together it's going to look really nice in a heading a text of 4XL on small devices and up we're going to give this a text of seven uh of six XL there we go so a bit larger and a font Das bolt and inside of here we're going to say magic search um and you're going to see what this does there we are there is our text with the letters kind of moved closer together which looks really nice um inside of a title and if you compare this with the demo that I showed you in the beginning of the video there were some sparkles like a little icon above this text and of course we're not going to leave that out here oh hell no we're also going to implement this in the project that we're doing right here right above the H1 element and to do this I'm going to show you a really cool way to create an icons component and let's create a new file in our components folder by the way didn't nextjs automatically create this interesting anyways let's create an icons. TSX file inside the components folder and inside of here we can export aons icons and this is just going to be an object this is not a standard react component but instead it is an object and what we can do by that is for example if we had an example here you don't need to follow along with this or this would be an SVG in reality and what this allows us to do is in our main component we can say icons import the component do example and just like that render out a beautiful icon now of course this gives us a syntax error because this won't work as is but you get the idea right we can create a really nice icons component this way now we're going to install an icon Library pnpm install npm install yarn ad doesn't matter and this is called lucd D react while that installs I can show you what this is lucid react let's go over here it's under lucd dodev and all it is is a really nice icon library with a ton of SVG totally free icons that we can easily style and use in our projects it's a super nice Library I use it a lot on all my projects oh and by the way we can already start back up or development server so while we're going to need the um lucd react more later one thing we can do right now is turn this example into something called sparkles sparkles there we go that's what we're going to call this icon and this is not not going to return jsx right away but instead a function and that's going to make the syntax error go away that we saw in the layout because in this function we can now receive props and these are going to be of type Lucid props now that's what we that's why we installed this icon library right now we're not going to use the actual icons from it yet we are going to do it later but for now we just need the type we could also do it like this for example where we just need the type to accept in our function for the icon and the actual icon also comes from the copy paste list it's super long we definitely don't want to type this out ourself so you can just um grab this from the copy paste list and then paste it in here for the Sparkles icon just like this we can save that head over to our layout and then use the Sparkles icon right here inside of our layout and the Beautiful Thing is right the the reason we gave it these props is that we can style the icon from wherever we call it and not in the icon definition which makes it super reusable so for example we can give it a class name of height 16 and a width of 16 oops wherever we call it and just like that we can head back over reload our page and see that the Sparkles icon should be there if we started our development server which we did it just takes a bit to load all right let's let's let this load and then there we are you can see the beautiful Sparkles icon above the magic search perfect now below this one last element that's going to be a P tag and this going to get a class name off oh and by the way why don't we move this into a nice side by side view so we can see uh the two things well well side by side anyways this pag is going to get a class name of Maximum with XL a text- center a text of large and also a text slate of 700 which is like kind of a dark gray dark bluish color and inside of this pag we're going to say a be oops beautifully designed hybrid search engine that enhances search accuracy by querying semantically related results you don't have to type this text you could literally put anything else here you could even leave away this entire section um if you just care about the search bar this is more like something you can put on your portfolio so it will give people a bit of context on what this is um but it's not super important and if you just care about the search bar then hey by all means you can totally skip this anyways I want to make this I want to make sure that it looks good when you build with me so uh let's implement it anyways below the ptag let's create a div and this gets a class name of MX Auto a margin top of 16 a width of full a ma a maximum width of 2 XL flex and flex - column to kind of vertically um align the elements and inside of here comes the most important component in this entire build and that is the search bar the search bar is a custom component that we haven't implemented yet so how about we do let's save this layout and by the way while we're already here we can simply also render out the children right below the search bar these are going to be our actual page contents later but I'm going to get more in depth into that which is a really cool principle by the way when we get there for now let's create the search bar and we're going to do that inside of our components folder create a search bar. TSX file and hit enter now for this search bar there are two main goals uh and I already have some stuff there search search bar there we go okay so what are the goals for the search bar first it needs to be intuitive it should be very easy to use it should be accessible to use for example if you press Escape then you're going to exit the search bar and so on it's going to be super intuitive and it's also going to be functional we're going to see a really cool approach that I probably demoed in the start of the video which that part I haven't recorded yet so I don't know but probably dude um it's going to be functional in that when we press the search bar it's going to put the state into the URL which is a really good idea because if you want to share that or if you reload the page then it's going to keep all the progress of the search which is a really really good idea much better than keeping stuff in state and in order to achieve this really nice search bar let's well created let's say con search bar is going to be equal to an arrow function and we're also going to export that search bar as the default search bar there we go at the very bottom and the search bar is going to be pretty simple in terms of component because at the top level we're going to return a div from here and this div is going to get a class name of relative of width four a height of 14 a flex a flex-all and a BG of white as well perfect now let's open up this div and already format this there we go and inside of this div we're going to create one more div and this going to get a class name of relative a height of 14 a z index of 10 and arounded Das MD for medium and inside of here comes the actual input right like a field where you can type stuff that's going to be the search bar and in order to already get a beautiful looking search bar out of the box with full accessibility we're just going to create or uh install it from our UI Library so we can say in our command line npx shat cn- UI at latest add and I think it's called input and then hit enter that's going to install a beautiful fully accessible input component that we're going to use as the basis for or or search bar and if you go into the input file it just created right here under our component slui folder this is where it installed it we can see the component right here nothing is abstracted we have the full code it just kind of writes the code Force which is I think a beautiful way of going about things and in order to use it we can simply import this input component that we just installed in our search bar and we can give it a class name oops this class name is going to be absolute an inset of zero and a height of full so it's going to take up the entire space of the parent element and hit enter and we can already go ahead and now import the search bar in our main layout reload the page so the error goes away we now know where the search bar component comes from and of course we need to restart our development server in order for this to actually work so let's give this a second and then see the search bar beautiful there it is our search bar is here on the page we can already type stuff in here perfect very very nice what we didn't do yet is a button like a search button with a like uh what is it called magnifying glass that indicates we can search but that is as easy as the adding the input right here we can simply go ahead and say npx Shar cn- UI at latest add button and just like with the input field that's going to create a no abstraction button. TSX file in our code with some custom variants so specific class names that build on top of the CSS variables we defined at the very beginning of the build and in certain sizes we can also do that and then basically all the code with very little abstraction is written for us so what that allows us to do is right below the input component put a button component that we just installed and this is going to get a class name of absolute a right of zero in inser y of zero a height of full and a rounded left none and hit save now what that's going to do is of course once once we start backup our development server that's going to put the search button here in the right side of our input field that's why we made this parent relative so we can style the parents according to the parent element so we can kind of move the search button to the very right of the input field so once we reload the page that's going to probably take a while as it sometimes does man eventually nexts is going to move to uh turbo pack and that is going to be much faster there we go there is our button right now there's nothing in the button so it looks super weird so instead of making this button self closing oops um we actually want to put something in the button and that's going to be a search oops search icon from theed react the icon Library we've already installed and this is going to get a class name of height six and a width of six as well and just like that we created a beautiful search button right here in the right side of our input field that when we click will then trigger the search functionality for our search bar very nice now we're going to do one important thing for accessibility and that is when we type in the input field and hit Escape we want to lose focus of the input field and currently that doesn't happen and implementing that is actually really straightforward let's create a const input ref and this is going to be equal to a use ref we get from react and we can pass this a typescript generic of HTML input element and then let's instantiate this as null and all this generic does is tell react that hey we're going to assign this to an HTML input element and for example not a div element so it can just um infer some types from it the way we do this is by passing the input a ref and this is just going to be the input ref so if we left this away way then typescript would oh actually typescript wouldn't be mad interesting it used to be mad but um now that we're working with the input raft that's going to give us some type safety so let's do that anyways um and the way we do this is by doing a or by checking for an Onkey down event on the input and we're going to get an event in the Callback function we get from that this is going to be a keyboard event and in this event we're going to do a check if the E do key in which we have access to the key that was pressed in the input field like for example if I type A AA that's going to be a the E do key and we're going to check if the E do key is triple equal to escape and if the user presses Escape in that or did I mess up the syntax here uh yeah I did if the user presses Escape in that case we are going to say input ref which is optional so we're going to put a question mark maybe it's not assigned yet if it's not then nothing is going to happen that's totally cool current dot oops do current there we go do blur and that's how we remove the focus from the input element and right now we should get an error if we reload the page from that because yeah there the error is this won't work because this is a server component by default so at the very top of the file let's put a use client now what's a server component well if we don't put this use client then this component is going to run exclusively on the server and then is passed down to our application as pre-rendered HTML with literally zero interactivity if you want any kind of interactivity which react hooks are if we want to use any kind of hook in this component then we need to mark this as a client component where it will be pre-rendered on the server and passed on as HTML but it will still allow for full interactivity because it's rendered on the client side beautiful so once we type something in and press Escape now we're going to lose focus on the input element ment perfect very very nice that's exactly what we want and now what's left is the actual search functionality what should happen when we press enter what should happen when we press the search button well we want to search so let's create a function for that con search and this is going to be an arrow function and in this Arrow function we're going to make use of a really neat helper from react that is called use transition so oh I switched into caps lock we're going to say cons and then array the structure we're going to worry about that later is going to be equal to use transition and what this hook allows us to do is to hook into the page switching cycle so while we are switching Pages we can show a beautiful loading State and this able the input of our search bar trust me if that sounds some somewhat abstract you're going to see exactly what that does right now so we can destructure two things from here the is searching if we are currently searching for the products if the user hit enter and also the start transition now this start transition it's going to get a callback function right here in our search and all we want to do is to push the current search query that's what we call whatever is in this input field that's the query we want to push that into the URL the way we do that is very simple with a router and this router is going to come from use router from next SL navigation very important not from next router that's for the old nexts next SL navigation is the new nexts that we are going to use this is important otherwise you're going to get an error if you get this from next SL um router no and uh yes this is the good one we need all right so inside of the start transition we're going to say router. push and we're going to push the following URL SL search and then question mark to append a query parameter to the URL query is going to be equal to query now this query where does it come from it doesn't exist we're going to get an error well that's what we're going to keep track of in state so we're going to initialize State I've got a snippet for this that lets me do this a bit easier and we're going to call this query and set query by Convention of course this is going to be a string and inside of this we can initialize this for example as an empty string and of course we also need to import use state from react now to keep track of the current query instead we're going to make this input a controlled component the way we're going to do that is by passing it both a value that's going to be our query and also an onchange Handler so on change we're going to receive an event in the Callback function we get in here and we're going to set the query to the E do target. value which is nothing more than the entire value of the entire input field and now what we can do is if the user presses enter while they're in the search bar well we want to trigger the search functionality we can do that right here in the on key down as well because if the E do key is enter then that means the user has pressed the enter key on their keyboard and we're going to trigger the search function that's just going to push the new URL into the state same thing should happen if the user presses the search button right so when this button is pressed on click we're going to start the search as well by the way we can already pass this button a size of small just to make it look a bit nicer let's save that it's just very tiny bit smaller I like it and let's see what happens so let's press the search button that's going to push that into the URL and we are going to get a 404 from that because there is no/ search page in our application really quick before we do that let's perfect this component because there's two main things we can do to improve this right now first of is loading States that's going to be really really simple for example we can disable the input disabled is going to be equal to is searching so if we are currently in the process of searching of querying our database and the vector store for the semantic search we're going to worry about that later then we want to show a loading State and we also want to disable that the user can type something in while we do that there's no point in that same thing goes for the button we're going to disable this if we are currently searching there we go and we're also going to show a loading icon if we are currently searching the way we do that is by replacing the search icon we hardcoded inside of of a button with a dynamic check if we are searching let's do a Turner operator if we are searching then we're going to render out a loader 2 icon from Lucid react which is like a little spinner and this gets a class name of height six a width six and then animate Das Spin and else if we are not loading or not searching then we are going to render all the search icon we had before and hit save on that by the way why why is our text white well I don't know but one thing that fixes it is by passing a text slate of 900 into the layout. TSX into the very top div that we have let's save that and then our text should be uh black perfect and right now after searching we're going to get a 404 this page could not be found of course because again there is no search page so we created it and this is going to be one of the most fun parts of the entire build because it beautifully showcases how to build a very nice kind of searching solution that matches full Tech search from postgres with or semantic search engine it's going to be very nice and where we are going to do this is right here inside of the app folder this is how we can create new kind of routes or Pages inside of our project and let's create a new folder called search in here and inside of the search let's create a page. TSX now the way in which we name this is actually important it's enforced by nextjs this is going to be the folder name is going to end up in the URL that's going to be the search and then the page. TSX inside of the folder is going to be the content that that is shown under the URL so if I um put a page right here cons page is just an arrow function that we also export default at the very bottom then if we return Like A P tag and here that says hello this is going to be the actual content that is then shown on the page now you might be wondering Josh why is the content of the page rendered here below our search bar and not like as the entire thing because it's an entire page right and the reason is in our layout. TSX you don't need to follow along right now I'm just going to show you what happens so if we didn't have this entire layout. TSX right here and instead we just rendered out the children then of course the hello would be the entire page but we can determine where we want to show the children the children is not nothing else than the page content in this case the P tag of hello and we can render out the children wherever and however we want and that includes below the search bar so literally our entire application depending on the page we're currently on is going to be rendered below the search bar because this layout what you can see at the top here this entire thing is always there um in all pages that we have right we could also Nest this layout that's not what we're going to do in this video but just so you understand understand why the hello is right here below our search bar it's because that's where we are rendering out our children and our children is the page because all the pages go through the root layout of our application right here okay and on this search page the logic is going to happen now first thing we want is to get the search query because that determines what we are searching for that's going to be the search term right and by default this is going to be a server component which is really really nice that's going to come in handy in a second here first step is actually getting the query inside of our page right here and this comes from the search parameters that we get past automatically by nextjs into this page we can simply destructure the search params right here as the props that are passed into the page and this is going to be of type page props now the page props is an interface we Define or S right here at the top interface page props and the type we're going to receive is the search prams and these are an object and each object in here has a dynamic key which is of type string that's why we put it in this array syntax because it's Dynamic we're not going to hardcode any specific key but it could be anything as long as it's a string and the value is going to be either a string or a string array or it's going to be under defined if the search query is not passed at all great what this allows us to do by typing this we can destructure or simply get access to the search prams um do query right here just like that and we now know that the query is going to be of type string or string array or undefined and just like that any other parameter that we passed into the page right this is user input so we don't know what will be passed into here but we can get access to the query just like this and then do some log IC check on it because of course we expect the query to be a string if it's anything else if it's not passed if it's undefined or if it's a string array we can't process it therefore we need to do something called a guard Clause a logic check that determines the rendering of the page basically if this is an array by checking array do is array we can check if this is an array the query um or if the query doesn't exist so if it's like an empty string or if it's not even passed if it's undefined in that case we're going to return a redirect function we get from next SL navigation and we're going to redirect the user to the homepage we cannot process their query if it's not a string it doesn't make sense therefore we're going to send them back to the homepage um until they type in something real you know and now is the time to do the actual um querying logic to get the data from our database that we want to show to the user the thing is we don't have a database yet but luckily creating one is super easy we're going to use postgress for this by the way this is not sponsored I genuinely use this myself I would use my SQL as well but the full Tex search in postgress is just really really nice so because it's very easy for everybody watching this to follow along we're going to use neon dotech which is basically a serverless or I don't even know of they're serverless but a postgress database provider that's all it is if you prefer a local database if you prefer any other kind of postgress or MySQL whatever um to follow along again that's totally cool this is not sponsored I just think this is the um kind of easiest way for you to follow along because in their free tier you don't need a credit card and we can create a database together right here but again if you want to do it locally totally fine as well now the project name can be anything let's just name it the search for example postgress 16 is totally fine and let's also name the database search and I'm going to host this close to me normally this would be close to your users at least to most of them and because I'm just using this locally right now let's set this to Europe Frankfurt and hit create project that's going to give us the database URL we're going to need to connect to this instance let's grab that and create a EnV file inside the root of our project let's call this EnV and by the way we can already add this to the git ignore so whatever we do we're not going to push this into um remote anytime soon and because we want to ignore this EnV file this is going to be a very sensitive um piece of information and let's name this the database URL is going to be equal to this string the connection string we got from our database or this would be your kind of local database if you follow along that way or from any other provider chances are they're also going to provide you with some kind of um connection string that we can use beautiful we can close out of the EnV and now is the time to actually connect to our database and to do that we're going to use an omm let's clear the screen and let's say pnpm install and the orm we're going to use for this is called drizzle dorm that's going to make interacting with our database super easy and we're also going to install yeah let's just uh install that that's going to be it for now um and we're also going to install one other thing that's going to allow us to open like a studio where we can interact with our database and that is called pnpm install minus d as a development dependency drizzle dash kit and hit enter that's going to allow us to locally host a studio where we can see like all the data in our database where we can modify the data delete it if you want um and so on so it makes that very very easy okay now to set up or to interact with our database let's create a drizzle. config dots at the very root of our or project inside of here to make our life much easier we're going to import one type and that is going to be the config type from drizzle Das kit now the type allows us to export default an object from this and we can say setus oop satisfies the config so we get some type safety as to what we are exporting from this file that's going to be a driver of PG because I'm going to use postgress for this build because the full text search in postgress is very nice it's natively supported it's pretty damn fast and very enjoyable to use um if you want to use MySQL or something you would um change that here to my SQL 2 but I recommend also following long and post grass now the schema is going to Define which kind of data structures will live in our database this is going to live under do/ Source SRC so this folder right here in our project sltb schema. TS this file doesn't exist yet we're going to create it in a second together the DB credentials in here is going to be a connection string there's some Alternatives right we could pass like the host and password and everything directly but we only need a connection string and this is going to be our process. env. databaseurl exclamation point to tell typescript yes this really exists yes we actually set this variable in or. EnV file and lastly the out is going to be/ drizzle this is going to be not important for us because we're not going to generate migrations but if you did create SQL migrations then they would be put in this folder for us right now that's not very important but let's set it anyways because it doesn't hurt okay now as for this file what what the hell is this it lives in our source folder SL DB well first of that's a folder that doesn't exist so let's create it and inside of here we are expecting apparently a schema. TS file now what is this schema. TS well basically it defines which kind of data structures live in our database let's let's just look at this let's just do a practical example so um understand what I mean let's export a cons called products table and this is going to be a PG table a postgress table we get from drizzle orm slpg core and let's call this table the products and inside of here this gets an object and here we Define each property of what a product should have for example an ID a product should have an ID it's going to be of type text and this text also comes from the same uh PG core right here let's call this the ID and this is going to be our primary key so it's going to be unique it's going to be like a unique ID and do default oops the default default I cannot spell today we're going to generate for this is going to be uu idore generatore um V4 there we go so basically when we create a new product an ID is automatically going to be generated for us we don't need to worry about it that's all it does the name of the product is going to be also a text let's call this the name and this is going to be not null because we're always going to expect a product to have a name the image ID is going to be also a text and let's name this image ID and this is also going to be not null the price of the product is going to be a double Precision oops Precision there we go so basically a floating Point number this is going to be the price and we also expect this to be not null there we go each product is also going to have a description and this description you can probably guess is going to be of type text description and we are actually fine with this description being null if it's not passed that's not a big problem so we're not going to enforce a not null and we also want a created ad this is going to be of type timestamp we also get from not MySQL core but PG core timestamp there we go and let's call this created ad and the default when we create a new product is going to be whenever we create the product so it's going to be default now and we can simply copy and paste this down once more and change this to update at there we go and let's rename this to updated ad as well so whenever we makes changes to an entry we can see when it was last changed perfect and that's going to be our database structure as easy as that we're going to have a bunch of products each product can have a name image ID price and so on and you can you can add anything you want in here right this is not like this is what I came up with I think this makes sense but if you want a product to have any additional Properties or model relationships with them you are totally fine to do so right here in the schema that's exactly what it's for and while we're here we can already export a type product that's going to make the typescript interaction with us a lot easier and this is going to be a type of products table. dollar sign infer not insert but infer select so what we get back as one product and basically it's going to infer the typescript type from our schema which is absolutely beautiful okay and that's basically all we need to do now last thing we need to connect to our database is going to be an index.ts file inside of our DB directory and that's going to be the DB connector very very simple sounds confusing trust me it's not this is what's going to allow us to actually call the database and get the products right here when we type something in so let's say cons connector is going to be neon and this is from a driver let's install this separately um let's say pnpm install npm install yarn at doesn't matter at neon database SLS server less and hit enter that's going to be a very easy kind of connector that allows us to connect to our database um assuming you're following along with the same provider that I'm using right here um if you're using something else then use the according provider for that and in here we can simply pass in the process. env. databaseurl as well perfect let's import or connector from the package we have just installed like that beautiful and also export a const DB and this is going to be equal to drizzle and that comes from import drizzle from drizzle dm/ neon HTTP there we go and we can simply invoke this with our connector now typescript is not going to be super happy this is a typescript bug this is totally fine to ignore um the DB will work just as expected but we're going to expect an error right here um to make typescript happy this is just a typescript thing we are very safe to ignore and then we are already done with this file now very quick before we can actually interact with our database we need to push the schema what we have just created into our database so it creates a table for that the way we do that super easy npx drizzle dkit push colon PG that's how we can push a post Crest schema into our remote database that's going to create the table we have created right here in typescript in our actual database as an SQL or postgress table in this case and beautiful that's literally all we need to do let's close out of so many of these files and finally actually implement the querying logic so we're going to say let products and you're going to see why we're using let here in a second it's going to be important later and this is going to be equal to await DB that we can now call from our database and by the way in order to use a weight we also need to mark this Pages asynchronous which we can do because this is a server component because we didn't use the use client at the very top right here so we don't want this by default it's going to be a server component by default it's going to run on the server therefore we can mark this pages asynchronous and then we're going to call the db. select basically this is very close to actual SQL syntax just type saave which is the beauty drizzle the omm that we're using and we're going to select something from our products table that we can import right here now we want to do a search right so what do we select from our products table we basically want to filter the products for the search query how does it work how do we do this conceptually so imagine a product it has a name of uh for example parka jacket it's going to be a jacket right and then it has a description of of uh a nice jacket for example so what should happen when a user searches for the term jacket that's going to be our query in this case that's what we called it basically what the user put into the search bar let's give this a bit more space by the way so it looks better then what do we want to do first off we want to go into our database so this will live in postgress land right here postgress where we can do a full text search we can literally get the query from the user jacket go into our database into the product name and product description and retrieve all results that literally match the search query jacket so for example the name right here would match the description right here would match as well and those are going to be the most relevant Search terms so when we search for bomber jacket for example and the name is exactly bomber jacket then even though the query doesn't appear as is as literally bomber jacket in the description it does in the name of the product and therefore this product is super relevant to the search query and we definitely want to retrieve that but what if we search for something like summer jacket that also is relevant with bomber jacket right it's good for the summer as well but we won't find it through a literal match because summer jacket doesn't appear in the name and it doesn't appear in the description that's when we're going to use the semantic search to enhance our search results because summer jacket is in the meaning it has very close to bomber jacket in that case we can retrieve the product name and description from the semantic search we're going to get to that later first off we're just going to focus on the postgress to get exact matches of our search query and the products we have in our database and to do that in postris there's something extremely powerful that is called Full text search that we're going to use right now together so which products are we going to search from our database it's going to be the ones where and this takes a magic SQL operator and what this SQL operator does is it allows us to write SQL or self to interact with a database directly and let's do a full text search together first off we're going to say toore TS Vector this is going to allow us to do a full text search this is going to be simple and what we want to pass in here is going to be the lower which is going to convert whatever we put in here to lower case and this is going to be the products table. name so we are searching for the user query right here right now in the product name that's what we just did right here but we can also involve the description in this so basically we're saying if the query matches literally in the name or also the description right the description is also important and the way we do that is by combining the name with an m empty space just like this and then also inserting the products table. description so basically we're searching in the product name or the product description because we're turning them into one big string and if it matches then the product is relevant to the search query beautiful so right here we're going to say at add which is like postgress specific syntax and then toore tsquery which is now basically what we're searching for and this is also going to be simple oops simple just like this and in here we're going to insert again to lower case so we ignore case sensitivity the query of the user but we're going to process it slightly for example we're going to trim it to remove leading and trading empty spaces and we're going to split this query at empty spaces and then join it back together in this specific syntax with an end sign right here because postgres full text search has a specific Syntax for example we cannot search for bomber jacket like this in postgress full text search this is invalid syntax what we can do is search for something like this where we expect bomber and jacket both individual words to match in the string that we are searching in which is this one right here a combination of the product name and the product description this is valid syntax this is not and similarly if we didn't care if it's either bomber or jacket we could use this syntax right here the or operator and all that would mean is if the query matches either bomber or jacket in the search string right here then we would match the product in our case we want both and we're going to then enhance that with semantic results later on and this is all we need to do a beautiful full Tex search in postgress and we're going to limit this to three results to not overload or page um of course you could show more results that's totally up to you I think three is totally fine perfect let's save that and let's also render out everything we get back from the database instead of a pre- tag and then in here we can kind of say json. stringify products so it's not going to look good but we're going to see everything we get back from the database and of course we still want to start back up our development server so let's give that a second and once that's done loading we can restart our page and then we should hopefully see an empty array meaning that the database interaction worked but we don't find any products for the search query I've entered which is ASD ASD of course we don't have any product for that and very very nice we do get an Mt array and let's see if we get any error we don't get any error perfect our database interaction fully works we connect and query our database in full Tex search now how cool would it be if we can actually already try this out with Real World products that is why don't you worry I've got your back I've created a complete seeding script for or database for you and me to use right now so all we need to do is create a seed. TS inside of our DB folder technically this could be anywhere but it's related to our database so let's put it um right here and listen at the time I'm making this video there is no GitHub repository yet so I'm going to copy the seating script over from the project I've already prepared to make this video of course um for you to follow along I'm going to link the GitHub repository at the very top of the description you can go into that GitHub repository and simply grab the seating script from the same kind of path it's going to be in Source DB and then seed you can simply go ahead grab that and I'm just going to do it right here together with you grab the entire script from the gab repository and paste it right here into the seed. TTS now there's going to be some things that we don't have in our project yet but we also don't need them right now for example the up stash Vector is going to be super helpful later but right now let comment it out and same thing goes for where we use it right down here in the products dot for each where we actually put the products in our database right now we only have um the postgress database set up that's why we we didn't set that up yet but that's going to be the next step for now to get the seating script to run we're going to need two more things that's going to be pnpm install Dov that's going to be at Faker DJs Faker which is going to allow us to generate random product prices and there's a lot more you can do with this but we're just going to use it for that and also we can already install at up stash SL Vector as well and the up/ SL Vector is going to be helpful later in the semantic querying of our product so when things don't match literally right here which might definitely be the case but imagine we're searching for summer jacket and then of course we would expect to find something like a bomber jacket right because it's a good summer Jack check it but we won't find it through postgress it doesn't work like that and that's exactly what the semantic um query is for using up St Vector that's why we need that and with those three installed beautiful or seeding script is or seeding script is done there we go and all we need to do to actually run this to put stuff into our um into our database is to go into our package.json right here and create a script to run this so let's go into the script scripts right here create a new one and let's call this drizzle colon seed for example this can be anything you want but I recommend putting it to that and this is going to be yarn TSX and then do/ Source SL DB seed. TS or of course you can use anything other than yarn like npm run uh for example or pnpm as well I generally use yarn for something like this oh and I forgot TSX is also a package that we want to install pnpm install TSX and all this does is it allows us to really easily run typescript files that's it that's literally all it does it allows us to execute the seating script and that's the only thing we need it for okay with that out of the way let's see if this actually works if we execute this script then or seeding script will be run and we can see like a lot of defined products right here with the image ID and the description oh and by the way the images um are also in the GitHub repository um so you don't only have the products but also beautiful images to go along with the products if you're wondering where those are in the GitHub repository check the public folder that's where they are you can download them and you can simply drag them over into your own public folder we can get rid of everything that's already in there and we can simply grab the images and um copy them over to or public folder just like this and then we also not only get the description and prices and names and everything and so on but also beautiful product images as I said dude I got your back everything is in here and we can just kind of close out of this and then that's going to put um the products inside of an array and then into our um database so let's try if this works let's say yarn drizzle seed or npm run drizzle seed doesn't matter how you execute it and hopefully that's going to run or seaing script successfully if we set up everything correctly so let's let's give this a second to run and then we should be able to see oh it got an error that's not good what's the problem no database connection string was provided to Neon perhaps an environment variable has not been set okay so it seems like it doesn't recognize or environment variable let's try something else let's put this into a env. local and do this again and did reset the environment variable yeah it's process. env. database URL so let's try this let's try it inside of a env. loal um execute the seeding script and hopefully that's going to work no that also doesn't work all right give me a second I'm going to fix this and I'm going to be right back oh and there we go okay I fixed the issue so it was that this env. config was set to a certain path of a env. development that I have in the other project but it doesn't exist here so all we need to do is get rid of this env. config invoke that save the C.S and then bam there we go we can execute or seeding script that's going to put about 21 beautiful products inside of our database and we can now see them in our studio so we're going to say yarn drizzle oops drizzle dkit studio and that's going to launch a local instance oh and we still need the pnpm install PG the postgress driver in order for this to work and properly and once we have the driver then we can start up our studio and they actually see the products in our database so let's click or actually let's let's just copy this right here let's give this a bit more space and then we should be able to see all the products right here beautiful so it's 21 products that we just seated into our database that we can now search for in our application which is the uh which is the best part right we can start or development server and then when once we type in something like um which product do we have here like it's 21 jackets um so for example let's type in the wind jacket into our search engine wind jacket hit enter that's going to search for literally this match inside of our database in the product names right here in the product description right here and then ideally of course uh on start this takes a bit to load but it should yes it should show us the products it matches exactly from our database beautiful that's probably one of the most important parts to building a really nice search engine and now the second part there is to that is the semantic part of it right now we're getting the most relevant results by literally matching the searge query but what about the semantic results like summer jacket and bomber jacket we could never catch that with literal matching but also it's probably the most important part imagine if there was a typo and this was um like boomber jacket or whatever if there's a typo in there neither is the match going to work then of course you could Implement something like fuzzy search but we're not going to get too deep into that but we want semantic results this is really really important and the way we do that is very easy using abash Vector right here and of course full disclosure I Work It Up stash but I also genuinely believe it's the easiest way and best way to do this cuz it's super fast and very nice to do um so the way we set this up is pretty straightforward let's head over to up stash and we can log to create our Vector database um so I've logged in with GitHub let's navigate over to the vector tab at the very top right here and click create a new index now what are we going to call this let's call this uh search for example this is going to be the name of our Vector database I'm going to put this into the EU vest region and as the dimensions we're going to set 153 six this is going to be important later and because these are the dimensions that the model we use for the semantic search is going to use so 1 1536 for here and then as the distance metric we're going to leave this as cosine I could get deeper into y um so here's a very very basic rundown if we have two vectors one and two then the cosign will compare how similar the angle is like imagine this was the angle right here that's what the co sign does and the ukian distance actually compares the um endpoint distance of the two vectors so that's the difference the cosine tends to be a bit better in sparse um Vector spaces which we definitely have in high dimensional Vector spaces like this so that's why we use cosine and not any of the others but these are also very frequently used cosine is best for or use case though now um you can go with a free plan I've already like this my personal account that has credit card and everything so so I'm going to go pay as you go you don't need any kind of credit card you can totally do this on the free plan and then we are going to copy over the EnV variables we get from this the upd vector rest URL and rest token it's just a rest endpoint we can make request or that the SDK will make requests to for us beautiful with that out of the way we can head back over into our C.S file and comment back in this absortion part we have from earlier so this is trying to do an upsert operation on a specific index and an index is nothing else than or vector database we have just created together so let's also comment back in the part at the very top right here the index and that allows us to actually interact with our Vector database and not only insert the products for the literal text matching in postgress but also for the semantic matching we're going to do inside of the uh Vector database so let's try this again let's run our seeding script once more because now again we're not going to just put this into postgres but we're also going to put this into our vector vector database at the same time and this currently depends on one thing that we don't have in our app yet and that is a function we get from SL liip SL vectorize and it should be at the very top right here exactly it's a function that doesn't exist in our project this function is going to be super straightforward let's create a new file inside of lib and call this vectorize do TS and what this vectorize does is basically it takes in text uh let's go to excal draw right here it takes in text for example bomber jacket and it converts this text into a vector and a vector is nothing else than a JavaScript array it doesn't have to be JavaScript but an array of numbers like one 2 5 1 for example in reality these are going to be like subzero uh floting points but just to simplify the concept A bit it's going to be basically an array of numbers and because we've specified a 1,536 dimension it's going to be 1,536 numbers in this array so the array is going to be 1,536 um elements long basically and this is a numeric representation of the meaning of the text so for example bomber jacket and down jacket would be very close together in the vector space meaning they are Sim semantically similar in meaning both are jackets you can wear whereas for example dog and bomber jacket are not as closely related in the meaning they have because a dog is not a jacket of course um so this allows us to then semantically compare different words together meaning if we search for summer jacket then we're also going to find results for something like bomber jacket even though these are not caught by the full teex search which is literally right this is a super super cool step to find relevant search results in our search engine and the way we achieve that is right here with the vectorized function that's going to be very easy so let's export a cons vectorize like this and this is going to be an asynchronous arrow function let's give this a bit more space and we can also open up this it just looks a bit nicer to have it open here and this Arrow function right is going to return a promise number array which is the vector that I just explained to you it's going to return that and it's also going to take in an input and this is going to be of type string so the literal uh let's give this a bit more space again why isn't this in a side by side there we go so it's going to take in the summer jacket and it's going to respond with the vector that I just showed you the number array now the way it does that is by using open Ai and open AI has a super easy to use SDK that allows us to do this let's clear the screen and say pnpm install open AI it's just a light wrapper around a rest API that they're using but with the SDK it's just a lot nicer to interact with this you can already start back up or development server and create one more file inside of lip and call this open ai. TS now inside of here very simple we can export a const open AI is going to be equal to a new open AI we get from the package we have just installed import open AI from open AI which is nothing else than a class we can now instantiate by passing it or API key and this is going to be process. EnV doop AI API key is what we're going to call this now of course this doesn't exist in ourv so let's create open AI API key is going to be equal to and then you can get this key from platform. open.com and we might need to log in right here so let me do that and then the API key comes from right here under API Keys you can simply create a new secret key I've already got five um I I have enough I'm just going to copy one over from my other project but you can create the key here and then copy and paste it as is right here as the open AI API key I'm going to do that from the other project and then close the EnV file and just so I don't accident leak my key but you get the key from right here on the right hand side and paste it right here if for any reason you can't use open AI they've got a generous fre tier so you should be able to this is super cheap um the vectorization very very cheap and very easy to do but if for any reason you can't use open AI then there are probably open- Source models that do the same thing maybe not as well but also pretty good um just search for like um op Source embedding model if for any reason you can't follow along with open AI but um most of the time this is not going to be a problem and it's also free because of their free tier okay so I'm going to copy and paste my token over close the file and then be right back okay so I've copied it over closed the file and let's continue in our vectorize dots because that's all we need to do for the open AI file is as easy as that and then as for the vectorization the conversion into a number oops number array just like this we are going to say con embedding response is going to be equal to await open ai. embeddings do create which is basically an embedding is like a vector that's all it is and we're going to pass in the input and we're also going to pass in the model and this is going to be text embedding ada2 the reason we're not using the larger models is because you can't use an up stash Vector database with more Dimensions that these use without um going for the pay tier so this is possible in the free tier that's why we're using it if you already have the pay tier for upstair Dash you could use a larger model but I want to ensure that everybody can follow along with this video so we're using this model right here with fewer Dimensions okay the vector resulting from that is going to be the embedding response do data at the index of zero because this is going to be an array do embedding and we can simply return the vector from this function which is a number array basically a numerical representation of the meaning of the input that we put in here that then allows us to import that in our seeding script and also Index this into our Vector database so we can access it during the search stage so let's run our seeding script once again that's going to put everything in our database and we we get one more error up stash Vector rest token is missing oh cuz I forgot to copy over the environment variables so these two right here the up stash variables we still need them in the EnV and not in the env. loal so simply copy them over here and paste them as they are in the EnV um I'm going to do that right now but I don't want to show you my open AI variable so I'm going to go in the EnV paste these in here as they are right here and then close out of the file again so we have these exact values in the EnV file and not the env. loal so I'm going to do that and be right back perfect okay and now we can try running our sing script again now up stash will know the credentials that it needs to connect and then we should have have all the stuff in our Vector database as well beautiful so the screeing the the screeing script the seating script ran successfully so if we go into our Vector database now we should be able to see we can close out of that in the data browser that we have 21 different vectors each representing a product like the dark par jacket with its description its price and the image very very nice we just set the groundwork for the semantic varrying mechanism to work that's literally 99% of the work done for it the last step in actually implementing this whenever we type something into the search bar right here is right here below or let products that's why we created this as a let and not as a constant because the thing is if we don't find enough hits from our main database the literal comparisons like if the products. length is less than three for example or less than limit you set it to whatever you want then we're going to also query semantically so we're going to search products for or by semantic similarity so when we search summer jacket we're going to find the bomber jacket for example like I don't think a bomber jacket is part of the data set but you get the point right so the const vector is going to be vectorize or helper function and we're going to vectorize the query from the user and we need to to await this because this is an asynchronous operation then let's query our Vector database for the semantically related vectors to that for example for summer jacket which products are semantically similar we can do that by saying con Dr is equal to await index and the index again we can create it right up here for example const index is going to be equal to a new index from at up/ Vector which is nothing else than our Vector database we can now interact with that's all this is and we can now query our Vector database just like this by saying dot query and then the top K is going to be five this is how many similar products do we want to retrieve in our case let's just um search for the five most similar ones you could also do more like 10 20 whatever you want but I think five is a good fit we're going to pass in the vector that we're searching for and we're going to say include metadata is going to be true just like that perfect okay and now let's say const Vector oops Vector products so the products we retrieved from our Vector database is going to be equal to res. filter the reason we are filtering is because it might very well happen the products we matched from our postgress database literally matched they might also be retrieved from our Vector database and we don't want duplicates that's why we're getting more products than we actually need in our case 5 so we can filter out all the duplicates that might be in there the way we do that is by saying the existing products we get access to in the Callback function of the filter and we're going to do a check if any product if products. sum and for the Callback we're going to do a check if the product. ID is triple equal to the existing product. ID and let's give this a bit more space and let's call this existing product and not products because it is one single product or or let's say or right here if the existing product DOT score is smaller than 0.9 the score is given to us by UPS vector by our Vector database indicating how similar two products are so for example a dog is not going to be very similar dog is oops is not going to be very similar to oops to a a par jacket for example so the score in this case the semantic similarity might be something like 0.8 or something it's not super similar whereas for example if we had a down jacket and a Parker jacket they are obviously very similar it's both jackets so this might be like a 0.93 kind of so it's way more similar and this threshold of what we consider similar and not similar we can Define right here and filter out all the products where we say hey that's not very similar anymore and we are going to set that to 0.9 in this application cuz I think that's a pretty good middle ground now inside of this if function to actually filter this out we're going to return false so they're not going to be included in the array anymore and else we're going to return true because the score is above 0.9 and it is not already present in the literal matches we got from our postgress database so it's a different product but also closely related great and then we're going to map over these and we're going to extract the metadata for each one and simply return the metadata and tell typescript that hey yes these really exist by the way with an exclamation point at the very end so basically saying hey this is not undefined as typescript infers um by default because we are explicitly including the metadata right here so we can be sure it will exist assuming we also said it but we did okay and then we can say products. push and we're going to push in the dot dot dot vector VOR oops Vector products that we have just gotten from our semantic search against the vector database and now typescript is going to complain because it is not assignable to parameter of type yada yada yada now what it's complaining about is specifically these two properties what we get from the vector database doesn't contain a created ad and it doesn't contain an updated ad as well that is why at the very top we're going to define a custom type right here and we're going to say export type core product and this is going to be the overlap between what we store in the vector database and the main database because in the main database schema. we're storing these two properties right here and we're not storing these in a vector database because we don't need so there's no point storing them in two separate spaces so we don't have them in our Vector database but here we are constructing an array the products array that should contain both um Vector database items and Main database items so the overl Laing things like created and updated ad won't exist in both we need to tell typescript that so let's create a custom type and this is going to be omit product we get from our schema and we're going to omit the created ad and the updated ad properties right here oh and these two need to be separated by an or operator not a comma there we go and now we can explicitly pass this core product into the where is it the products right here we can Define this as a core product array just like that beautiful now we're still going to get an error right here for the vector products because we haven't explicitly typed this yet we don't know what we get back as the metadata from our Vector database it could be anything it's unknown and to do that is very simple we can pass the core product as a generic into our new index um instantiation right here and by doing that we can tell up stash Vector hey we know the type of metadata that we're going to get back it's going to be of type core product don't you worry about it and then it is not going to worry about it perfect so we just implemented the core Logic for a similarity search engine and a postgres full Tech search kind of hybrid search engine just like that very very nice work oh and of course we still need to start back up our development server absolutely great job that's all the core logic done and now it's just about making this look good the search results page and then also the product detail page which is going to be easy it's going to be important but it's going to be easy but the really important part the core logic congratulations we have just done that together and we're going to get the most relevant products right here pretty ugly but it works so very very nice job now let's make this look good and instead of the pre we're going to render out an unordered list a UL element right here and give it a class name of padding y4 a divide Dy a divide d z-100 a background white and a shadow DMD and lastly a rounded db- MD so rounded bottom medium inside of this UL we're going to map over the products but only the first three so we're going to say products do slice from 0 to three so taking the first three items from our search results up here and then we're going to map over them and for each product we're going to render out a nextjs link element which is nothing more than like an anchor tag but in the nextjs way of doing things and this is going to get an H ref we need to pass that and this is going to lead to is an interpolation string as a dynamic string right here slash products slash and then the product. ID that we have beautiful and we also need to assign this link something called a key which is going to be a unique identifier which is the product do ID and because we're mapping over something the first element in react that's not next as specific that react always gets a key element um okay inside of here is an Li element and this gets a class name of MX Auto padding y4 padding X of 8 a flex and a space X of four inside this lii element let's create a div element and this is going to get a class name of relative Flex items D Center a background zinc of 100 a rounded dlg a height of 40 and a width of 40 and this is going to be the image the product image that we're showing here so let's create the image from next SL image this is going to be self closing with a loading of eager so it's not going to be lazy loaded that would look a bit weird a fill property an ALT of product- image and lastly a source course probably the most important thing we're doing last year is going to be slash and then the product. image ID just like this let me scroll down a bit so it's easier for you to see there it is this is going to be the image source let's save that and see what happens because we should already be able to see that we get back the product images there we are very very nice what about the rest well that's what we're going to do right now below the div tag right here let's create one more div and this is going to get a class name of with full Flex one a space y of Two and a padding y of one and in here we're going to create an H1 element and this is going to get a class name of text large a font D medium and a text Gray of 900 and inside of this H1 we're going to put the product. name right that we're mapping over the product that we're mapping over the name from that product okay right next to that or below that A P tag with a class name of Pros a Pros DSM for small a text Gray of 500 and a line clamp of three this is one of the coolest hin CSS properties there are basically it means however long this text is if it's like 50 words of lur ipsum the paragraph is never going to exceed three lines that's what the line clam three does we could put it to two then it would always be two lines and that's just insanely cool to make sure that however long the text is it's never going to be out of like the the product area that we're giving to the product right it's not going to be too much and in here we're going to put the product do description perfect last thing we're going to do one more P tag with a class name of text base a font D medium medium there we go and they Tes gray of 900 and this is going to contain the dollar sign product do price and then do to fixed of two so we're always showing two numbers after the comma let's see what that looks like and it looks super nice we're showing the price right there we're showing the text that just looks really really nice and that's or search results page now one thing you'll notice that I don't like is that currently we have search results but the input is empty there's no text in here and that doesn't make sense sense so let's close out of all of these and navigate back into our main page. TSX oh no never mind it wasn't here it was inside of the search bar component there we go this is where we need to go and really quick in order to always synchronize the URL and the actual input field very very neat trick we can do for that and that's getting the search prams in our client component the const search pram oops pams are going to be equal to use search pams that we get from next SL navigation and if we call these search params then we can get the current query so for example con uh let's call this default query is going to be equal to search pam.get and we're going to get the query parameter or we're going to set this to an empty string which is going to be by default and then we can simply pass this default query as the default value into our query state if we save this you're going to notice something cool there it is it just popped up the text right here from the URL so when we type something in like hello world for example and hit enter it's not going to find any results and don't wor we're going to create a nice state for this as well in case it doesn't find any closely related results but we can see the URL and the actual input are now synchronized which is amazing now good question what should happen if you don't have search results let's Implement something that looks good in that case and we're going to do that in our app in our search page. TSX and we know that we don't have any results to show if the products oops products. length is triple equal to zero in that case we didn't get anything relevant from our database or from the semantic search and we can show kind of like an empty state in that case no search results so in that case we're going to return a div element it's going to get a class name of text Center a padding y of four a BG of white there we go a shadow D MD and a rounded B oops rounded db- MD a rounded bottom of medium in here we're going to put an X icon from Lucid react that is going to be self closing with a class name of MX Auto a height of eight a width of eight and a text Gray of 400 right below here an H3 element and this is going to get a class name of mt2 a margin top of two a text small we're going to give this a font D semi bolt semi bolt there we go a text Gray 900 and that's it and inside of this H3 we're going to say no results beautiful and right below here we're going to put a P tag with a class name of margin top one a text of small an mx- auto a maximum width of pros and lastly a text Gray of 500 there we go and inside of this P tag right here we are going to say sorry we couldn't oops couldn't find any matches for and then a JavaScript space so we can always ensure that there's a space between this sentence and what comes next because next comes a span element that's going to get a class name of text green 600 and a font medium font D medium and this should be text green 600 and not the gray 600 and in here we're going to put the the user query so we're going to say hey we didn't find anything for that query and then also put a period behind the closing span so let's see what this looks like right now let's why is this not split screen there we go let's search for Hello World of course we're not going to find anything for that there is no product that's similar to hello world in our database so we're going to get a no results page sorry we couldn't find any matches for hello world just very clearly communicating to the user that hey there is nothing like that in our database what the hell are you searching for now what about the beautiful loading state that I teased in the beginning of the video If you give this a lot more space go to the homepage and search for something like a down jacket hit enter Then There is no beautiful loading set just kind of Waits and then it shows the product results and this is a tiny detail but it matters so much it makes the user experience a lot nicer and I want to show you how that works so inside of our search folder let's create a new loading . TSX file what that's going to do under the hood is use react suspense to wrap this page in the state we Define or in the jsx component as the fallback we Define right here in the loading state so it's not magic it's just a bit of abstraction on top of the react suspense API that is done by nextjs in this specific syntax with a loading. TSX essentially let me show you how this works it's going to be very easy to understand let's export a default function loading from here this is not going to get any props and we can simply return the loading State from here this is going to be a UL an unordered list with a class name of padding y4 a divide of Y A divide gray of 200 and that's give this a bit more space there we go a background of white a shadow shadow DMD and a arounded db- MD arounded bottom medium inside of this UL let's create a new array of length three that we're going to fill with null values because we don't care about what's in the array we only care about the length of it and then we're going to map over this and because we don't care about the values we can simply get the value as an underscore we're not going to use it and oops we don't want curly braces here we want straight normal braces here to directly return some jsx that's going to be an Li element and this Li is going to get a key and the key is going to be the index that we can get from here which is the second argument that is passed into the Callback function that's going to be the key of this Li element and a class name of MX Auto a padding y of four a width of full a padding X of 8 an animate Das PSE a flex and a space X of four and let's give this a lot more space let's already save this so we can get rid of the ugly arrow on the right hand side and let's also format this so inside of this Li we're going to create a self-closing div first now this div is going to get a class name of round it- LG for large a background gray of 300 not 400 300 a height of 40 and a width of 40 and in the loading State this is going to represent the image that's why we give it the exact dimensions that the image also has below that one more div with a class name of with full a flex -1 a space y of four and a padding y of one inside of here let's create one div and this is also going to be self closing with a class name that's going to be highight 10 background gray of 300 around it and also a width of four and then right below here let's open up one more div and this is going to get a class name of space Y 2 to vertically separate the items and in here come three self closing you can just copy and paste so let's create the first one it's going to get a class name of height four height four there we go a background gray of 300 a rounded and a width of 45th and this is how we do that so width -4/5 so 80% basically and holding shift alt and arrow down we can copy that two more times so we have three of those in total perfect and that's that's the loading State that's literally all we need to do for a beautiful loading State let's let's try this out let's go back to our homepage and let's say like down jacket and hit enter and you're going to see the beautiful loading State pop up right there while the search is loading how cool is that very very nice and now the last thing we're implementing is what happens when you click on a product well right now again we get a 404 page that's not ideal we don't want that to happen so the path we are pushing into the URL through the link item that we click on through this one right here currently does not exist so let's create that and we're going to do that inside of our app folder create a new folder called products and inside of this folder one more folder and it's going to be an angled brackets the product ID and why didn't the angled bracket work because I'm on the English keyboard I switched over by accident okay so in angled brackets the product ID that's how we can create a dynamic route in nextjs and inside of here goes once again a page. TSX now what's up with the angled brackets right here what meaning does that have so basically the way we create Dynamic pages in nextjs is like this we can create a page cons page and again this is nothing else than an aror function we export default at the very bottom page just like that um but if you take a look at the URL structure that we have it's SL product SL2 and the two actually represents the product ID right here so it's going to be passed automatically as the name we have in the angled brackets into our page that's how we do Dynamic stuff in next so in order to receive it let's mark this as asynchronous and also receive the prams in here and these are going to be of type page props now the page props is an interface we need to Define ourself interface page page props there we go and it will only have one property which is the prams as an object and inside of here lives the pro prodct ID as a string so how do I know this is the product ID because this right here matches whatever is in the angle brackets you can call this whatever you want but it needs to match the angle brackets and this string right here they need to be the same that's important because that's what's going to be passed in dynamically into the page as we kind of render this and let's navigate away from this because it doesn't look very nice there we go and down jacket let's just see everything works yes it does per so let's build out this Dynamic page right now so the way we get access to this is by destructuring from the prems that's how we get access to the product ID inside of the component and if we don't have the product ID if it's not passed into this page then we're going to return a not found which is basically going to throw an arrow under the hood and show a 4 or4 page for this product if the user enters an ID manually that doesn't exist in our database base to actually fetch the product from our database we can destructure the product from the await DB dot select Dot from and we want to select from the products table and we're going to select the product. wear EQ which stands for equal and seems like the Auto Imports don't work very well right now so let's import the EQ from drizzle omm just like that and this is nothing else than a function in which we can make sure that the products table. ID matches the product ID that is passed into this Dynamic route and the reason we are destructuring right here is because normally in Array comes back of products but we are only looking for one product that matches right here so we can immediately array the structure now if there is no product like that if we don't have a product once again let's return a not found from here that's going to throw internally so technically we don't even have to return this but I think it's much more readable if we do um so we're going to throw a 404 If the product the user is searching for doesn't exist in our database if it does though and we can assume it does because we're behind the guard Clause we know this product exists now then we can return the jsx we want to render out that's going to be a div at the top level with a class name of padding Y8 padding bottom of eight a padding X of 12 a divide --y a divide zinc of 100 a background of white a shadow D MD for medium and a rounded db- MD for bottom medium perfect inside of here we're going to create one div and inside of this div we're going to create a back button this is going to be a custom component and what it's going to allow us to do is after clicking on a product there's going to be a button that takes us back to the search page um which is just really nice for you user navigation throughout our website this component doesn't exist yet so let's create it it's going to be a very basic component so let's create a new file and call this back button. TSX and the reason we're creating this as a separate component is so we can mark it as a client component because again by default or page is going to be a server component that's why we can just query our database like this but the back button should um happen on the client side because we want some interactivity in here so the con back button is once again nothing else than a regular Arrow function that we export default at the very bottom back button there we go and from here we're going to return a button we get from our UI library and add some stuff on it for example we're going to add the class name and this is going to get a flex a gap of two an items Das Center a text DSM and also a padding bottom of Two Perfect now the button should take us back to to the search page how do we do that very easy first off we need access to the router from use router we get provided to from next SL navigation and all we need to do now is to give this button onclick Handler and onclick we can simply call the router. bag method which is like clicking the back button on your browser pretty much you know very very nice and it also allows us to keep the cache between navigation for instant page transitions which is incredible for the user experience inside of this button we're going to render out a Chevron left icon to indicate like an arrow to the left and this is going to get a class name of height four with four and right below it we're going to say back and now we can go ahead import the back button right here save everything and now we can actually click on a product and we should be able to see the back button right here beautiful one thing we didn't pass into this button yet is the variant we want because right now this is very like very visually like the black doesn't look good so we're going to pass in the variant and that's going to be secondary so it's going to be a bit less stingy to the eye it's going to look a bit more subtle very very nice let's format this save the back button that's all we need to do here looks amazing and if we click it it takes us back immediately to the cached results of the search how cool is that beautiful okay right below the back button still inside of the div let's create one more div and this is going to get a class of margin top four inside of this diff let's open that up it's going to live an H1 element that's going to get a class name of text 3XL a font D bolt a tracking Das tight a text Gray of 900 and on small devices a text of 4 XL just a little bit larger and this is going to contain the product. name let's save that see what it looks like dark down jacket very very nice now with the closing H1 and one closing div and two closing divs to go that's where we're going to work now and let's create one more div right here this is going to get a class name of aspect Square so a 1:1 ratio an my y of six a margin y of six a border a border Dash border and this might look weird but border is just a CSS variable color that we've declared right here so essentially we're saying the Border should have a color of what we called border again looks weird but it does make sense and a width of 52 and a height of 52 inside of this div one more div this is going to get a class name oops not children a class name of relative a background zinc of 100 withth of full height of full an overflow Das hidden and a rounded DXL and inside of this div we're going to render out the product image that comes from next SL image it's going to be self closing and this is going to get fill a loading of eager so it's not going to be lazy loaded a class name of height full with full object D cover and object D Center to kind of align the image a source and this source is going to be there we go as a template string the Slash and then product. image ID so for example um the dark down jacket 1.png that's going to be the image ID right here and last thing is going to be alt and this is going to be product image let's hit save see how that looks like we should see it show up right here there is the product image absolutely beautiful nice okay so two closing divs down with two more closing divs to go let's continue let's create a new div with a class name of margin top 4 open that up and that should be class name there we go okay now inside of this div one more div this gets a class name of flex items D Center and in here lives A P tag this P tag gets a class name of font D medium and a text Gray of 900 and inside of this speag we're going to put dollar sign and then the product. price. to fixed two which is going to fix it to two numbers after the comma and if we save that we can see the product price appearing right here very very nice okay with three closing divs to go this time we're going to create one more div with a class name of margin top 4 and a space way of six for the vertical spacing and a P tag in here with a class name of text-base Maximum width of pros and a text muted D foreground which is a specific class we can use it's a color um that we're going to use for the text in this P tag and this is going to contain the product. description just like this let's save that see what happens there is the product description absolutely beautiful and last thing in here is going to be with three closing diffs to go one more diff with a class name of margin top six a flex and an items Das Center there we go and inside of here we're going to create a check icon we get from luced react and this is going to get a class name of height five with five a flex shrink of zero and a text green of 500 and right below the check we can put a P tag that's going to get a class name of margin left 2 a text of small and a text muted foreground as well just like the P tag up here with a product description and inside of here we're going to say eligible for express delivery and honestly this just looks nice it doesn't hold that much meaning but it just looks like an actual e-commerce web website in this case which I think is pretty important all right and as for the very last part with three closing divs down and one more closing div to go let's create the last or one of the last divs of this build this is going to get a class of margin top six inside of here we're going to create a button this comes from our UI library and in this button we can say add to cart and the class name this button is going to get is going to be with full and also a margin top of 10 to kind of space it out from the rest and below this button we're going to create a div element and by the way we can already see what this looks like right here add to card beautiful and inside of this diff we just created it gets a class name of margin top 6 and a text- center inside of here let's open up one more div this is going to get a class name of inline-flex a text a text a text- smm a text small and a text- medium there we go let's open this up and in here we're going to put one more icon and that's going to be a shield icon from Lucid react or icon library and this icon is going to get a class name of margin right two a height of five width of five Flex shrink of zero and a text Gray of 400 beautiful right below the shield we're going to put a span element and this is going to get a class name of text muted foreground as well and on Hover we're going to give it a text Gray of 700 and inside of this span we're going to say 30-day return guarantee once again this is more decorational this is something you want in an actual e-commerce context probably to just increase trust from the customer and that's it that is our search engine it's all the code we need let's navigate to the very start and let's see if everything works correctly so if we type in some something that doesn't exist yet we're going to get a nice beautiful loading State and then a no results page indicating that there are no matches for or search query and if we enter anything else super fast we're going to get these search results exactly matching our search query or if we have something like a winter jacket and the main literal matching search results from our um postgress database are then enhanced with semantic search for this hybrid search engine that we created and once we press on a product we get taken to the product page we can add it to our card immediately it looks super super nice and we can immediately go back to the cach results for an amazing user experience absolutely nice job following along I really hope you enjoyed this build and building along with me this pretty damn fast and nice um search engine that matches literal text and also combines it with semantic search I really hope you enjoyed hey dude and I mean that good job you created something really cool a literal search engine you can put this in an e-commerce shop you can fine-tune this as much as you want there's so many ways for you to build on top of this with the postgress database already baked in I really hope you now understand how to use an Norm how to interact with the database how the entire data flow comes together to provide the user with really cool search results and I kind of hope you also enjoy the design I know it's secondary but I think design is super important and we paid a lot of attention to the design in this video as well so I really hope you enjoyed once again I keep repeating myself and that's going to be it for this video if you liked it make sure to drop a comment I'm going to do more of these and uh maybe also like the video that that's it right that's that's already it all right dude I'm going to see you in the next video Until then have a good one and bye-bye
Info
Channel: Josh tried upstash
Views: 23,394
Rating: undefined out of 5
Keywords: react, typescript, nextjs, search engine, postgres, postgresql, neon database, neon, upstash, upstash vector, josh tried coding, joshtriedcoding
Id: _cqFkK3WLvg
Channel Id: undefined
Length: 110min 14sec (6614 seconds)
Published: Fri Mar 15 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.