Corporate semantic search engine with vector storage in PostgreSQL. This project demonstrates how to build an embedding-based information retrieval system using Spring Boot, Angular, PostgreSQL and the pgvector extension.
- Java: 21.0.2 LTS
- Spring Boot: 3.2.6
- Maven: 3.9.6
- Hibernate: 6.4.8.Final
- PostgreSQL: 16.2
- pgvector: 0.6.2
- Angular: 17.3.12
- Angular CLI: 17.3.12
- TypeScript: 5.3.3
- RxJS: 7.8.1
- Node.js: 20.12.2 LTS
- Docker Compose: v2.26
- Backend Docker image:
eclipse-temurin:21-jre-alpine - Frontend Docker image:
node:20.12-bookworm-slim(build),nginx:stable-alpine(serve)
- Docker with Docker Compose v2.26 or higher.
- (Optional) Node.js 20.12.2 if you want to run the frontend in local development mode.
- (Optional) Maven 3.9.6 and Java 21.0.2 if you want to compile the backend locally.
- Windows users: For ingestion scripts, use
curl.exeor Git Bash. PowerShellInvoke-RestMethodmay cause encoding issues (see troubleshooting).
semantic-search-engine/
├── backend/ # Spring Boot application
│ ├── src/main/java/... # Java source code
│ ├── src/main/resources/ # application.yml and schema.sql
│ ├── pom.xml # Maven with Spring Boot 3.2.6
│ └── Dockerfile # eclipse-temurin:21-jre-alpine
├── frontend/ # Angular 17 application
│ ├── src/app/ # Components, services and models
│ ├── package.json # Angular 17.3.12 dependencies
│ ├── Dockerfile # Node builder + Nginx
│ └── nginx.conf # Proxy to the backend
├── scripts/ # Utility scripts
│ ├── generate-sample-data.sh # Inserts documents via API (bash)
│ └── seed-db.sql # Inserts documents via SQL
├── docker-compose.yml # Orchestrates PostgreSQL + backend + frontend
├── .env.example # Example environment variables
└── README.md # This guide
cd semantic-search-engine
cp .env.example .envThe .env file contains default credentials suitable for local development.
docker compose up --build -dThis builds and starts three services:
postgres: PostgreSQL 16 with pgvector on port5432.backend: Spring Boot REST API on port8080.frontend: Angular application served by Nginx on port80.
The backend waits for PostgreSQL to be healthy before starting, and the frontend waits for the backend.
curl http://localhost:8080/api/healthExpected response:
{"status":"UP","service":"semantic-search"}./scripts/generate-sample-data.shThe script waits for the backend and then ingests 15 sample documents about AI, NLP, transformers and vector databases.
docker exec -i semantic-search-postgres psql -U pablo -d semantic_search < scripts/seed-db.sqlThis alternative does not require the backend to be running.
# Clear the table if it already has data
docker exec -i semantic-search-postgres psql -U pablo -d semantic_search -c "TRUNCATE documents RESTART IDENTITY;"
# List of sample documents (correct UTF-8)
$documents = @(
"Transformers revolutionized natural language processing by introducing the self-attention mechanism.",
"The attention mechanism lets models weigh the importance of different parts of a sequence.",
"BERT is a bidirectional transformer-based pre-trained language model.",
"GPT uses autoregressive decoders to generate coherent text.",
"Vector databases store embeddings for efficient semantic search.",
"pgvector is a PostgreSQL extension that stores and queries embedding vectors.",
"Cosine similarity measures closeness between embedding vectors.",
"Embeddings are dense numerical representations that capture the semantic meaning of text.",
"Deep learning enables machines to understand and generate natural language.",
"Diffusion models generate high-quality images from text descriptions.",
"RAG (Retrieval-Augmented Generation) combines document retrieval with text generation.",
"Cognitive agents use reasoning, planning and external tools to solve complex tasks.",
"HNSW is an approximate nearest-neighbor search algorithm in vector spaces.",
"Tokenization splits text into smaller units such as words or subwords.",
"Fine-tuning adapts pre-trained language models to specific tasks and domains."
)
foreach ($doc in $documents) {
$json = "{`"content`":`"$doc`"}"
Write-Host "Ingesting: $doc"
curl.exe -X POST http://localhost:8080/api/ingest -H "Content-Type: application/json; charset=utf-8" -d $json
}Open http://localhost in your browser. Type a query such as:
"transformers and natural language processing"
and press Search. Results are ordered by cosine similarity.
On Linux/macOS/Git Bash:
curl -X POST http://localhost:8080/api/search \
-H "Content-Type: application/json" \
-d '{"query": "embeddings and vector databases", "topK": 5}'On PowerShell (Windows):
curl.exe -X POST http://localhost:8080/api/search -H "Content-Type: application/json" -d '{"query":"embeddings and vector databases","topK":5}'On Linux/macOS/Git Bash:
curl -X POST http://localhost:8080/api/ingest \
-H "Content-Type: application/json" \
-d '{"content": "Your document text here"}'On PowerShell (Windows):
curl.exe -X POST http://localhost:8080/api/ingest -H "Content-Type: application/json; charset=utf-8" -d '{"content":"Your document text here"}'- The embedding service generates normalized random 384-dimensional vectors to demonstrate functionality without depending on external APIs.
- In production it is recommended to replace
EmbeddingServicewith a call to a model such assentence-transformers/all-MiniLM-L6-v2. - The native pgvector query uses the
<=>operator for cosine distance on theembedding vector(384)column. - An HNSW index (
vector_cosine_ops) was created to accelerate similarity searches. - Document insertion uses
JdbcTemplatewith a native query that casts the embedding to thevector(384)type viaCAST(? AS vector(384)). This prevents the JDBC driver from interpreting the array ascharacter varying.
Symptom: ERROR: column "embedding" is of type vector but expression is of type character varying.
Cause: The PostgreSQL JDBC driver sends the double array as character varying and pgvector does not perform implicit conversion.
Solution: Insertion is performed through JdbcTemplate with the query:
INSERT INTO documents (content, embedding) VALUES (?, CAST(? AS vector(384)))
RETURNING id, content, embedding, created_atThe explicit CAST guarantees conversion to the native pgvector type. Make sure the backend has the spring-boot-starter-jdbc dependency in pom.xml.
Symptom: HttpMessageNotReadableException: JSON parse error: Invalid UTF-8....
Cause: PowerShell Invoke-RestMethod sends JSON with a non-UTF-8 encoding, breaking accents and tildes.
Solution: Use curl.exe or Git Bash instead. See Option C in the sample data section.
Symptom: The npm ci command can only install with an existing package-lock.json.
Cause: package-lock.json was not included in the build context.
Solution: Run npm install in the frontend folder before docker compose up --build. This generates the package-lock.json file that npm ci requires.
cd backend
./mvnw spring-boot:runcd frontend
npm install
npm startThe application will be available at http://localhost:4200.
This project's code is licensed under the MIT License.
This repository was published in 2026 as a curated reference of work originally designed and prototyped between January 2024 - June 2024. The technology stack reflects the versions available during that development window and has been intentionally preserved for historical accuracy.