- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Seven Mistakes When Building Search Features (and What to Do Instead)
About Post
Search looks like the easiest feature on the backlog. One input box, one query, done by lunch:
Product::where('name', 'like', "%{$term}%")->get();
And it works. With 200 rows in your local database, it's instant and finds everything. Then real data arrives, real users type real things, and search quietly becomes the feature people complain about most. It's slow, it finds the wrong things first, and it can't find "colour" when the product says "color".
Here are the mistakes I see most often when building search, why each one hurts, and what to do instead.
Mistake 1: LIKE '%term%' on a big table
Why it hurts: a normal B-tree index is sorted from the first character, like a phone book sorted by surname. You can quickly find everyone whose name starts with "Ali". But "contains ali" means reading every entry. A leading wildcard forces MySQL to scan the whole table, every search, for every user.
The fix: it depends on what users search for.
- Codes, references and emails usually make sense as a prefix search:
LIKE 'INV-2026%'can use an index. - Words in names, titles and descriptions need a full-text index (mistake 3).
- On a genuinely small table,
%term%is fine. Don't over-engineer a search across 300 rows.
Mistake 2: forgetting that % and _ are wildcards
Why it hurts: in a LIKE pattern, % means "anything" and _ means "any single character". A user searching for "50%" or "first_name" gets surprising results, and someone typing a lone % matches every row. Query bindings protect you from SQL injection, but they don't escape LIKE wildcards.
The fix: escape them before building the pattern.
$escaped = str_replace(['\\', '%', '_'], ['\\\\', '\%', '\_'], $term);
Product::where('sku', 'like', $escaped . '%')->get();
MySQL uses backslash as the default LIKE escape character, so this works there; other databases may need an explicit ESCAPE clause.
Mistake 3: never trying FULLTEXT
Why it hurts: people jump from LIKE straight to "we need Elasticsearch", skipping the tool already built into MySQL and PostgreSQL.
The fix: a full-text index splits text into words and indexes them, so word searches stay fast on large tables. Laravel supports it directly:
// Migration
$table->fullText(['name', 'description']);
// Query
Product::whereFullText(['name', 'description'], $term)->paginate(20);
A few MySQL gotchas worth knowing: very short words are ignored by default (InnoDB's minimum token size is 3 characters), common "stopwords" are skipped, and the columns in your query must match the columns of a full-text index. If a search for "IT" returns nothing, now you know why.
Mistake 4: ignoring relevance
Why it hurts: a search for "laptop bag" that shows twenty laptops before the one bag isn't wrong, it's useless. Sorting results by newest or by ID treats a product with the term in its title the same as one that mentions it once in paragraph six.
The fix: order by how well each result matches. In MySQL's natural language mode, MATCH ... AGAINST returns a relevance score you can sort by:
Product::query()
->select('*')
->selectRaw('MATCH(name, description) AGAINST (?) AS score', [$term])
->whereFullText(['name', 'description'], $term)
->orderByDesc('score')
->paginate(20);
Even without full-text, simple rules help a lot: exact matches first, then title matches, then everything else.
Mistake 5: searching on every keystroke, and showing stale results
Why it hurts: typing "invoice" fires seven requests. That's load on your server for nothing, and it causes a sneaky bug: responses can arrive out of order. If the request for "inv" is slow and finishes after the one for "invoice", the user sees results for the wrong word.
The fix: debounce the input and cancel requests that are no longer needed.
let timer, controller;
input.addEventListener('input', () => {
clearTimeout(timer);
timer = setTimeout(async () => {
const q = input.value.trim();
if (q.length < 2) return;
controller?.abort(); // cancel the previous search
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, { signal: controller.signal });
renderResults(await res.json());
} catch (e) {
if (e.name !== 'AbortError') throw e;
}
}, 300);
});
Now the request only fires once the user pauses, only for useful queries, and an old response can never overwrite a newer one.
Mistake 6: expecting users to type perfectly
Why it hurts: people make typos, use plurals, mix spellings, and search in their own words. Database search mostly does exact word matching, so "recieve" finds nothing.
The fix: when search is central to your product, use a dedicated search engine. Laravel Scout gives you one API over several engines: Meilisearch and Typesense (both self-hostable) and Algolia (hosted) offer typo tolerance, relevance tuning and filters out of the box.
class Product extends Model
{
use Searchable;
public function toSearchableArray(): array
{
return ['id' => $this->id, 'name' => $this->name, 'description' => $this->description];
}
}
Product::search($term)->paginate(20);
Scout also has a database engine that uses your existing database, with attributes to choose full-text or prefix matching per column. It's a nice starting point: you get the Scout API now and can switch to a real engine later without rewriting your controllers. The trade-off with external engines is another service to run and keep in sync, which Scout handles through model events and queues.
A simple ladder: prefix LIKE for codes and IDs, full-text for words in a few columns, a search engine through Scout when typos, ranking and filters really matter. Climb only as high as your users need.
Mistake 7: not looking at what people search for
Why it hurts: you're tuning search based on your guesses, not on real behaviour.
The fix: log search terms and result counts (without personal data), and look at the searches that return zero results. They tell you exactly which synonyms, spellings and missing content to deal with. It's the cheapest search improvement there is.
The checklist
- No leading-wildcard
LIKEon big tables. - Escape
%and_in user input. - Try full-text indexes before reaching for new infrastructure.
- Sort by relevance, not by date.
- Debounce, set a minimum length, cancel stale requests.
- Use Scout with a real engine when typo tolerance matters.
- Read your zero-result searches.
What's the strangest thing you've seen users type into a search box? Every product has a few that make you rethink everything.

Be first to comment it...