Supercharged Search With Laravel and Typesense - 4 Hour Course
Chapters20
The video introduces Typesense as a fast search tool and teases the 22-episode journey covering basics to advanced topics like semantic search and vector queries.
A practical, step-by-step guide to building a Laravel search app with Typesense, covering setup, schema, importing data, queries, filtering, faceting, and even semantic search and instant search integration.
Summary
Jeffrey from Laracasts walks you through getting supercharged search in Laravel using Typesense. He starts by installing Typesense locally (via Homebrew, Docker, or binaries) and then spins up a fresh Laravel app to wire in the Typesense PHP client. The video delves into core concepts like documents, collections, and the importance of a well-defined schema, including how to set a default sort field. You’ll see practical data import using the included books dataset, and how to verify the server is up with a curl health check. The course then moves into querying, highlighting results, and progressively building a playground of routes that demonstrate everything from basic searches to filtering, sorting, and pagination. Jeffrey emphasizes best practices for environment configuration, service binding (via Laravel’s container), and using Laravel Scout to synchronize Eloquent models with Typesense. He also covers advanced topics: faceting (authors, publication year), vector/semantic search, and even how to implement an instant search UI with Alogia/Typesense adapters. The hands-on workflow includes debugging techniques (like data import errors due to schema mismatches), and practical UI refinements with Blade and Tailwind, all while keeping the examples grounded in real-world apps like Laracasts courses and book datasets. By the end, you’ll be equipped to implement complex search features, including overrides, analytics, and ready-to-deploy cloud options.
Key Takeaways
- Install and run Typesense locally on macOS (Homebrew) and verify with a curl health check against http://localhost:8108/health.
- Define a collection (Books) with fields like title (string), authors (array of strings), publication_year (int), ratings_count (int), and average_rating (float).
- Import a large dataset (books.jsonl) into Typesense and verify collection schema via curl to /collections/books.
- You can switch to TypeSense Cloud for hosted deployments, and Jeffrey notes memory considerations when using embeddings (recommend 2GB+ for embedding workloads).
- Use Laravel Service Container to bind a pre-configured Typesense client for reuse across routes, avoiding repeated config code.
- Faceting requires marking fields as facets in the schema (e.g., authors, publication_year) and rebuilding the index to enable facet counts in results.
- Typesense supports advanced features like text-match-based sorting, multiple tiebreakers (text match, publication year, ratings), and vector/semantic search using embeddings and cosine distance to relate query vibes to documents.
Who Is This For?
Essential viewing for Laravel developers who want to add fast, advanced search capabilities with Typesense. Those upgrading from Laravel Scout or building search-first apps will benefit from the hands-on, practical approach to schema design, data import, and UI integration.
Notable Quotes
""Typesense makes me feel not dumb and trust me that is high high praise.""
—Jeffrey frames Typesense as a friendly, approachable search engine to motivate learners.
""We start by installing Typesense locally... and verify that it’s up and running with a curl health endpoint.""
—Shows the practical first steps to bootstrap the environment.
""A collection is like a table and a document is like a row in that table.""
—Key Typesense terminology explained clearly.
""Facets allow you to group results by a field like authors or decade, making it easier to drill down.""
—Explains the power and use of faceting in real apps.
Questions This Video Answers
- How do I install and start a Typesense server for a Laravel project?
- What's the difference between a Typesense collection and a document?
- How do I enable faceting for a field like authors in Typesense?
- Can Laravel Scout synchronize Eloquent models with a Typesense index automatically?
- What are the best practices for semantic search and embeddings in Typesense?
LaravelTypesenseTypesense CloudLaravel ScoutSemantic SearchVector QueriesFacet/FacetingInstant SearchTailwind CSSInertia.js or Vue (SPA groundwork)
Full Transcript
It's fast. Super fast. Lightning fast. [Music] Welcome to Supercharged Search with Typesense. Hey there, I'm Jeffrey. All right, so before we get started, bear with me. I have a quick 30 second story for you and don't skip over it. Okay, so when I was younger and I was learning how to perform advanced search, I would review certain unnamed tools and I would come away feeling like I'm too dumb for this tool. I don't know, can you relate? I would read the documentation and think, "Wow, that didn't help me at all. I actually have no clue whatsoever where to begin." And so, yeah, as a result, I just wouldn't use the tool at all.
And you know I think if typesense had been available back then I sure could have avoided so many headaches and so much confusion. Okay because you know what if there's one thing I can say about typesense and actually there's many things I could say but if I had to distill it down to one thing only I would say that typesense makes me feel not dumb and trust me that is high high praise. Okay, so I have prepared 22 episodes of TypeSense goodness for you. We start at the beginning and we work our way all the way to the top.
So you're going to learn about basic queries and setup and filters and sorting and ranking and semantic search and vector queries and TypeSense cloud. There's so much to cover and I really do hope you're excited. So let's get started. All right, let's get started. I hope you're excited. So, we're going to start by, of course, installing Typesense. I'll click on guide and install. Now, of course, depending upon your OS, you will install this uh in a different way. And of course, if you're following along with a course like this, you already know what you need to do here.
So, option one is Typesense Cloud, which is sort of their turnkey service that you should take a look at. We will review that later in the course. So, for now, we will install it locally on our machine. And yeah, if you prefer Docker, here you go. If you want to use Mac with Homebrew or the binary or Linux or Windows, just find the one you like uh and you'll get started. So, I'm on a Mac and I actually like to keep it simple. I'm going to use Homebrew here. So, these are our commands. We want to install Typescent server and then we will uh start the service so that it's always available to us.
All right, let's get going. I'll switch to my terminal. paste that in and give it just a minute to do its thing. All right, so of course I already have Typesense installed on my machine, but on your end you'll see something along the lines of binding to port 8108, which of course is the port that you will connect to from your Laravel app in the future. All right, so now I just want to verify that everything is up and running. So here's what I'll do. I'm going to make a curl request to localhost. And like we discussed, 8108 is what it binds to.
And we'll go to the health endpoint. And sure enough, I get okay, true. We're all set to go. All right, let's get started. I'm excited. So, we're going to create a generic Laravel app. And I'm just going to call it, I don't know, supercharged search. All right, no starter kit. It's going to be pretty bare bones Laravel. SQLite is fine. We can install our dependencies. Very good. So now we can view this in the browser. Uh-huh. We have our basic Laravel splash page. Great. Next, let's CD in there and open this in my editor. All right.
So now, while we're thinking of it, let's open up our environment file and set some initial values. So I will scroll down to the bottom and yeah, we know that our Typesense host is local host, right? and the port is 8108. So let's do this. Type sense host equals of course localhost and type sense port is 8108. And then also we're going to need an API key, right? So let's do API key equals. And what should this be locally? Well, if we switch back to the documentation, let's scroll back to my homebrew installation method. And yeah, right here you'll see the default API key is XYZ.
And again, the port is 8108. Okay, so let's set that here. X Y Z. All right, so now if we need to reference any of these values in our project and hint, we are going to uh now we have those available to us. Very cool. Okay, so the next step is to set up our PHP client. So once again, back to Arc. Let's go into install a client. And of course, they have client libraries for PHP. So let's take a look at that. All right. So, let's scroll down and let's copy the composer require. I will paste that in.
All right. And believe it or not, we're all set to go. So, yeah, think about what we did in this video. We installed Typescent server. We started it. We verified that it was up and running by making that curl request, right? Then we set up a fresh Laravel app and we assigned some initial TypeSense specific environment variables. And then finally, we finished up by pulling in the Typesense PHP client that we're going to make use of in the next episode. I'll see you then. All right. So, now that we have Typesense fully installed and ready to go, of course, the next step is to perform a search.
So, let's get going. First, we need some kind of data set, right? So, here's what we'll do. Let's return to the Typesense documentation and down to building a search application. And yeah, you'll see they have a huge books database that's going to be perfect for our needs. So, we will pull that in. And then later, we're going to work with something a little more custom. All right. So, notice it goes to your temp directory and it downloads this uh books file and unzips it. All right. So, let's copy that, paste it in. All right. And yeah, now we have this books.json file.
So, if I were to catch that is really big, but you can see it is a massive collection of books that consist of a title and authors and ratings and everything that we might want to search or filter upon. All right, so why don't we do this? Let's move it out of my temp directory into my code/supcharge search directory. All right. So now if I switch back to PHP Storm and yep, here's our massive books database, uh, so to speak. We're all set. All right. So now let's go into our routes file. And this is a good place to play around and connect to our client.
All right. Let's get going. So the first step is to create a new client. And make sure you pull in the right one. We want Typesense client. All right. And next, we need our configuration. For example, the API key. Uh, what is the host? And you'll remember in the last episode we assigned those to environment variables that you see here. So as you may know in Laravel it's bad form to um access the environment variables directly. Instead we should go through our config. So let's put this within services. And then if I scroll down I can place it right here.
So we're going to have typesense and yeah I'm just going to reference everything we created. For example, API key will be in the environment types API key. All right. Next, the host and the same thing Typesense host and then finally uh port right will be environment type port. Okay. So now we can safely uh reference those values. All right, back to our routes file. All right, so now we can get going. Let's declare the API key. And yeah, we're going to look in config in the services file. The key is type sense and we want API key.
And you know what? Already I can tell we have inconsistent naming here. It's fine, but I'm going to get tripped up. So I'm just going to normalize that. And we will use snake case. That's fine. Okay. So let's switch back. Paste that in. And next we can set up our nodes. And this will be uh what's our connection? Local host. What's the port? What's the protocol? So let's do this. The host is going to be config services.typesense.host. The port will be services.typesense.port. And let's also do the protocol uh http https. So why don't we make that configurable by returning to our services file and we'll add another one here for protocol.
Yes. So the entire point of all of this is so that we can easily swap out these values in a local environment versus staging or production. So in my environment file, I will paste this in type sense protocol and we'll set that to HTTP. That looks good. All right, back to our routes file. This is looking pretty good. Uh the last thing I want to do is set up the connection timeout. So this is what you think it is. How long uh before we can't connect? Let's set it to two. And of course, uh, that can be configurable if you want, but I think it's okay for now.
And yeah, we have our client. So, let's do this. Uh, before we do anything else, let's die and dump this object and see what we're working with. So, I'll die and dump the client. And then I'm going to access our projects homepage and the browser. All right. And sure enough, we have an instance of our Typesense client. And yeah, we can see it consists of a lot of things. We have the config. We have collections which you can sort of think of as um tables. We have aliases and keys. Don't worry about this right now.
The important thing is we're up and running. Okay. All right. So, I have two definitions that I want you to memorize. First up is document. So, in the context of Typesense, a document you can sort of think of as a table row or a JSON object, right? So for a book that object might consist of the the author of the book, the title of the book, the release year, things like that. So that collection there, that object in types sense you can think of as a document. All right. And now your next definition is collection.
So once again in the context of typesense, a collection is sort of like a table. It's synonymous with a database table. A table consists of rows. A collection consists of documents if you want. It's sort of a one:one link there and that will really help you understand it. Okay. Collection table document table row. Does that make sense? Okay. So, let's create our schema. Let's create a collection. We are working with books. So, I'm going to call it book schema. Okay. So, this is standard type sense syntax here. We need to give it a name. What is the name of your collection or what is the name of your table?
We'll just call it books. That's fine. Next, what are the fields? Again, think of it like your data. Use all of your database knowledge here. What are the fields in this table or in this collection? Well, what are the things that I want TypeSense to know about here? Maybe the title, maybe the author, maybe the publication year. That would be fine. when did it come out? Maybe the ratings count, maybe even things uh custom like the average rating. Yeah, we can we can dynamically uh determine these values and sync them up. And I'll show you a number of ways you can do that in your own projects.
Uh but yeah, things like this are probably what you would want. And if we switch back to our books database here, we can quickly see we have title, we have author, we have publication year, we have average rating. Yeah, we're only going to include things that would make sense to sync up with Typesense. All right, things that we might want to search or filter upon. Okay, so let's get going. Our first field has a name of title and we need to give it the type. Well, a title is of type string. There we go. Done.
Next, author. Well, if you think about it, a book can be written by more than one author. So in this case, the type is string, but it's really sort of like an array of strings. That's one way to think of it. So here's what we can do. Use this common syntax here. All right. So now it can be one or many strings. Cool. Next, publication year. So here it's not going to be a string. It's going to be effectively a number, right? So we're going to set the type to int uh and 32-bit. All right.
Next, the ratings count. This also will be int32. And by the way, there's also int 64, which you might want to use for timestamps and things like that. There's not many types, so don't worry about this. Okay. And then finally, we're going to have average rating. So, let's update this one. And yeah, if you want, you could use um in 32. You can even use a float, which might make sense. And yeah, this is basically our book uh table schema. That's a way to think of it. Okay, so now I want to decide what is our default sorting order.
And here's what I mean. If I search these books and we have multiple books that match your search query, for example, if I search for H A, well, I'm immediately thinking Harry Potter there, right? But maybe there's another book that also begins with H. Maybe the Harry Truman biography or something like that, right? So in those situations like how do we determine the correct sorting order? What is the importance level? What is the logic that determines uh when one might come before the other? And yeah, this just depends on your application. So in our case, we might want to say well the number of ratings is most important or even the average rating.
So the book that has a much higher rating should naturally come before one that has a horrible rating, right? probably not as good and not as likely to be the thing that you're searching for. So yeah, it just depends on uh your collection and how you want to organize things. I'll give you an example for Larass when we have courses that you search on. Well, how would you want the default sorting order to be for a programming course? Well, it could be the number of comments in the course. It could be the release year. It could be um the number of views the course has received, right?
And these are all things that factor into the equation. For example, what if a really old Laravel course has so many more views than a new one? Well, it's still not the one that I want to show up first, right? Because it's so old and outdated. So often you will want to take multiple things into account. You want to consider the release year, but maybe also the number of videos, uh the number of comments, the number of views. And there are ways to assign weights to each of these components. and I'll show you that later in the course.
Okay. So, anyways, for now I'm going to set the default sorting order. And by the way, it's default because you can override it if you need to uh as part of your search query. But by default, we're going to sort these by the ratings count. And I think that's fine. And whoops, I'm sorry, not default sorting order, default sorting field. What is the field that is our default for sorting? Okay, this is looking good. So now that we've effectively defined the schema for a collection, let's go ahead and create it. And here's how. We already have our client object up here.
So we'll say client collections. And we're going to create a new one. Nice and readable. Let's pass through our book schema. Finally, I will return done. And yeah, let's give this a run by opening the browser. So we give this a run and we get done. I think we're all set to go here. So, let's inspect this. Let's do a quick curl request to ensure that we do have a books collection up and running. And yeah, I'm just going to paste this in because it's a little bit wordy. But very quickly, we're going to perform a curl request.
I'm going to set the request type to get. We're going to once again hit that localhost port 8108 that we bind to. And then specifically, I want the collection/books endpoint. Again, books corresponds to our collection name. So, we're going to make that request. But, of course, to authenticate or authorize that request, we need to send through our API key. So, we do that here as a header. So, if we give that a run, sure enough, we can see all of the schema that we defined. But yeah, if we were to try something else like courses, well, that doesn't exist.
So, we get not found. We're up and running here. And yeah, if you want, you could even save this to a little alias if you want a quick sanity check to make sure uh everything was configured properly. All right, so now that we've defined the schema for our first collection and we've created it, the final step for this video is of course to import our books and then we'll be all set to go. So here's what I'll do. I'm going to comment this out and I'm going to keep everything you see here just so you can review it in the source code.
But yeah, of course, if I were to hit this endpoint again, we would see an error because we've already created the books collection, and we don't have to do that again. Of course, you would only do this once. All right, so now, right down here, here's what I want to do. I basically want to fetch this file and then import it into Typesense. So, here's how. Let's say file, good old file getcontents. I'm going to look in our project base path and it's called books.jsonl. All right. So, that's our data. We'll save that to books.
If we die and dump books, you'll see a massive collection here. That's what we want. All right. Now, I can say in our client, go into your collections. And specifically, we want the one for books, right? The one we just created. So, grab that collection. All right. And now, I effectively want to import a bunch of documents, right? And remember, a document is a synonym for a table row. It's just types and speak for a table row. So let's say for the documents, let's import all of these books. And then we'll say books imported. All right.
So back to the browser, refresh, and sure enough, we get books imported. And that's a wrap for episode 3. Okay. So now we've made a little more progress, right? You've learned about collections, you've learned about documents, you've learned how to import a mass of data. In the next episode, we're going to query against that data. Now, before we perform our first search, why don't we do just a little bit of organization, mostly to make it as easy as possible for you to copy and paste whatever you need. So, here's what I'll do. I'm going to copy this route and I'm going to rename it to well, what did we do here?
We created a collection. So, I will call it create collection. Now, keep in mind these are not endpoints you actually want in your app. We're just using this uh as a as a playground. If you wanted to configure these as artisan commands, that would be fine as well. They're not intended to keep, just to play around. Okay. So, now if I paste in the next one, I will call this one import collection. And each one will be like a self-contained snippet uh for how to perform an action. So for example at the top to create a collection here's what we need we'll say collection created.
All right next to import a collection once again we are duplicating here but that's okay. We already have our schema and we've already imported it. So all we would have to do is get the database get the collection and then we would uh defer to books and import them like so. All right. So, now we're going to do a third one, and this is going to be our playground for searching um a collection. All right, so here's what we do. We have our client. We've already imported these, so I can get rid of it. And let's get going.
We'll say client, collections, books is the name of our collection. And I'm going to search through our documents or search through our table rows. So, documents, search. All right. What is the query? Uh well, let's start by hard- coding it and then of course later we will make that available through the query string. So yeah, maybe we can search for um a book I like is called dark matter. So let's start by looking for dark as our query. All right, this will give us our results. And why don't we dye and dump the results and have a look in the browser.
So take note of this endpoint. Switch back. Go to search collection. And we get no search fields specified for the query. So here's what's going on. It's saying, "Hey, you're trying to query against this collection, but you didn't tell me what we're looking in. Are we looking through the title? Are we looking through the author?" We need to be explicit about that, right? So let's switch back and add our search fields query by. And we're just going to look at the title for now. It's the only thing I want factored into the equation. All right, so let's see.
Oh, this is interesting. Zero found and zero hits. That's not right. So, here's what I'm thinking. Uh, maybe we made a mistake importing our data. So, here's what we can do. Let's once again run that command to check our books collection. And yeah, this shows all of the schema like the name, author, title, publication year. But at the very bottom, we should also see aha, here it is. So yeah, at the bottom it shows the number of documents that are available here and it's set to zero even though we performed that import. So here's what I'm thinking.
We probably have some glitch related to our schema. So let's scroll up to where we import the collection. So API key, this looks good. Yes. Yes. Yes. Yeah, it must be something related to the import. So here's what we can do. We can return the response and this will show us for each document um if it was successful, if there was an error. So, here's what I'll do. Let's run this route again, but then die and dump the response and it'll tell us if we uh misconfigured something. Import collection. And aha, we found it. We should have checked for this.
So, yeah, we can see we have a 400 because the author has been declared in the schema, but it wasn't found in the document. So, it's really important that we sync these up. So, of course, in this case, I know exactly what the problem was. When we defined the schema right up here, I used the name author. But of course, because it is potentially an array, the actual schema key is authors. And we can verify this by looking in the books database. And we can see, yep, it was set to authors. So, this was my mistake, but you know what?
I'm going to keep it in the course because uh well, it's just a great example of how you can go about debugging uh some of these things that you might run into in your own projects. Okay, very cool. So, let's do this. Let's reimpport this. And here's what I can do. Because we have this handy dandy uh playground route, what I will do is at the very start before I run this, I'm going to uh say for the books collection, just delete it. We're going to start entirely from scratch. So, delete the existing books collection and then build up a new one.
Okay. Switch back. Let's go to create collection. There we go. Next, I will import the collection. Yep, lots of success TRS. So, if I go back to the command line and we run this again, you'll see that now we have almost 10,000 documents. And this is what we want. Okay. So, now, sorry about that. So, now if we swap back down to the very bottom, uh, we can try this out. So, right down here, let's get rid of that. Let's search the collection. We search for dark. And this time, let's see what we get. All right.
Now, we get 134 results. Let's have a look at the hits. So, what is the topmost hit? Let's see. We have our document, which again, I'm just drilling this into. Think of it like a table row in your head. And sure enough, we can see the top result is a book called Dead Until Dark. And the author is Charlane Harris. Let's just keep going. Let's look at one more. Uh, we can see this one's called Dark Places. Now, here's a cool thing that Typesense offers. you'll see a highlight section. So, this is exactly what you think it is.
It will show us the title, but it will highlight the relevant part that matches your search query. So, you'll see if I go into highlight for title, here is our snippet. So, notice we have the same title, but it wraps uh the part that matches your query within mark tags. And then when you display your result, you can highlight it, set a yellow background. Uh you've seen things like that a million times, right? So if I were to change this to d a and if we come back and refresh now of course only the d portion will be highlighted as you can see right here.
Very cool. All right. So now let's do this. Let's swap out this hard-coded query with something that comes from the request. So I'll say look in the query string for Q. And of course, you probably want to validate this as well, but we're going to skip over that just uh in the playground phase of our learning. Okay. So now, for example, if I were to search for Q equals Harry, well, I would imagine that Harry Potter is going to be the top result. Let's see. Open up the documents. And sure enough, we get Harry Potter and the Philosophers's Stone.
Very cool. But now, here's something to consider. If I instead search for Rowling, the writer of Harry Potter, and if we give this a run, well, we get just one result. And you know what? I bet it's not what we expect. We take a look and sure enough, we get the howling. And this is actually uh an interesting little note here. It matched howling because it's assuming that possibly we have a typo here. So, Typesense will take that into account. even if maybe you have a mistake here or there, it's smart enough to figure out what you possibly uh meant.
Now, in this case, I search for rallying and maybe I would still expect Harry Potter to show up, but it doesn't in this case. And if we switch back, we can see why, right? We are querying by the title. We didn't say query by the author. We said no, the only thing I want you to use here is the title of the book, not the authors. So, here's what I could do. I could do a comma-epparated list here. And now I can say, yeah, look in the title, but also look in that array of authors.
So if I come back and we give this another run, this time we have many more results. Go to the very top, go into the document, and yeah, once again, now even though I'm searching for Rowling, it matched it. And of course, that's the most popular book in the world. So, uh, it brings that up as the number one result. Check the author. And we have JK Rawling. And I don't know this this is maybe like the translation uh for this edition. I'm not entirely sure. All right. So, let's do this. Let's wrap this up.
Uh we have our results. So, I'm going to wrap this within a Laravel collection. And I just want to filter this down here. So, let's go into the hits. And then this is an array. And I'm going to say for each hit I want to pluck the document. And maybe let's just grab the title of the book. For now, I just want an array or a collection, a Laravel collection that contains only the titles uh for the results. So, here's what I might do. I'm going to map over it. And for each hit, I'm going to return the document.
All right. So, now we're going to have a collection of only the documents. And then within the document, I'm just going to grab the title. So, why don't we say hit document title. All right. And hopefully we did everything correctly here. Let's return the titles. Come back, give it another run, and sure enough, if we pretty print, we have a an array in this case of all of the matches. All right, so notice that philosopher stone comes first and then the cursed child comes last. This is kind of what we would naturally expect, but of course, you have full control over the sort order.
So why don't we play around with this? So here's what we can do. I can say sort by and we're going to provide some kind of um schema value that we want to sort according to. Again, do we want to sort according to the ratings count? You could even sort according to a release year. Any kind of number uh by default will work just fine. Uh we can even take that further. But as an initial starting point, think of things that are numbers that will help you. So for example, if I want to sort by ratings count in descending order, this is how we might do this.
So if I come back and give this a refresh, we can see this result here. Why don't we reverse it? Let's do ascending order, which means the least ratings count will come first. Once again, give it a refresh. And now we can see, yes, sure enough, we're going to get some kind of book about that universe, but not one of the core books will come first because of course those will have lesser ratings than the actual novels. So everything's working great. And yeah, you have full control here. You can even combine things. So for example, by default, TypeSense has this thing called text match.
And this is just a special keyword that represents how closely the query matched uh the the results here. So I could say text match in descending order and yeah this is going to give you u possibly what you expect. So notice yeah in this case it's not really factoring in the ratings count because we overrode that. We said no just focus on the text match. And in this case, even though Harry Potter in the Philosopher Stone and Harry a history uh are wildly different in terms of what you're looking for, if you think about it, they both begin with the word Harry, right?
So, in the eyes of Typesense, in this case, they are matches. So now sometimes you have to think about well what do you do in the case of a tiebreaker if Harry Potter and the Philosopher Stone and Harry a history both have the same score because they both start with Harry? Well, what is the tiebreaker? And we can have multiple tiebreers if we want. Here's what we can do. Why don't we focus on the text match but then in the event of a tiebreaker let's say hm what is the thing that determines whether one comes before the other?
And yeah, we'll fall back to ratings count if we want. Ratings count in descending order. The one that has a higher ratings count will come first. Come back, reload. And now once again, we can see, okay, these all start with Harry and they have much higher ratings count than that uh biography, Harry, a history. So, of course, they should come first in the result set. And this is what we want. Now, in this case, notice we're getting the cakuz calling. I'm not even sure what that is. That's probably matching against uh one of the authors.
So, if I get this uh out of there and we give it a reload and uh I'm sorry, let's update this to Harry. Now, we'll see uh the results that we'd probably expect. Great. So, once again, if we switch back, we can reverse this and say, "No, let's focus on the least ratings first." All right. Now, we're once again going to see uh one of these biographies. Full configurability at your fingertips. Let's do a little more. Uh let's say well yes focus on the text match but maybe h maybe the release year should be factored in.
Maybe you consider that more important. Uh an ancient book that has a lot of ratings maybe uh for your search platform isn't as important as a more recent book. So in that case we could say publication year in ascending order would be oldest or descending would be favor the newer ones first. Okay. So now if I switch back, yep, we can see Harry Potter and the Cursed Child um of course was released much more recently than the original novel. So it comes higher up on the list at the top. Cool. Now, keep in mind you could do a third level.
You could do a third tiebreaker or a second tiebreaker, I suppose, where you could say, "Okay, well after the publication year, then focus on the ratings count in descending order." So here's how you need to think about this. These are tie breakers because by default we're going to focus on what best match the query, right? But then assuming that multiple documents have the same score based on your query, like multiple books that start with the word Harry, then we say, what is the tiebreaker? And we say, all right, the tiebreaker is going to be the publication year.
But now imagine there's still a tie there. there are multiple books that start with the word Harry that were released in the year 1998 or whatever. So now well what's the tiebreaker for that? If we have multiple Harry books released in hairy books released in 1998 what is the tiebreaker at that point? Well at that point let's say the one that has more ratings is popular and yeah these are these are the sorts of things you're going to be doing in your own applications. Okay, but for now let's keep it very simple and just say match on the search query and then we will uh favor the ratings count.
And this is fine. All right, so let's reformat this. And then the last thing I'll show you is of course you can declare how many results you want per page. So we could say just as an example, give me only three results. And now if I reload, of course we're going to have an array here of only three. But yeah, you can override this or stick with the defaults. Whatever you want to do here, we have a big list of results. Cool. All right, let's move on to filtering now. And yeah, this is really easy.
When you think of filtering, just think of wear conditions. So imagine you wanted to say, "Give me all books that were released in the year 2000 or higher." All right. Well, there you go. That's a filter. Set the release year to greater than or equal to 2000. All right. Let me show you how. So, let's open up our routes file. And you'll remember that we've set up a handful of playground routes just to see the full workflow. But notice it's getting a little bulky because we have to instantiate this client from scratch where we pass through the nodes and the API key and the connection timeout settings.
So, why don't we just take 2 minutes and extract this into a Laravel service provider. So, here's what I'm going to do. I'm going to copy this instantiation here and I'm going to throw it into the service container. And here's how. I'm going to open up my applications service provider and I'm going to register a new binding. So I can say bind. And when you think of bind, just think of put this into the container. Bind it into the container. So I'm going to give it the name of the type sense class. That will be our key.
and when we resolve it or basically when we pull it out of the container and sometimes I think of toy chests, you put things into the toy chest and then you pull it out of the toy chest. So when I pull client out of the toy chest, what I actually want to receive is this fully instantiated client object that already contains the configuration. And yeah, that's it is in the container. So now check this out. If I return to my routes file, I can remove this entirely and instead pull it out of the container like this.
Done. I'll do the exact same thing here. Done. And then finally, yeah, we'll do one more right here. And now, yeah, I don't have to manually instantiate and configure it every single time. I've already done that. and it's inside the container ready to go. Cool. All right. So now let's copy this and we're going to create another playground route. This one will be for filter search. Okay. So here's what we want to do. I'm going to reproduce what we talked about at the beginning of the video. I want to grab um all books that were released in the year 2000 or higher.
Actually before we do that, let's make it simple. give me all books that were released in the year 2000, specifically 2000, that match a particular search query. All right, let's see how we can do that. First, I'm going to remove the titles and return the full result set so that we can see it in the browser. Let's go to filter search. And of course, we need to provide a query. All right, query equals dark. All right, so let's have a look. Let's pretty print. And yeah, here's all of our hits. So it looks like the top one is called Dead Until Dark by Charlene Harris and the release year or the publication year is the actual term here is 2001.
Okay. So now I want to say no give me only books that match the dark query um but were released in the year 2000 if any. Let's see how it turns out. I'm going to add a new filter by key. Okay. And what are we filtering by? Well the publication year, right? All right. But next, let's complete this wear condition. The publication year, should it be equal to, not equal to, greater than, less than, one of many? Well, in this case, we said it must be equal to 2000. All right, so here's the syntax I want you to memorize.
You have field name followed by a colon, then an operator, then a value. All right, so let's do it together. Publication year is the field name followed by a colon and then the operator would be greater than equal to not equal to in our case it's very simple equal to right and now finally we need the value so the value in this case would be 2000 all right so when you read this I want you to say all right I'm going to filter this by the publication year where the year is equal to 2000 all right let's see what we get if anything.
Come back and give this a reload. And now the top matching result is His Dark Materials. And let's check the release year. Bam, 2000. Let's just take another look. All right, here's another one. Batman Dark Victory. And once again, publication year is 2000. Very cool. All right. Now, though, what if we want to say, well, you know what? It could be the year 2000 or 2001. I can't quite remember. All right. So, now we need to tweak this. We're kind of saying, well, the publication year can be 2000 or 2001. And we could write it like that.
But I could also say, well, it's kind of in a range, right? It could be within 2000 up to 2001. So, to declare a range, we can do this field name colon wrap it sort of like in an array syntax. It's equal to one of the items within this array. At least that's sort of how I parse it in my head. So, if I do something like this, this should do the trick. All right, let's give it a shot. Refresh. And here's what we're going to do. We're going to search for publication year and then 2000.
And yeah, we got lots of results there. Many. If I check 2001, we got lots of results as well. But of course, 2002, nothing. 2003 because of course those are not included in our array here. Okay. So yeah, you can imagine you want things like this. Well, it could be 2002, 2003, but you don't want to manually write this out in most cases. So it would be cool if we could have a range where we say, yeah, just give me old books released in the first decade of the 2000s. All right, we'll use a range.
And yeah, again, it's going to use syntax that you're probably familiar with already. We're going to use periods. 2000 to 2010. Sort of like how you might manually write it uh in real life. 2000 dot dot dot to 2010. Okay. So now one more time we give it a refresh. I think you're getting the basic idea. Let's look for 2003. We found it. 2005. Yep. We got one called Dark Lover. No judgment. 2006. 2009. It works. But once again, 2011, it's not going to pull up anything. All right. So what kind of filters are available to us?
Well, take a look at this. So, here are the operators that are available to you. And again, I want you to notice they follow the exact same structure. It's always going to be field name colon operator value. Right? So, let's pick a random one. How about uh greater than? All right. So, if we wanted to say the price is greater than 100, the field name is price colon. The operator is greater than the value is 100. Let's pick another one. Um, how about uh is not equal to. All right, here we go. This is the one we want.
So the operator is again what you would normally expect in a programming language. All right, so the field name is status colon. The operator is not equal to and then the value is inactive. So we can use a string in this particular case. Give me all results where the status is not equal to inactive. And yeah, I think you get the basic idea. You can memorize this very very quickly and if not uh this is available of course in the typesense documentation. All right, so let's finish up by doing just a couple more. How about I want to say hm give me all books where the author is equal to Blake Crouch.
How would we do that? Well, it looks like we want this one at the top. All right, let's give it a shot. So filter my results by those where the authors and remember it's plural in this case is equal to Blake Crouch. All right. So I happen to know Blake Crouch wrote a book called Dark Matter and our query is for dark. So maybe we'll get his book. All right. So we have our query set to dark. We've applied our filter and sure enough there we go. The top result is by Blake Crouch and the title is Dark Matter.
Okay, so notice now let's keep going. The publication year for that book is 2016. But imagine we had an additional filter where maybe the publication year was set to 2000, right? Well, if we did that, of course, this would not be included in the result set. So, let me show you how to do that. If we want to append filters, we can once again just use a common syntax you're already familiar with. Uh, amperand ampersand or um two pipes for or. So in our case, if I said, "Give me books that match this query that were authored by Blake Crouch and the publication year colon equals value." Exact same syntax is 2000.
Well, now of course if I come back and give it a refresh. Yeah, in this case we don't get any results. Okay, but if I were to tweak this to 2016, give it a refresh. Now we get exactly one hit. Notice we found one matching result. And again, Blake Crouch wrote Dark Matter in 2016. It fits. Okay. But now you could also do or. So you could say, "All right, give me books that match our search query that were authored by Blake Crouch or the publication year was 2000." So any books by Blake or any books um that match the query dark that were released in 2000.
I'm not sure if we have any results, but let's see. And sure enough, we have nine results. So, once again, let's search for year 2000. And yeah, we get a bunch of books that were not by Blake Crouch, but they still are returned because they match the second part of that filter by clause. Very cool. Okay, so now let's keep going. Let's say let's go back to publication here. Sometimes you want to say like okay give me the books that were released either um 1990 to 2000 or 2010 to 20120. Right? So you have multiple ranges.
And can we do that? Absolutely. Let me show you how. So we've already learned that we can use this syntax to set a range. Right? So now at this point we're saying give me books that were released in the year 1990 all the way up to 2000. All right. So that takes care of the first part. But then I think after that we said but also maybe 2010 to 2020 or something like that. So here's what we can do. We could of course just copy this and say well or publication year is 2010 to 2020.
Yeah I think that will work. So come back and give it a refresh and let's look for 2010 2015 but also 1994. Yeah, that works. But there's actually a cleaner way we could do this. So let's get rid of this and instead use a commaepparated list. I could say 2010 to 2020. All right, so let's give it a shot. I need to show you some proof. So let's search for publication year set to 1994. Yeah, we that fits 91 1990. Yep. But maybe nothing in ' 89 because that's not part of the results. And then 2000 will have something but 2001 is not in the range but 2010 is in the range and 15 but not 2022 or 21.
So yeah, this is uh what you can see here is whatever it is you need to filter by, there will be a way to do it and just refer to the documentation to learn more. So, let's wrap up and say maybe you want to read um an old book. You want to read a classic and it needs to be released before the year, I don't know, 1950 or something. So, you'd want to say, "All right, give me all books that match my query where the publication year uh is less than 1950." All right. Well, let's do it one more time.
The field name colon the operator, which would be less than, right? And then the value, which is 1950. All right, let's see what we got. Come back. Give it a refresh. And it looks like we have only two results. We have Heart of Darkness, released in 1899. And then finally, we have Where are you? Um, oh, same book, but it shows up two different times. I don't know, maybe it's a um a translation. Either way, it works. You get the idea. Okay, so now here's something to think about. Of course, you can provide these filters to your users.
And of course, I'll show you how to do that when we focus on the front end a bit more. But you can also use it for special cases where you want to filter based upon uh maybe some status that your user has in your system, right? Things that cannot be indexed but you still want factored into the query, right? So imagine like for something like Larcast if you wanted to say all right give me all courses that the user has completed but also match this particular uh search query right well we know how to search right we know how to filter based on a query and a category but then what about things like whether or not this user has completed the course versus that user well for those situations you can still use filter by clauses right you could just fetch all of the ID ids that the user has completed.
Give me all course ids that are completed by the user and then we are going to include those within um a a filter by clause. So maybe something like this. So our field is id right then a colon the operator is equal to and then we provide the ID of the course the user completed right. So in this case I'm saying all right search the courses in our example here but filter them down. I only care about the courses that match the query and also have an ID of five. And yeah, maybe they completed two courses.
We could do something like this. But now what if they completed like 50 courses? Well, you could dynamically construct this if you want. And that's what you would do. But uh don't forget we could also use one of many, right? So that would be the array syntax. I could say ID colon operator is this array syntax. And then once again, we would dynamically fetch all of the ids the user completed. So maybe something like this. Search courses for the query, but return only the ones that match the query and also have an ID of 5 or 12 or 20.
And that's how we can do it. All right. So yeah, as you can see, it's incredibly flexible and very likely whatever it is you need to accomplish, you can accomplish. All right, let's move on to something new in the next episode. All right, next up, I think we should move on to faceting. So, yeah, when you hear that word facet, think of group by or categorized by. And you know what? You've seen this a million times. If you've ever gone to Amazon and you perform a search, you see your results off to the right and then to the left, you're going to see a bunch of checkboxes, right?
So maybe if we're looking for books, we'll see all authors who are represented uh within that result set. And if I click on one of their names, well, now the results update, right? Now the results show my search query matches, but also only the books that match the particular author name that I selected. That is a facet. We could also maybe have a facet for the release decade. So I want to see only the books released in the 2000s decade or the 2010s or the 2020s, right? So yeah, it's it's a way to further categorize the results so that we can provide a better UI to our users.
All right, so let me show you how. Let's open up our routes file. And once again, we're sticking with our playground approach here. So I will add another route and this one will be for faceting. All right, so I'm going to illustrate this in two steps. In this video, I'll show you programmatically how to facet our result set. And then in the next episode, we're actually going to write some front-end code and some view code to display these results. Okay, so let's have a look here. We are now searching our books database for this query.
I'm only looking in the title field. That's the only thing that qualifies for a match. We're going to sort by the closest match. And then if we have a tie, the tiebreaker will be the ratings count. Uh so the higher rating comes first. Next we get 50 results per page. And then in the last episode we added a filter. I will remove that now. All right. So if we leave it like this and we view this in the browser here you can see our query faceting and then the query set to hairy. If we view the results.
Yeah, we've seen this a bunch of times right? We see all the results but no categorizing or no grouping or no facets. So let's return to our code. And now I'm going to say facet by authors. But h let's see if this works. Let's come back and give it a refresh. And huh, we get an error. Object not found. We could not find a facet field named authors in the schema. Okay, so here's the issue. When we defined our schema, and if I switch back, we did this in the very first or second episode. Here's where we defined the schema.
As part of this, we need to be explicit about which fields should be facetable or should be qualified as facets. So in our case, notice we have authors here, but I didn't specify that it is a facet. Okay, let's update this now and then reimpport and rebuild our database, so to speak. Let's set facet for this field to true. So yeah, just remember this. If you ever want to categorize results based on a particular field, you need to be explicit here and just think of it as our way of saying, all right, I want to cluster all of the results into categories specifically for authors.
Okay, so now we have updated our schema, but it's not magic. It doesn't magically take effect. We would need to rebuild this index. All right, so here's what we're going to do. I'm going to hit this endpoint again. And you'll remember that well we build up the schema and yeah I'm just going to delete the existing books database or index so that we can populate a brand new one from scratch. So at that point we will have a new index but it won't have any rows uh or documents within it. So if I scroll down we have another endpoint here uh where we once again read that books database and we bulk import it.
All right, so let's hit these two routes one after another. Or of course we could move these to artisan commands so that we can run this directly from the CLI. But for now in the browser is nice and friendly. So let's do that. Create collection. There we go. Next. Import collection. Books imported. I think we're ready to go. So now let's go back to that faceting endpoint. And once again I will set the query to Harry. And now because authors is listed as a facet, notice we have this new key here, facet counts. All right.
So let's have a look here. Let's just go through it together. Sure enough, like before, we still have our matching results. Nothing has changed there. The only change is right up here. We now have additional categorization. It's hard to say categorization data. So for facet counts we have an array of objects where each object represents one author who was included as part of the result set. So here I can see yep JK Rowling has 10 books or 10 items or documents that were included as part of the results. Right? So remember we perform the search we find the matching results and then we cluster those results according to in this case the author's facet.
That's sort of what's going on here. So yeah, I can see David Colbert has one book in here. Uh Sean Klene has one book in here as well. And as you can imagine, we can represent these within a sidebar. Now I can show all books that match your query. But then off to the side, we can have a little section and that section will say authors. And within that box, I'm going to have a bunch of checkboxes. And each checkbox will be one author name who is included within the results. And what we can do, and we're gonna have to get a little fancy here, a little a little dynamic.
We're gonna have to write some JavaScript. But once we check one of these check boxes, I can repopulate the result set. I can say, okay, you checked on JK Rowling here. So, here's what you want. You want to perform a new search query that matches uh whatever the query was, in this case, Harry, but also it needs to be filtered by this particular author. So again, faceting allows us to do that and it takes two steps, right? The first step is to perform the query and compile all of the facets. We then pass these to our front end.
We render them and then we listen for when the user uh selects one or more of them. Okay, so in this video, here's what you need to know. When you define your schema, you need to think ahead and you need to ask yourself, is this field a particular kind of field that we might want to categorize upon? I might want to group results based upon this value and the answer is yes. I'll give you another example once again for Lariccast courses. What would make sense in terms of grouping or facets for a Lariccast course? And if you want, hit pause and and uh think about it.
Course has a title. Course has a description. Course has comments. But also course has like a taxonomy, a category. Is this a frameworks course? Is this a testing course? Uh and then also our courses can be assigned multiple topics, right? So you have a top level category, but then there can be many topics within it. So maybe a course that deals with AlpineJS could have the topic Alpine.js, but maybe that course also uses uh Typesense. Well, we might have a a topic called Typesense. Maybe the course also uses layer of a livewire. We might have a topic called Livewire.
So, as you can imagine, these are perfect examples for facets. If I'm searching for some kind of course in the sidebar, it would be useful if I could see all topic names that are represented as part of that search result. That way, I can say, you know what, I'm actually only interested in the courses that match my query, but are also under the topic alpine.js. JS and this once again is what faceting allows for. So in the next episode, let's build a simple UI. All right, welcome to a new chapter. We're now going to switch over to the front end and build a search UI.
But you know what? I'd like to take this super incrementally, one step at a time. So in this video, we'll have a simple blade file. You submit a form, you see the results, right? very basic, but then we'll expand. We will highlight the results. We will set it up so that as the user is typing their search query, we instantly display results. We will add facets to the sidebar. So, I really do think you're going to get a lot out of this. Let's get going. So, as you can see here, our routes file is empty.
We're going to start from scratch. All right, let's get going. The very first thing I'd like to do is return a view. And let's just call it search. All right, let's go ahead and create that file. And yeah, in this case, we don't really have a full site here. We don't have the layout file. So, I'm going to put it all in line as you see here. Search for something. All right, let's have a look in the browser. All right, we got something on the page. It's not nothing. Okay, let's switch back and get to work.
So, the first thing I'm going to do is once again pull in Tailwind, but I don't want to deal with a build step or anything like that. So let's just reference it from a CDN like this. We can use cdn.tailwindcss.com. So now instantly we have access to all of the default uh Tailwind utility classes. So now if I come back and give it a refresh, we can see the update there. Great. All right. So now, well, before we even add a form, before we add an input, let's just imagine we're reading from the query string.
We're going to perform the search and return the results. So with that in mind, let's return to our routes file. All right, so let's create our query variable. And I'm going to read the query string for Q. That's pretty common, but this time I'm going to add a default of star. And this is a special symbol with Typesense that just means everything, right? All right. So now let's perform our search. So we will have our Typesense client. And remember in our service provider we um bound client into the service container so that we instantiated and configured it as we would need for our application.
So now when I ask for client it's ready to go and I don't have to set the API key in the nodes all over again. All right cool. So let's switch back and we'll say client collections books documents search. All right, let's provide our configuration. And yeah, this is a a a good refresher if you're working along. So let's see what AI has for us here. It's saying, yeah, the Q is query. That's correct. We're going to query by the title only in this particular case. We will not yet faceted. We'll add that later. And yeah, let's just keep it super simple like this.
All right. So if we fetch the results and I return them as JSON, if I come back and I give this a refresh, yeah, this is this is a review at this point. You already know how to do this. So in our case, it's just returning um everything basically. Maybe it's it's doing a default sorting. Uh but there is no actual query taking place here. But if I were to override this and say query equals uh project, I'm sorry, let's fix that project. Even though it should still work. If we take a look, it looks like the top result is called the Rosie project.
Never heard of it, but lots of ratings. Okay, very cool. All right, so now, yeah, if you want, you could just take this huge object and pass it to your Blade View. And yeah, for a serverside rendered app, that's that's fine. But if instead you're going to pass this to your front end, and it's going to have to be sent to your JavaScript, uh, there's a lot here. You may not want to do that. So, with that in mind, why don't we just pluck the information we need for now? And in this video only, I want to go into hits and I just want to grab the title for each document.
So let's do that now. Collect results hits pluck document. All right. So now, yeah, if we were to take this incrementally to show you what we get here, we now have an array of only the documents. But like I said, we want to further pluck the title. So using Laravel collections, I can use the dot syntax like this. give me the document and within each document give me the title. So now if we give it a refresh I have an array of only the titles and yeah for this initial example is all I care about later we'll grab some more.
We'll call this titles or why don't we just say results and I'm going to pass that to my view. All right let's switch over to our search place and render the results. I'll do it within a simple uh unordered list. So I'm going to use a for else here and we'll say for results as result. And what's nice about this is I could then say well if there are no results what do we do on that condition? And in this case I can just say no matching results. Okay. So yeah for each one why don't we display the result which would be the title itself.
So let's echo the result. All right. So if we have a look there we go. It's not very pretty but it works. If I now swap this out with dark, there's the matching results. Hairy. You get the idea. Okay. Now, let's style it just a little bit. So, very quickly, why don't we put padding 8 all around? And we'll say for the unordered list, let's do a border. Maybe border gray of 300. And within it, we'll have just a little padding all around. How about padding Y2 instead? And padding X4. Yeah, we're not trying to win any awards, of course.
Uh, just something reasonably good-looking. Okay. Next, I want an input above it. So, let's do that now. We'll add our form here. So, within it, we're going to have an input one more time with a type of search. We'll give it a name of Q. And when the form submits, of course, just basic form submission here. It is going to go to search and the method is going to be get. So when I submit this form, it's going to make a get request to the current page and send through a Q uh value in the query string.
So let's give it a shot. Oh, we need to see it a little better. Let's give it a class of border. We'll do the exact same border gray 300. Uh but let's set border bottom to zero so that we don't have doubled up borders. All right, there we go. Let's increase the width. And yeah, we just want to kind of connect it there like it's all one uh little component. Okay, so what should we do here? Uh well, first by default because we already have a query um we'll say dark once again. Then the input should be populated with the current query, right?
So let's do this. Let's put these on their own line since we don't have a lot of real estate. And then uh I will set the value equal to request Q. All right, come back, give it a refresh, and there we go. Uh, next, let's give it the exact same padding on the left and right. There you go. And maybe, yeah, we're just going to duplicate the padding to make it consistent. Very cool. So, now for dark, here are the matching search results. And of course, there's lots of accessibility to be uh considered here. Um, we should ideally be able to press the down arrow key to select the results, but that is more advanced, more intricate stuff, far beyond what we're focused on right now.
So, just give me a little a little leg room um to ignore some of that stuff for now. Okay, great. So, if I were to change this, well, even though we don't have a submit form by default, if I press enter, it will do the default action, right? Which is submit the form, which means I instantly uh see the results here. But of course, we could add a little submit button as well if we want further because we have an input type of search, we get an X button for free that will clear that out.
All right. All right. So, once again, let's say project hail. Run it. And uh actually, it didn't bring up what I thought it would, but um maybe maybe this book list is a little bit older. Uh I was expecting project hail Mary. Anyways, we do see the Rosie project and the happiness project. This is all looking pretty good. Okay, so think about what we've accomplished here. If I go back to our routes file, all we really did is when you load the page, we read the query string. And if you didn't pass anything, we'll just search everything.
we will always perform a search and that way no matter what um we we we have something to look at uh we have some recommendations to look at and of course in real life these would be clickable and maybe you click on it and it takes you to the dedicated book page so yeah I would imagine based on these results the default is going to be popularity it's going to show me the most popular books in that entire database and this would this would probably match that. Okay, next we perform our search and we've learned how to do that.
Grab your collection and then search through the documents for the query and then specify what you are querying by. So yeah, once again remember if you're going to look for um Blake Crouch who wrote Dark Matter, well it's not going to look through the authors. It's only going to look through the titles. You need to be explicit about what you are searching. So if you wanted to do authors as well, include that. And now if I run it again, we get the top matching result. It's going to work. No problem at all. Okay. So now if we come back, we have our results.
In this little example, all we cared about was the title. In real life, you're going to want a little bit more. But we fetch those titles and we pass them to our view. And then we render them in as simple a way as we can possibly manage. We loop over them and we show the results. And then we have a form above it where you can submit uh for new results. So, so far incredibly easy. In the next episode, let's take it one step further. I'll see you then. All right, welcome back. So, now let's take it one step further and I would like to add highlighting to my results.
All right, so let's do this once again. Let's return the results. And yeah, for now we're going to keep it again very simple. We're only going to query by the title. All right. So now if I give it a refresh, you'll notice for the hits in addition to document, we also have these two sections for highlighting. So here's one and here's another. And these are just different ways to access the same highlighted um or marked search result. The only difference is with highlight you have an object uh that is keyed by the field that received the highlight whereas in the next one you just have an array of objects.
So yeah, if you think about it, what we could do is say, "All right, I want to pluck the highlights right here, and that's going to give us all of these for each matching result." So if we were to move return results down here, again, we're just going to review this incrementally. Now, this is what we get. Okay, but once again, we can take this further. All I really care about in this example is the matching snippet. So, let's do this. Because highlights is an array of arrays, why don't we flatten it? But not all the way.
Let's just do one level of flattening. So, now if I give it a refresh, yeah, this is what we want. So, notice if I didn't pass one here, it's going to flatten everything. Uh, and that's not what I want. Just a single level of flattening. All right. So, then finally, we have these objects where I can um finish up by plucking the snippet itself. Pluck snippet. And that's going to give us everything we need here. All right. So now once again I have an array of the matching titles but this time we have proper highlighting that is based upon uh your search query.
So if I change this once again to uh matter you'll see any reference to matter is going to be wrapped within mark tags that I can then style however I want. All right let's send it to the view. Come back. Give it a refresh. And now we're seeing the full HTML because we're escaping it. But yeah, it's perfect. It's exactly what we want. So let's switch to the view and update this. Right down here, we'll say um without escaping, I want the full HTML here. Display the result. Come back, give it a refresh, and again we have some default highlighting here that will take any mark tag and add a yellow background, which is often what you want.
Let's try it out. Harry, very good. project wizard. Uh, and yeah, notice in this case, even though I spelled it wrong, we have type sensitivity with type sense, and it knows, ah, this is probably what you were looking for. So, it can account for that, which is cool. All right. Yeah, we're working incrementally. So, we've made one more improvement. In the next episode, let's take it even further. I'll see you then. All right, welcome back. Let's take it another step further. So now we have our search working. Next in the sidebar, I'd like to have a list of filters.
So in this case, I'd like to see all matching authors who are represented as part of these search results. So we've learned a little bit about facets. Now let's put it into practice. So let's start once again in our routes file. And like we did before, we're going to return the results object from typesense so we can see what we're working with. All right. So once again, well, how do we how do we grab the facets? How do we find the matching authors? Well, by default, it's not included. We have to explicitly request faceting. So if I come back, I can say facet by and now we provide any fields that we have interest in, but also any fields that are designated as facets.
So remember, you designate that when you create the schema. And if you forgot how to do that, go back to the episode where we create our first schema. So yeah, in this case, we're going to facet by authors. Now, if I come back and give it a refresh, we're going to get something a little different. Now, we're going to have this facet counts key. So you'll see it's an array, and we have an object here that has counts. And yeah, just take a look at all of this. So here is the facet field name. So you can imagine looping over all of your facet counts and saying for each one we have because we might facet by authors, we might facet by the decade that the book was released.
Uh it could be a number of things. The length maybe if you want to narrow it down to short, medium, long reads. Uh you can really filter however you want. Uh however would make sense. And then we have um the total values for this particular facet. Okay. So then finally within the accounts object we have all of the items in this case all of the authors who are represented or included as part of the search results. All right. So um yeah why don't we why don't we just pluck what we need and then send it to the viewer.
All right. So let's do this. We'll say results facet counts. And once again I'm going to collect these so that I can easily map over them in an object-oriented way. Of course you can use array map if you prefer. So, let's map over them. And for each facet, you know, once again, if I just die and dump immediately so that we're on the same page. Now, we're going to have an array. We have the field name, and then we have all of the matching um authors in this case. So, I care about the field name.
So, let's return an array. And we'll have the name will be facet field name. All right. What else do we want? I would like all of the results here. So each one contains the count, the highlight, the value. We can keep that. We'll say uh filters. Yeah, because if you think about it, each of those authors is kind of a filter. You select the author and then it should further filter the search results down to those that match the query, but also were written by uh the selected author. So we'll say filters equals and yeah, if we wanted to grab that, but you know what?
I think it's fine. We're just going to do something like this. Okay, so this is our facets and once again if we do a sanity check to see what we got. Now we have an array uh of objects and each object contains the facet name as well as the list of filters. And I think this looks pretty good. So now I'm going to pass this to our view like this. Okay, now let's display it in our view. Go into search and let's see here this. So, let's wrap all of this here. This will be uh excuse me, this will be sort of our main section.
And then we're going to have an aside like so. And then why don't we wrap this whole thing in a div so that it can be our grid. All right. So, we'll say facets go here. And if we come back, give it a refresh. Yep, it's top to bottom. So, I'm going to say no, I want a grid. And I want it to be like a 12 column grid. So, grid calls 12. The aside will be three. It'll span three uh grid rows or columns. And then the last one will be 3 + 9 equals 12.
Right? So call span 9. Come back. Give it a refresh. Now they're side to side. Uh next. What should we do here? Actually, why don't we change this? Maybe maybe a little more space. And that'll narrow it a little bit. I'll add a gap. Yeah, is good enough. Okay. So now let's loop over our facets. And remember there there could be one as there is in this case but in real life there could be there could be many that you are faceting by. All right let's get started. So right here we'll say for each facets as facet and yeah remember if you're working along you're just going to forget what's inside these objects and that's totally fine.
So remember to use dd. DD is your friend. If you're in a blade file dump is your friend and you just quickly got to check all right what's in here. Yep. Okay. So we have name and then I'll say right here this will be the facet name. What else do I need? Oh, we have filters. So I should loop over the filters and for each one I display the value. Okay, so let's do that. For each facet filters as filter then once again um maybe this is also an unordered list. Let's reformat. Um, I think later, uh, this will probably be a form, but for now, we're just we're just getting it on the page.
And then we'll say filter value. Give it a refresh. And there you go. So, let's style it a little bit. We'll say you are font bold. This will be UC words. Uh, it'll capitalize the first letter. All right. Uh, next, why don't we wrap this whole thing? This will be its own little component once again. And that way I can say border gray 300 um padding x4 and I'm sorry border. There you go. Add some padding y. Of course we could extract these into reusable styles or components if we want. But yeah, you get the general idea here.
So now if I look for matter uh the author's facet sidebar section uh will update as well. If I look for Harry then of course we're going to see JK Rowling at the top. It's working. Cool. So, why don't we do this? Um, why don't we say border B, border gray 300. Like I said, we're not trying to win any awards. And even if we were trying, I'm not going to win cuz I'd barely know what I'm doing here. Uh, but we can do a couple things here. Let's remove the padding. Yeah. And then here, I can manually add it back in and do it here as well.
Yep. And then py2. So, we're just adjusting it. So we have um this this solid line that extends to the very bottom. Maybe I should do this as well. No, maybe just PBT or PB2. Good enough uh for our needs. So finally, should there be no padding on the bottom? I think that looks good. Okay, it doesn't matter. Uh so now we have one section. And of course, if we were faceting by more than one section, uh we could display those results as well. That's the entire point. Okay, so now that we can see which authors are represented as part of these search results, well, that's only partially helpful.
In the next episode, I want to take it a step further. I'd like to see a little checkbox next to each name. And when I select it, it will submit the form and refresh the results to include only the results that have in this case matter uh in the name, but also were written by whoever I happen to select here. So, I hope you're excited. I'll see you in the next episode. All right. So, here you can see we have highlighted search results, which is pretty cool and really easy to implement. And then off to the side we have our facets.
So, we have all of the top authors who are included as part of these results. But yeah, right now in our implementation, you can't click on these. They don't do anything. So yeah, let's discuss two different ways that we might go about implementing this. All right, so let's start within our search view. And yeah, you should be focused on the aside here. So notice we loop over each facet and for each one we display a div that contains the heading as well as a list of each filter for that facet. And yeah, once again, if I switch back, we have our heading and a list of each filter.
Cool. So yeah, one thing we could do, it's maybe not the most flexible option, but maybe it would work for you, is just make each of these filters uh an anchor tag. So yeah, you don't even need a form in this case. You can dynamically figure out what the URL should be and what the query string should be and then the user clicks on it and it just reloads the page. That would be an option. So here's what you might do in that case. This would now be an anchor tag. So let's wrap it. And yeah, once again, it's still going to visit the search page.
So now if I come back and refresh, yes, I can click on any of these, but we're not doing anything. We're not taking that value and sending it along with the request. So let's make sure we do that as part of the query string. Maybe something like this. In the query string, we'll set the author name equal to the filter value. All right. So now, once again, if I click on one of these and I open up the URL so you can see better. Sure enough, it passed through author and then Stephen King in this case.
But here's one issue. If I look for what's a Stephen King book? How about uh it's not going to work. That's too generic. Um The Shining. Okay. So now if I click on Stephen King though, watch what happens. Currently the URL includes the query, but when I click on Stephen King, we lose the query because it wasn't included uh when the user clicked that link. So, let's make sure that we merge in any of the existing um query string values uh in the URI along with author. And here's how we could do that. So, hm, let's do this.
Let's swap it out entirely with a call to Laravel's uh URL query method. This allows us to generate an absolute URL, but also send through query string values. So, for example, if I just did something like this, we're going to get a URL to search. Give it a refresh, click on it, and that's the URL, right? But if I commandclick, we can also send through query string values. So if I were to say, excuse me, excuse me, two times, fu is bar and come back, refresh. If I click on this now, you'll see we went to search and then we appended fu equals bar.
It takes care of that for us. So yeah, what you could do is maybe start with request um all or maybe just grab all of the things within the query string. So now, yeah, if I switch back, let's look for once again shining. All right, notice the query string. Next, if I click on one of these, notice it retains the query string. So now all I have to do is keep the existing query string, but then merge in uh the current author. So if we want to do that, maybe, and I apologize, I don't have too much real estate here.
Why don't we switch? Yeah, that's better.…
Transcript truncated. Watch the full video for the complete content.
More from Laracasts
Get daily recaps from
Laracasts
AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.









