Skip to content

Commit eebbf73

Browse files
authored
Building elasticsearch apis with fastapi websockets - style changes (#477)
1 parent a7a71a2 commit eebbf73

File tree

3 files changed

+135
-67
lines changed

3 files changed

+135
-67
lines changed

supporting-blog-content/building-elasticsearch-apis-with-fastapi-websockets/README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,28 @@ python3 -m venv venv
2525
pip install -r requirements.txt
2626
```
2727

28-
4. **Run the API with uvicorn:**
28+
4. **Set up Elasticsearch credentials:**
29+
30+
Set your Elasticsearch endpoint and API key as environment variables:
31+
32+
```bash
33+
export ELASTICSEARCH_ENDPOINT="your-elasticsearch-endpoint"
34+
export ELASTICSEARCH_API_KEY="your-elasticsearch-api-key"
35+
```
36+
37+
5. **Create the index and load data:**
38+
39+
Run the ingest script to create the products index and load the sample data:
40+
41+
```bash
42+
python ingest_data.py
43+
```
44+
45+
This will:
46+
- Create the `products` index with the proper mapping
47+
- Load all products from `products.ndjson` into Elasticsearch
48+
49+
6. **Run the API with uvicorn:**
2950

3051
```bash
3152
uvicorn main:app --reload

supporting-blog-content/building-elasticsearch-apis-with-fastapi-websockets/index.html

Lines changed: 111 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,88 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>TechStore - Product Search</title>
7+
<script src="https://cdn.tailwindcss.com"></script>
78
</head>
8-
<body>
9-
<header>
10-
<h1>🛍️ TechStore - Find Your Perfect Product</h1>
9+
<body class="bg-gray-50 min-h-screen">
10+
<header class="bg-white shadow-sm border-b">
11+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
12+
<h1 class="text-3xl font-bold text-gray-900">
13+
🛍️ TechStore - Find Your Perfect Product
14+
</h1>
15+
</div>
1116
</header>
1217

13-
<main>
14-
<form onsubmit="event.preventDefault(); searchProducts();">
15-
<fieldset>
16-
<legend>Product Search</legend>
17-
<p>
18-
<label for="searchQuery">Search Products:</label><br />
19-
<input
20-
type="text"
21-
id="searchQuery"
22-
placeholder="Search for phones, laptops, headphones..."
23-
size="50"
24-
required />
25-
<button type="submit">🔍 Search</button>
26-
</p>
27-
</fieldset>
18+
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
19+
<form onsubmit="event.preventDefault(); searchProducts();" class="mb-8">
20+
<div class="bg-white rounded-lg shadow-sm border p-6">
21+
<h2 class="text-lg font-semibold text-gray-900 mb-4">
22+
Product Search
23+
</h2>
24+
<div class="flex flex-col sm:flex-row gap-4">
25+
<div class="flex-1">
26+
<label
27+
for="searchQuery"
28+
class="block text-sm font-medium text-gray-700 mb-2"
29+
>Search Products:</label
30+
>
31+
<input
32+
type="text"
33+
id="searchQuery"
34+
placeholder="Search for phones, laptops, headphones..."
35+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
36+
required />
37+
</div>
38+
<div class="flex items-end">
39+
<button
40+
type="submit"
41+
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md font-medium transition-colors">
42+
🔍 Search
43+
</button>
44+
</div>
45+
</div>
46+
</div>
2847
</form>
2948

30-
<fieldset>
31-
<legend>Live Notifications</legend>
32-
<p id="status">🟡 Connecting to live notifications...</p>
33-
</fieldset>
49+
<div class="bg-white rounded-lg shadow-sm border p-4 mb-8">
50+
<div class="flex items-center justify-between">
51+
<div>
52+
<h3 class="text-sm font-medium text-gray-900">
53+
Live Notifications
54+
</h3>
55+
<p id="status" class="text-xs text-gray-500 mt-1">
56+
🟡 Connecting to live notifications...
57+
</p>
58+
</div>
59+
<div class="w-2 h-2 bg-yellow-400 rounded-full animate-pulse"></div>
60+
</div>
61+
</div>
3462

3563
<section id="searchResults">
36-
<h2>Search Results</h2>
37-
<blockquote>
38-
<em>🔍 Enter a search term above to find products</em>
39-
</blockquote>
64+
<h2 class="text-2xl font-bold text-gray-900 mb-4">Search Results</h2>
65+
<div class="bg-white rounded-lg shadow-sm border p-6">
66+
<p class="text-gray-600 italic">
67+
🔍 Enter a search term above to find products
68+
</p>
69+
</div>
4070
</section>
4171
</main>
4272

4373
<!-- HTML Dialog for notifications -->
44-
<dialog id="notificationDialog">
45-
<fieldset>
46-
<legend>🔔 Live Search Activity</legend>
47-
<p id="notificationMessage"></p>
48-
<p>
49-
<button onclick="closeNotification()" autofocus>OK</button>
50-
</p>
51-
</fieldset>
74+
<dialog id="notificationDialog" class="rounded-lg shadow-xl border">
75+
<div class="bg-white p-6 rounded-lg">
76+
<h3 class="text-lg font-semibold text-gray-900 mb-4">
77+
🔔 Live Search Activity
78+
</h3>
79+
<p id="notificationMessage" class="text-gray-700 mb-6"></p>
80+
<div class="flex justify-end">
81+
<button
82+
onclick="closeNotification()"
83+
autofocus
84+
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md font-medium transition-colors">
85+
OK
86+
</button>
87+
</div>
88+
</div>
5289
</dialog>
5390

5491
<script>
@@ -68,6 +105,9 @@ <h2>Search Results</h2>
68105
ws.onopen = function () {
69106
statusDiv.innerHTML =
70107
"🟢 Connected - You will see when others search for products";
108+
109+
statusDiv.className = "text-sm text-green-600";
110+
71111
console.log("Connected to WebSocket");
72112
};
73113

@@ -84,11 +124,16 @@ <h2>Search Results</h2>
84124

85125
ws.onclose = function () {
86126
statusDiv.innerHTML = "🔴 Disconnected from live notifications";
127+
128+
statusDiv.className = "text-sm text-red-600";
129+
87130
console.log("Disconnected from WebSocket");
88131
};
89132

90133
ws.onerror = function (error) {
91134
statusDiv.innerHTML = "❌ Connection error";
135+
statusDiv.className = "text-sm text-red-600";
136+
92137
console.error("WebSocket error:", error);
93138
};
94139
}
@@ -118,19 +163,19 @@ <h2>Search Results</h2>
118163

119164
if (!query) {
120165
resultsDiv.innerHTML = `
121-
<h2>Search Results</h2>
122-
<blockquote>
123-
<strong>⚠️ Please enter a search term</strong>
124-
</blockquote>
166+
<h2 class="text-2xl font-bold text-gray-900 mb-4">Search Results</h2>
167+
<div class="bg-white rounded-lg shadow-sm border p-6">
168+
<p class="text-red-600 font-medium">⚠️ Please enter a search term</p>
169+
</div>
125170
`;
126171
return;
127172
}
128173

129174
resultsDiv.innerHTML = `
130-
<h2>Search Results</h2>
131-
<blockquote>
132-
<em>🔍 Searching TechStore inventory...</em>
133-
</blockquote>
175+
<h2 class="text-2xl font-bold text-gray-900 mb-4">Search Results</h2>
176+
<div class="bg-white rounded-lg shadow-sm border p-6">
177+
<p class="text-gray-600 italic">🔍 Searching TechStore inventory...</p>
178+
</div>
134179
`;
135180

136181
try {
@@ -148,12 +193,12 @@ <h2>Search Results</h2>
148193
}
149194
} catch (error) {
150195
resultsDiv.innerHTML = `
151-
<h2>Search Results</h2>
152-
<fieldset>
153-
<legend>❌ Search Error</legend>
154-
<p><strong>Error:</strong> ${error.message}</p>
155-
</fieldset>
156-
`;
196+
<h2 class="text-2xl font-bold text-gray-900 mb-4">Search Results</h2>
197+
<div class="bg-white rounded-lg shadow-sm border p-6">
198+
<h3 class="text-lg font-semibold text-red-600 mb-2">❌ Search Error</h3>
199+
<p class="text-gray-700"><strong>Error:</strong> ${error.message}</p>
200+
</div>
201+
`;
157202
}
158203
}
159204

@@ -162,28 +207,30 @@ <h2>Search Results</h2>
162207

163208
if (data.results.length === 0) {
164209
resultsDiv.innerHTML = `
165-
<h2>Search Results</h2>
166-
<fieldset>
167-
<legend>❌ No products found</legend>
168-
<p>No products match "<strong>${data.query}</strong>"</p>
169-
<p><em>Try searching for: iPhone, laptop, headphones, watch, etc.</em></p>
170-
</fieldset>
171-
`;
210+
<h2 class="text-2xl font-bold text-gray-900 mb-4">Search Results</h2>
211+
<div class="bg-white rounded-lg shadow-sm border p-6">
212+
<h3 class="text-lg font-semibold text-red-600 mb-2">❌ No products found</h3>
213+
<p class="text-gray-700 mb-2">No products match "<strong>${data.query}</strong>"</p>
214+
<p class="text-gray-600 italic">Try searching for: iPhone, laptop, headphones, watch, etc.</p>
215+
</div>
216+
`;
172217
return;
173218
}
174219

175-
let html = `<h2>✅ Found ${data.total} products for "${data.query}"</h2>`;
220+
let html = `<h2 class="text-2xl font-bold text-gray-900 mb-4">✅ Found ${data.total} products for "${data.query}"</h2>`;
176221

177222
data.results.forEach((product) => {
178223
html += `
179-
<fieldset>
180-
<legend><strong>${
181-
product.product_name
182-
}</strong></legend>
183-
<p><big>💰 $${product.price.toFixed(2)}</big></p>
184-
<p>${product.description}</p>
185-
</fieldset>
186-
`;
224+
<div class="bg-white rounded-lg shadow-sm border p-6 mb-4">
225+
<h3 class="text-xl font-semibold text-gray-900 mb-3">${
226+
product.product_name
227+
}</h3>
228+
<p class="text-2xl font-bold text-green-600 mb-3">💰 $${product.price.toFixed(
229+
2
230+
)}</p>
231+
<p class="text-gray-600">${product.description}</p>
232+
</div>
233+
`;
187234
});
188235

189236
resultsDiv.innerHTML = html;

supporting-blog-content/building-elasticsearch-apis-with-fastapi-websockets/inegst_data.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ def create_products_index():
1616
mapping = {
1717
"mappings": {
1818
"properties": {
19-
"product_name": {"type": "text", "analyzer": "standard"},
19+
"product_name": {"type": "text"},
2020
"price": {"type": "float"},
21-
"description": {"type": "text", "analyzer": "standard"},
21+
"description": {"type": "text"},
2222
}
2323
}
2424
}

0 commit comments

Comments
 (0)