DEV Community

Rahul Kapoor
Rahul Kapoor

Posted on

Real-time Search and Auto-Suggestions for Property Listings

Real-time Search and Auto-Suggestions for Property Listings

This comprehensive guide will walk you through building a real-time search system with auto-suggestions for property listings. I'll provide a complete solution with frontend and backend components.

System Architecture

  1. Frontend: React.js with TypeScript
  2. Backend: Node.js with Express
  3. Database: MongoDB (for property listings)
  4. Search Engine: Elasticsearch (for fast autocomplete)

Step 1: Set Up the Backend

Install Dependencies

npm init -y
npm install express mongoose body-parser cors elasticsearch
Enter fullscreen mode Exit fullscreen mode

Backend Server (server.js)

const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');
const elasticsearch = require('elasticsearch');

const app = express();
app.use(cors());
app.use(bodyParser.json());

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/propertyDB', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

// Elasticsearch client
const esClient = new elasticsearch.Client({
  host: 'localhost:9200',
  log: 'trace'
});

// Property Schema
const PropertySchema = new mongoose.Schema({
  title: String,
  location: String,
  price: Number,
  type: String,
  area: Number,
  bedrooms: Number,
  // Add other property fields
});

const Property = mongoose.model('Property', PropertySchema);

// API Endpoints
app.get('/api/properties/search', async (req, res) => {
  const { query } = req.query;

  try {
    const results = await esClient.search({
      index: 'properties',
      body: {
        query: {
          multi_match: {
            query: query,
            fields: ['title', 'location', 'type'],
            fuzziness: 'AUTO'
          }
        }
      }
    });

    res.json(results.hits.hits.map(hit => hit._source));
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.get('/api/properties/suggest', async (req, res) => {
  const { prefix } = req.query;

  try {
    const suggestions = await esClient.search({
      index: 'properties',
      body: {
        suggest: {
          property_suggest: {
            prefix: prefix,
            completion: {
              field: 'suggest',
              fuzzy: {
                fuzziness: 1
              }
            }
          }
        }
      }
    });

    res.json(suggestions.suggest.property_suggest[0].options);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Start server
app.listen(3001, () => {
  console.log('Server running on port 3001');
});
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up Elasticsearch

  1. Install Elasticsearch (https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html)
  2. Create an index with mapping:
curl -X PUT "localhost:9200/properties" -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "location": { "type": "text" },
      "type": { "type": "keyword" },
      "suggest": {
        "type": "completion",
        "analyzer": "simple",
        "search_analyzer": "simple"
      }
    }
  }
}
'
Enter fullscreen mode Exit fullscreen mode

Step 3: Frontend Implementation (React)

Install Dependencies

npx create-react-app property-search --template typescript
cd property-search
npm install axios @material-ui/core @material-ui/labs
Enter fullscreen mode Exit fullscreen mode

Search Component (Search.tsx)

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { TextField, List, ListItem, ListItemText, Paper } from '@material-ui/core';

interface Property {
  id: string;
  title: string;
  location: string;
  price: number;
  type: string;
}

const Search: React.FC = () => {
  const [query, setQuery] = useState('');
  const [suggestions, setSuggestions] = useState<Property[]>([]);
  const [results, setResults] = useState<Property[]>([]);
  const [isSearching, setIsSearching] = useState(false);

  useEffect(() => {
    if (query.length > 2) {
      const timer = setTimeout(() => {
        fetchSuggestions();
      }, 300);
      return () => clearTimeout(timer);
    } else {
      setSuggestions([]);
    }
  }, [query]);

  const fetchSuggestions = async () => {
    try {
      const response = await axios.get('http://localhost:3001/api/properties/suggest', {
        params: { prefix: query }
      });
      setSuggestions(response.data.map((item: any) => item._source));
    } catch (error) {
      console.error('Error fetching suggestions:', error);
    }
  };

  const handleSearch = async () => {
    if (query.trim() === '') return;

    setIsSearching(true);
    try {
      const response = await axios.get('http://localhost:3001/api/properties/search', {
        params: { query }
      });
      setResults(response.data);
    } catch (error) {
      console.error('Error searching properties:', error);
    } finally {
      setIsSearching(false);
    }
  };

  return (
    <div style={{ maxWidth: 800, margin: '0 auto' }}>
      <TextField
        fullWidth
        variant="outlined"
        label="Search properties..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
      />

      {suggestions.length > 0 && (
        <Paper style={{ position: 'absolute', zIndex: 1, width: '100%' }}>
          <List>
            {suggestions.map((property) => (
              <ListItem 
                key={property.id} 
                button
                onClick={() => {
                  setQuery(`${property.title}, ${property.location}`);
                  setSuggestions([]);
                }}
              >
                <ListItemText 
                  primary={property.title}
                  secondary={`${property.location} - ${property.type}`}
                />
              </ListItem>
            ))}
          </List>
        </Paper>
      )}

      <div style={{ marginTop: 20 }}>
        {isSearching ? (
          <p>Searching...</p>
        ) : (
          results.map((property) => (
            <div key={property.id} style={{ padding: 10, borderBottom: '1px solid #eee' }}>
              <h3>{property.title}</h3>
              <p>{property.location} - {property.type}</p>
              <p>Price: ${property.price}</p>
            </div>
          ))
        )}
      </div>
    </div>
  );
};

export default Search;
Enter fullscreen mode Exit fullscreen mode

Step 4: Indexing Properties

Create a script to index existing properties:

// indexProperties.js
const mongoose = require('mongoose');
const elasticsearch = require('elasticsearch');

mongoose.connect('mongodb://localhost:27017/propertyDB', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

const Property = require('./models/Property');
const esClient = new elasticsearch.Client({ host: 'localhost:9200' });

async function indexProperties() {
  const properties = await Property.find();

  for (const property of properties) {
    await esClient.index({
      index: 'properties',
      id: property._id.toString(),
      body: {
        title: property.title,
        location: property.location,
        type: property.type,
        price: property.price,
        suggest: {
          input: [
            property.title,
            property.location,
            `${property.title} ${property.location}`,
            `${property.type} in ${property.location}`
          ]
        }
      }
    });
  }

  console.log('Indexing complete');
  process.exit();
}

indexProperties();
Enter fullscreen mode Exit fullscreen mode

Step 5: Deployment Considerations

  1. Performance Optimization:

    • Implement debouncing for search input (already done in the React component)
    • Cache frequent search results
    • Use pagination for search results
  2. Scalability:

    • Consider using Redis for caching
    • Implement load balancing for the backend
    • Use a managed Elasticsearch service for production
  3. Security:

    • Add rate limiting to API endpoints
    • Implement JWT authentication
    • Sanitize all search inputs

Step 6: Testing the Implementation

  1. Start your backend server:
node server.js
Enter fullscreen mode Exit fullscreen mode
  1. Start your React app:
cd property-search
npm start
Enter fullscreen mode Exit fullscreen mode
  1. Test the search functionality:
    • Type at least 3 characters to see autocomplete suggestions
    • Press Enter or click a suggestion to see full search results

Conclusion

This implementation provides a robust real-time search system with auto-suggestions for property listings. The combination of Elasticsearch for fast autocomplete and fuzzy matching with MongoDB for primary data storage creates a powerful search experience.

Key features:

  • Real-time suggestions as users type
  • Fuzzy matching for misspellings
  • Fast response times
  • Scalable architecture

You can extend this system by adding filters (price range, property type), sorting options, or personalized recommendations based on user behavior.
Written by think4buysale.in Software and ompropertydealer.com

Top comments (0)