A semantic search engine built on top of the Turkish Wikipedia corpus. The project combines Word2Vec embeddings, Elasticsearch, and a Next.js frontend to let users search Wikipedia articles using natural language — including synonym-aware and vector-based search modes.
Note: This project was built as a university course assignment. As a result, the UI labels, in-code comments, and some variable names are in Turkish. The codebase and documentation are otherwise written in English.
This project is split across two repositories:
| Repository | Description |
|---|---|
| wikipedia_synonym_search | Data pipeline — parses the Turkish Wikipedia XML dump, trains the Word2Vec model, and indexes articles into Elasticsearch |
| wikipedia_synonym_search_web | This repo — Next.js frontend + Flask API that serves search results to the browser |
The pipeline repo must be run first to produce the trained model and Elasticsearch index that this web app depends on.
Most search engines match keywords exactly. This project goes further: when you search for teknoloji, the engine also finds articles related to bilişim, yazılım, and mühendislik — because Word2Vec knows these words appear in similar contexts across 30,000 Turkish Wikipedia articles.
Three search modes are available:
| Mode | How It Works |
|---|---|
| Synonym | Expands your query with semantically similar words from Word2Vec, then runs an Elasticsearch multi-match search across title and text fields |
| Full-text | Standard Elasticsearch full-text search with a Turkish language analyzer (stop words + Snowball stemmer) |
| Vector | Converts your query into a word vector and finds articles whose average word vector is closest via cosine similarity |
When you click a result, the article summary is fetched live from the Turkish Wikipedia REST API and displayed in the right panel — with your search terms and synonyms highlighted in different colors.
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ Next.js Frontend (port 3000) │
│ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ Left Panel │ │ Right Panel │ │
│ │ · Mode selector │ │ · Article title │ │
│ │ · Search bar │ → │ · Short description │ │
│ │ · Synonym tags │ │ · Full summary text │ │
│ │ · Result list │ │ · Highlighted terms │ │
│ └──────────────────┘ └────────────────────────┘ │
└────────────────┬────────────────────────┬───────────────────┘
│ POST /api/search │ GET Wikipedia REST API
▼ ▼
┌───────────────────┐ ┌─────────────────────────┐
│ Flask Backend │ │ tr.wikipedia.org │
│ (port 5000) │ │ /api/rest_v1/page/ │
│ · search.py │ │ summary/{title} │
│ · Word2Vec model │ └─────────────────────────┘
└────────┬──────────┘
│
┌────────▼──────────┐
│ Elasticsearch │
│ (port 9200) │
│ 30K TR articles │
│ + word vectors │
└───────────────────┘
The Next.js frontend never talks to Elasticsearch directly. It sends requests to its own /api/search route, which proxies them to the Flask backend. The Flask backend handles Word2Vec synonym expansion and Elasticsearch queries.
Frontend
- Next.js 15 (App Router)
- TypeScript
- Tailwind CSS
Backend
- Python + Flask
- Gensim Word2Vec (skip-gram, 100-dimensional vectors, trained on 30K articles)
- Elasticsearch 8.x with Turkish language analyzer
- MongoDB (article storage during the pipeline phase)
wikipedia_synonym_search_web/
├── app/
│ ├── api/search/route.ts # Next.js API route — proxies requests to Flask
│ ├── components/SearchPage.tsx # Main UI: search bar, results, article panel
│ ├── lib/elasticsearch.ts # HTTP client for the Flask backend
│ ├── _styles/globals.css # Global styles + custom scrollbar
│ ├── layout.tsx # HTML shell
│ └── page.tsx # Entry point with Suspense boundary
├── backend/
│ ├── api.py # Flask server exposing /search, /synonyms, /health
│ ├── search.py # Core search logic: Word2Vec + Elasticsearch queries
│ ├── model/ # Trained Word2Vec model files (not committed to git)
│ ├── .env # Elasticsearch credentials
│ └── README.md # Backend-specific setup and API docs
├── .env # Frontend environment (API URL)
└── README.md
The data pipeline lives in the separate wikipedia_synonym_search repository (see Repositories above). It ran three steps before this web app was built:
Parsed the Turkish Wikipedia XML dump (trwiki-latest-pages-articles-multistream.xml) with xml.etree.ElementTree. Cleaned article text (removed templates, HTML, wiki markup, URLs) and stored 30,000 articles in MongoDB.
Fetched articles from MongoDB, preprocessed tokens (punctuation removal, stop word filtering, Turkish Snowball stemming), and trained a skip-gram Word2Vec model with Gensim:
vector_size = 100window = 5min_count = 5epochs = 10sg = 1(skip-gram)
Indexed all articles into Elasticsearch. Each document has:
title— analyzed with a Turkish language analyzer (stop words + Snowball stemmer)text— same analyzerword_vector— 100-dimensional dense vector (average Word2Vec vector of all tokens in the article), enabling cosine similarity search viascript_score
Note: The pipeline is currently limited to 30,000 articles for testing/portfolio purposes. The full Turkish Wikipedia dump contains ~500K+ articles. To index the full dataset, set
limit=Noneinpipeline/xml_processor.pyand re-run all three pipeline steps. Expect significantly longer processing times.
- Node.js 18+
- Python 3.10+
- Elasticsearch 8.x
- MongoDB
- Trained Word2Vec model files (produced by the pipeline repo — see
backend/README.mdand the Repositories section above)
If you haven't already, follow the setup instructions in the wikipedia_synonym_search repo to ingest, train, and index the Wikipedia corpus. This produces:
- The trained model files that go into
backend/model/ - The
wikipediaindex in Elasticsearch
| Service | Port | Command |
|---|---|---|
| MongoDB | 27017 | Runs as a Windows service (auto-start) |
| Elasticsearch | 9200 | net start elasticsearch-service-x64 |
cd backend
pip install flask flask-cors python-dotenv elasticsearch gensim numpy
python api.pyThe Flask server starts on http://localhost:5000.
npm install
npm run devOpen http://localhost:3000.
.env (frontend root):
PYTHON_API_URL=http://localhost:5000backend/.env:
MONGO_URL=mongodb://localhost:27017
ELASTIC_URL=http://localhost:9200
ELASTIC_USERNAME=elastic
ELASTIC_PASSWORD=your_passwordThe Flask backend exposes three endpoints.
| Parameter | Type | Default | Description |
|---|---|---|---|
q |
string | required | Search query |
mode |
string | synonym |
synonym | text | vector |
top |
int | 5 |
Number of results (max 20) |
Example:
GET /search?q=teknoloji&mode=synonym&top=5
Response:
{
"query": "teknoloji",
"mode": "synonym",
"synonyms": [
{ "word": "bilişim", "score": 0.8712 },
{ "word": "yazılım", "score": 0.8501 }
],
"results": [
{ "title": "Bilgisayar", "url": "https://tr.wikipedia.org/wiki/Bilgisayar" },
{ "title": "İnternet", "url": "https://tr.wikipedia.org/wiki/İnternet" }
]
}Returns semantically similar words from the Word2Vec model.
| Parameter | Type | Default | Description |
|---|---|---|---|
q |
string | required | Query word |
top |
int | 10 |
Number of synonyms (max 50) |
Example:
GET /synonyms?q=teknoloji&top=10
Response:
{
"query": "teknoloji",
"synonyms": [
{ "word": "bilişim", "score": 0.8712 },
{ "word": "yazılım", "score": 0.8501 }
]
}{ "status": "ok" }query → Word2Vec.most_similar() → [synonym1, synonym2, ...]
↓
expanded_query = "query synonym1 synonym2 ..."
↓
Elasticsearch multi_match (best_fields) on title^2 + text
query → Elasticsearch multi_match on title^2 + text
(Turkish analyzer: stop words + Snowball stemmer)
query tokens → Word2Vec vectors → mean vector
↓
Elasticsearch script_score: cosineSimilarity(query_vec, 'word_vector') + 1.0