The personal full-stack portfolio and engineering journal of Onur Erkoç.
This README is available in two languages:
onurerkoc.dev is a production-deployed personal portfolio built as a real
full-stack system rather than a static portfolio template.
The platform combines:
- a React and Vite frontend;
- a Java 21 and Spring Boot backend;
- PostgreSQL persistence;
- Docker-based local and production environments;
- Nginx reverse proxying;
- Cloudflare-managed DNS and HTTPS;
- Resend API contact notifications;
- route-aware technical SEO;
- a feature branch and pull request workflow.
The purpose of the project is to present my work while documenting the engineering, infrastructure, and deployment decisions behind it.
The application is live:
https://onurerkoc.dev
Completed areas:
- personal portfolio homepage;
- project list and detail pages;
- backend-driven project data;
- PostgreSQL persistence;
- contact message persistence;
- Resend HTTPS API notifications;
- Docker images for the frontend and backend;
- Docker Compose orchestration;
- production deployment on Ubuntu;
- host and container Nginx configuration;
- HTTPS with Let's Encrypt and Certbot;
- Cloudflare DNS;
- route-specific SEO metadata;
robots.txt,sitemap.xml, and JSON-LD;- Git feature branch and pull request workflow.
A production portfolio platform powered by a Spring Boot API, PostgreSQL, React, Docker Compose, and Nginx.
- Repository: onurerkoc-dev/onurerkoc.dev
- Live: onurerkoc.dev
A full-stack workspace for technical SEO analysis, metadata inspection, robots.txt and sitemap checks, PageSpeed reporting, WHOIS lookup, and domain research.
- Repository: onurerkoc-dev/seo-toolkit-platform
A dependency-free Python CLI that creates controlled query sets for authorized OSINT, education, security research, and bug bounty reconnaissance.
- Repository: onurerkoc-dev/Advanced-Google-Dork-Generator
- React
- Vite
- JavaScript
- React Router
- CSS
- Nginx production container
- Java 21
- Spring Boot
- Spring Web MVC
- Spring
RestClient - Spring Data JPA
- Hibernate
- Maven
- REST API
- Resend Email API
- PostgreSQL 18
- Docker
- Docker Compose
- Ubuntu 24.04 LTS
- DigitalOcean
- Host Nginx
- Cloudflare DNS
- Let's Encrypt
- Certbot
- UFW
User
-> Cloudflare-managed DNS
-> DigitalOcean Ubuntu server
-> Host Nginx
-> React frontend container
-> Frontend Nginx
-> Spring Boot backend container
-> PostgreSQL container
Docker Compose runs three services:
frontend
backend
database
Public traffic enters through ports 80 and 443. Host Nginx forwards the
website to the frontend container:
Internet
-> Host Nginx
-> 127.0.0.1:3000
-> Frontend container
The frontend container forwards API traffic through the internal Docker network:
Browser
-> /api
-> Frontend Nginx
-> backend:8080
-> Spring Boot
The application ports are bound only to localhost:
Frontend: 127.0.0.1:3000
Backend: 127.0.0.1:8080
PostgreSQL is available only inside the Docker network and is not exposed to the public internet.
- personal introduction and technical focus;
- real project cards instead of placeholder projects;
- project data loaded from PostgreSQL;
- slug-based project detail routes;
- backend-driven project summaries;
- technology lists;
- architecture decisions;
- project roadmaps;
- loading, error, empty, and not-found states.
- controlled React contact form;
- backend validation and input trimming;
- field length limits;
- PostgreSQL persistence;
- automatic creation timestamps;
- Resend HTTPS API notification emails;
- verified
mail.onurerkoc.devsending domain; - visitor email address passed as
reply_to; - database persistence before the notification attempt;
- error logging when notification delivery fails.
- route-specific page titles;
- route-specific meta descriptions;
- dynamic canonical URLs;
- Open Graph metadata;
- Twitter card metadata;
robots.txt;sitemap.xml;Personstructured data;WebSitestructured data;- project-specific
SoftwareSourceCodestructured data; noindex, nofollowon not-found routes.
PostgreSQL
-> ProjectRepository
-> ProjectEntity
-> ProjectMapper
-> ProjectDto
-> ProjectService
-> ProjectController
-> JSON response
-> React frontend
React ContactForm
-> Frontend API layer
-> POST /api/contact
-> ContactController
-> ContactService
-> ContactMessageRepository
-> PostgreSQL
-> ContactNotificationService
-> Spring RestClient
-> Resend HTTPS API
-> Notification inbox
The database save happens before the notification attempt. If the email API request fails, the contact message remains stored in PostgreSQL.
onurerkoc.dev/
├── backend/
│ ├── database/
│ │ └── project-data.sql
│ ├── src/main/java/com/onurerkoc/backend/
│ │ ├── contact/
│ │ │ ├── ContactController.java
│ │ │ ├── ContactMessage.java
│ │ │ ├── ContactMessageRepository.java
│ │ │ ├── ContactNotificationService.java
│ │ │ ├── ContactRequestDto.java
│ │ │ └── ContactService.java
│ │ ├── controller/
│ │ │ └── HealthController.java
│ │ └── project/
│ │ ├── ProjectController.java
│ │ ├── ProjectDto.java
│ │ ├── ProjectEntity.java
│ │ ├── ProjectMapper.java
│ │ ├── ProjectRepository.java
│ │ └── ProjectService.java
│ ├── src/main/resources/
│ │ └── application.properties
│ ├── Dockerfile
│ ├── pom.xml
│ ├── mvnw
│ └── mvnw.cmd
├── frontend/
│ ├── public/
│ │ ├── robots.txt
│ │ └── sitemap.xml
│ ├── src/
│ │ ├── api/
│ │ ├── components/
│ │ ├── pages/
│ │ ├── App.jsx
│ │ ├── App.css
│ │ └── main.jsx
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── package.json
│ └── vite.config.js
├── docs/
│ └── production-deployment.md
├── infrastructure/
│ └── nginx/
│ └── onurerkoc.dev.conf
├── .env.example
├── .gitignore
├── compose.yaml
└── README.md
- Java 21
- Node.js
- npm
- Git
- Docker Desktop
Create a real .env file in the repository root:
POSTGRES_PASSWORD=your-database-password
CONTACT_EMAIL_ENABLED=false
RESEND_API_KEY=your-resend-api-key
CONTACT_EMAIL_FROM="Onur Erkoç Portfolio <contact@mail.onurerkoc.dev>"
CONTACT_NOTIFICATION_TO=your-notification-address@example.comKeep CONTACT_EMAIL_ENABLED=false until the Resend domain and API key are
ready.
The real .env file is ignored by Git. Never commit production passwords or
API keys.
Validate the Compose file without printing resolved secret values:
docker compose config --quietBuild and start the stack:
docker compose up --build -dCheck the services:
docker compose psRead backend logs:
docker compose logs --tail=200 backendOpen:
Frontend: http://localhost:3000
Backend: http://localhost:8080
Stop the stack without deleting database data:
docker compose downDo not use docker compose down -v during normal development. The -v flag
deletes the PostgreSQL volume and its data.
Copy the SQL script into the database container:
docker compose cp backend/database/project-data.sql database:/tmp/project-data.sqlRun the script:
docker compose exec database psql `
-U postgres `
-d onurerkoc_dev `
-f /tmp/project-data.sqlThe script recreates the canonical portfolio projects without deleting contact messages.
Create the onurerkoc_dev PostgreSQL database and set the required connection
values:
cd backend
$env:DB_URL = "jdbc:postgresql://localhost:5432/onurerkoc_dev"
$env:DB_USERNAME = "postgres"
$env:DB_PASSWORD = "your-postgresql-password"
.\mvnw.cmd spring-boot:runStart the backend first, then:
cd frontend
npm install
npm run devVite serves the frontend at:
http://localhost:5173
Vite forwards /api requests to the Spring Boot backend.
| Variable | Purpose |
|---|---|
POSTGRES_PASSWORD |
PostgreSQL container password |
DB_URL |
JDBC connection URL |
DB_USERNAME |
PostgreSQL username |
DB_PASSWORD |
Backend database password |
| Variable | Purpose |
|---|---|
CONTACT_EMAIL_ENABLED |
Enables or disables notification delivery |
RESEND_API_KEY |
Authenticates requests to the Resend Email API |
CONTACT_EMAIL_FROM |
Verified sender name and email address |
CONTACT_NOTIFICATION_TO |
Notification recipient |
Real values belong only in the Git-ignored .env file.
GET /api/healthGET /api/projectsGET /api/projects/{slug}Examples:
GET /api/projects/onurerkoc-dev
GET /api/projects/seo-toolkit-platform
GET /api/projects/advanced-google-dork-generator
Unknown slugs return 404 Not Found.
POST /api/contactExample request:
{
"name": "Onur Erkoç",
"email": "onur@example.com",
"message": "Hello from the contact form."
}Responses:
201 Created -> Message validated and stored
400 Bad Request -> Invalid or oversized request
Current limits:
name -> 100 characters
email -> 255 characters
message -> 2000 characters
A successful request stores the message before attempting notification delivery.
Portfolio projects are stored in:
projects
project_tech_stack
project_key_decisions
project_next_steps
Contact messages are stored in:
contact_messages
Project reads use:
@Transactional(readOnly = true)Open Session in View is disabled:
spring.jpa.open-in-view=falseEntity-to-DTO mapping is completed inside the service transaction.
Deployment documentation:
docs/production-deployment.md
Reusable host Nginx configuration:
infrastructure/nginx/onurerkoc.dev.conf
Run on the Ubuntu server:
cd ~/onurerkoc.dev
git switch main
git pull origin main
sudo docker compose up -d --build
sudo docker compose pscurl https://onurerkoc.dev/api/health
curl https://onurerkoc.dev/api/projects
curl -I https://onurerkoc.dev/robots.txt
curl -I https://onurerkoc.dev/sitemap.xmlPublic ports:
22 -> SSH
80 -> HTTP
443 -> HTTPS
Not publicly exposed:
3000
8080
5432
main
-> focused branch
-> commit
-> push
-> pull request
-> review
-> merge
-> update local and production main
Create a branch:
git switch main
git pull origin main
git switch -c feature/feature-nameReview and commit only the intended files:
git status
git diff
git add path/to/file
git diff --cached
git commit -m "Describe the completed change"Push:
git push -u origin feature/feature-nameAfter merge:
git switch main
git pull origin main
git branch -d feature/feature-name- Real
.envfiles are ignored by Git. - Database passwords and API keys are never committed.
- Contact notifications use a verified Resend sending domain.
- PostgreSQL is not exposed to the public internet.
- Frontend and backend container ports are bound to localhost.
- Only SSH, HTTP, and HTTPS are public.
- UFW restricts public network access.
- Contact messages remain stored if notification delivery fails.
- The public contact endpoint still needs rate limiting and stronger spam protection.
- expand the personal introduction and experience sections;
- add résumé and education content;
- improve project screenshots and case studies;
- strengthen mobile presentation;
- refine calls to action.
- define a locale strategy;
- add Turkish and English interface content;
- decide the multilingual route structure;
- translate validation and status messages;
- add multilingual SEO rules.
- add automated backend and integration tests;
- replace schema auto-update with Flyway migrations;
- improve email delivery observability;
- add endpoint rate limiting;
- add contact spam protection.
- add critical frontend flow tests;
- add CI checks;
- automate deployment after the manual workflow is fully documented and understood.
onurerkoc.dev, statik bir portfolyo şablonu yerine gerçek bir full-stack
sistem olarak geliştirilen ve production ortamında çalışan kişisel yazılım
portfolyomdur.
Platform şu parçaları bir araya getirir:
- React ve Vite frontend;
- Java 21 ve Spring Boot backend;
- PostgreSQL veri kalıcılığı;
- Docker tabanlı yerel ve production ortamları;
- Nginx reverse proxy;
- Cloudflare tarafından yönetilen DNS ve HTTPS;
- Resend API iletişim bildirimleri;
- route bazlı teknik SEO;
- feature branch ve pull request akışı.
Projenin amacı yalnızca çalışmalarımı göstermek değil; bu çalışmaların arkasındaki yazılım, altyapı ve deployment kararlarını da belgelemektir.
Uygulama canlıdır:
https://onurerkoc.dev
Tamamlanan alanlar:
- kişisel portfolyo ana sayfası;
- proje liste ve detay sayfaları;
- backend tarafından sağlanan proje verileri;
- PostgreSQL veri kalıcılığı;
- iletişim mesajlarının kaydedilmesi;
- Resend HTTPS API bildirimleri;
- frontend ve backend Docker imajları;
- Docker Compose servis yönetimi;
- Ubuntu production deployment;
- host ve container Nginx yapılandırması;
- Let's Encrypt ve Certbot ile HTTPS;
- Cloudflare DNS;
- route bazlı SEO metadata;
robots.txt,sitemap.xmlve JSON-LD;- Git feature branch ve pull request düzeni.
Spring Boot API, PostgreSQL, React, Docker Compose ve Nginx kullanan canlı kişisel portfolyo platformu.
- Repository: onurerkoc-dev/onurerkoc.dev
- Canlı: onurerkoc.dev
Teknik SEO analizi, metadata inceleme, robots.txt ve sitemap kontrolleri, PageSpeed raporlama, WHOIS sorgulama ve domain araştırmasını tek çalışma alanında birleştiren full-stack platform.
- Repository: onurerkoc-dev/seo-toolkit-platform
Yetkili OSINT, eğitim, güvenlik araştırması ve bug bounty keşif süreçleri için kontrollü sorgu listeleri üreten, harici bağımlılığı olmayan Python CLI aracı.
- Repository: onurerkoc-dev/Advanced-Google-Dork-Generator
- React
- Vite
- JavaScript
- React Router
- CSS
- Production Nginx container
- Java 21
- Spring Boot
- Spring Web MVC
- Spring
RestClient - Spring Data JPA
- Hibernate
- Maven
- REST API
- Resend Email API
- PostgreSQL 18
- Docker
- Docker Compose
- Ubuntu 24.04 LTS
- DigitalOcean
- Host Nginx
- Cloudflare DNS
- Let's Encrypt
- Certbot
- UFW
Kullanıcı
-> Cloudflare tarafından yönetilen DNS
-> DigitalOcean Ubuntu sunucu
-> Host Nginx
-> React frontend container
-> Frontend Nginx
-> Spring Boot backend container
-> PostgreSQL container
Docker Compose üç servis çalıştırır:
frontend
backend
database
Dış trafik 80 ve 443 portlarından host Nginx'e ulaşır:
İnternet
-> Host Nginx
-> 127.0.0.1:3000
-> Frontend container
Frontend container, API trafiğini dahili Docker ağı üzerinden backend'e iletir:
Tarayıcı
-> /api
-> Frontend Nginx
-> backend:8080
-> Spring Boot
Uygulama portları yalnızca localhost'a bağlıdır:
Frontend: 127.0.0.1:3000
Backend: 127.0.0.1:8080
PostgreSQL yalnızca Docker ağı içinden erişilebilir ve internete açık değildir.
- kişisel tanıtım ve teknik odak;
- placeholder yerine gerçek proje kartları;
- PostgreSQL'den yüklenen proje verileri;
- slug tabanlı proje detay route'ları;
- backend tarafından sağlanan proje özetleri;
- teknoloji listeleri;
- mimari kararlar;
- proje yol haritaları;
- loading, error, empty ve not-found durumları.
- kontrollü React iletişim formu;
- backend doğrulaması ve girdi temizleme;
- alan uzunluğu sınırları;
- PostgreSQL veri kaydı;
- otomatik oluşturulma zamanı;
- Resend HTTPS API bildirim e-postaları;
- doğrulanmış
mail.onurerkoc.devgönderim alan adı; - ziyaretçi adresinin
reply_toolarak kullanılması; - bildirim denemesinden önce veritabanı kaydı;
- bildirim gönderilemediğinde hata loglama.
- route bazlı sayfa başlıkları;
- route bazlı meta description;
- dinamik canonical URL;
- Open Graph metadata;
- Twitter card metadata;
robots.txt;sitemap.xml;Personstructured data;WebSitestructured data;- projeye özel
SoftwareSourceCodestructured data; - bulunamayan route'lar için
noindex, nofollow.
PostgreSQL
-> ProjectRepository
-> ProjectEntity
-> ProjectMapper
-> ProjectDto
-> ProjectService
-> ProjectController
-> JSON yanıtı
-> React frontend
React ContactForm
-> Frontend API katmanı
-> POST /api/contact
-> ContactController
-> ContactService
-> ContactMessageRepository
-> PostgreSQL
-> ContactNotificationService
-> Spring RestClient
-> Resend HTTPS API
-> Bildirim kutusu
Veritabanı kaydı bildirim denemesinden önce yapılır. E-posta API isteği başarısız olsa bile iletişim mesajı PostgreSQL içinde kalır.
- Java 21
- Node.js
- npm
- Git
- Docker Desktop
Proje kökünde gerçek bir .env dosyası oluştur:
POSTGRES_PASSWORD=veritabani-parolan
CONTACT_EMAIL_ENABLED=false
RESEND_API_KEY=resend-api-anahtarin
CONTACT_EMAIL_FROM="Onur Erkoç Portfolio <contact@mail.onurerkoc.dev>"
CONTACT_NOTIFICATION_TO=bildirim-adresin@example.comResend domaini ve API anahtarı hazır olana kadar
CONTACT_EMAIL_ENABLED=false kullan.
Gerçek .env dosyası Git tarafından ignore edilir. Production parolalarını
ve API anahtarlarını hiçbir zaman commit etme.
Secret değerleri terminale yazdırmadan Compose dosyasını doğrula:
docker compose config --quietSistemi build edip başlat:
docker compose up --build -dServisleri kontrol et:
docker compose psBackend loglarını oku:
docker compose logs --tail=200 backendAdresler:
Frontend: http://localhost:3000
Backend: http://localhost:8080
Veritabanı verilerini silmeden sistemi durdur:
docker compose downNormal geliştirme sırasında docker compose down -v kullanma. -v seçeneği
PostgreSQL volume'unu ve içindeki verileri siler.
SQL dosyasını database container'ına kopyala:
docker compose cp backend/database/project-data.sql database:/tmp/project-data.sqlScripti çalıştır:
docker compose exec database psql `
-U postgres `
-d onurerkoc_dev `
-f /tmp/project-data.sqlScript, iletişim mesajlarını silmeden güncel portfolyo projelerini yeniden oluşturur.
onurerkoc_dev PostgreSQL veritabanını oluştur ve bağlantı değerlerini tanımla:
cd backend
$env:DB_URL = "jdbc:postgresql://localhost:5432/onurerkoc_dev"
$env:DB_USERNAME = "postgres"
$env:DB_PASSWORD = "postgresql-parolan"
.\mvnw.cmd spring-boot:runÖnce backend'i çalıştır, ardından:
cd frontend
npm install
npm run devVite frontend adresi:
http://localhost:5173
Vite, /api isteklerini Spring Boot backend'e yönlendirir.
| Değişken | Açıklama |
|---|---|
POSTGRES_PASSWORD |
PostgreSQL container parolası |
DB_URL |
JDBC bağlantı adresi |
DB_USERNAME |
PostgreSQL kullanıcı adı |
DB_PASSWORD |
Backend veritabanı parolası |
| Değişken | Açıklama |
|---|---|
CONTACT_EMAIL_ENABLED |
Bildirim gönderimini açar veya kapatır |
RESEND_API_KEY |
Resend Email API isteklerini yetkilendirir |
CONTACT_EMAIL_FROM |
Doğrulanmış gönderici adı ve e-posta adresi |
CONTACT_NOTIFICATION_TO |
Bildirimin gönderileceği adres |
Gerçek değerler yalnızca Git tarafından ignore edilen .env dosyasında
tutulmalıdır.
GET /api/healthGET /api/projectsGET /api/projects/{slug}Örnekler:
GET /api/projects/onurerkoc-dev
GET /api/projects/seo-toolkit-platform
GET /api/projects/advanced-google-dork-generator
Bulunamayan slug için 404 Not Found döner.
POST /api/contactÖrnek istek:
{
"name": "Onur Erkoç",
"email": "onur@example.com",
"message": "İletişim formundan gönderilen test mesajı."
}Yanıtlar:
201 Created -> Mesaj doğrulandı ve kaydedildi
400 Bad Request -> Geçersiz veya fazla uzun istek
Mevcut sınırlar:
name -> 100 karakter
email -> 255 karakter
message -> 2000 karakter
Başarılı istek, bildirim göndermeyi denemeden önce mesajı veritabanına kaydeder.
Portfolyo proje verileri şu tablolarda tutulur:
projects
project_tech_stack
project_key_decisions
project_next_steps
İletişim mesajları şu tabloda tutulur:
contact_messages
Proje okuma işlemleri:
@Transactional(readOnly = true)Open Session in View kapalıdır:
spring.jpa.open-in-view=falseEntity-to-DTO dönüşümü service transaction içinde tamamlanır.
Deployment dokümanı:
docs/production-deployment.md
Tekrar kullanılabilir host Nginx yapılandırması:
infrastructure/nginx/onurerkoc.dev.conf
Ubuntu sunucuda:
cd ~/onurerkoc.dev
git switch main
git pull origin main
sudo docker compose up -d --build
sudo docker compose pscurl https://onurerkoc.dev/api/health
curl https://onurerkoc.dev/api/projects
curl -I https://onurerkoc.dev/robots.txt
curl -I https://onurerkoc.dev/sitemap.xmlİnternete açık portlar:
22 -> SSH
80 -> HTTP
443 -> HTTPS
İnternete açık olmayan portlar:
3000
8080
5432
main
-> odaklı branch
-> commit
-> push
-> pull request
-> kontrol
-> merge
-> yerel ve production main güncelleme
Branch oluştur:
git switch main
git pull origin main
git switch -c feature/feature-nameYalnızca gerekli dosyaları incele ve commit et:
git status
git diff
git add dosya/yolu
git diff --cached
git commit -m "Tamamlanan değişikliği açıkla"Push:
git push -u origin feature/feature-nameMerge sonrasında:
git switch main
git pull origin main
git branch -d feature/feature-name- Gerçek
.envdosyaları Git tarafından ignore edilir. - Veritabanı parolaları ve API anahtarları commit edilmez.
- İletişim bildirimleri doğrulanmış bir Resend gönderim alan adı kullanır.
- PostgreSQL internete açık değildir.
- Frontend ve backend container portları localhost'a bağlıdır.
- Yalnızca SSH, HTTP ve HTTPS dışarıya açıktır.
- UFW dış ağ erişimini sınırlar.
- Bildirim gönderilemese bile iletişim mesajı veritabanında kalır.
- Public iletişim endpoint'i için rate limiting ve daha güçlü spam koruması henüz eklenmelidir.
- kişisel tanıtım ve deneyim alanlarını genişletmek;
- özgeçmiş ve eğitim bilgilerini eklemek;
- proje ekran görüntülerini ve vaka analizlerini geliştirmek;
- mobil sunumu iyileştirmek;
- çağrı butonlarını daha anlaşılır hale getirmek.
- locale stratejisini belirlemek;
- Türkçe ve İngilizce arayüz içeriği eklemek;
- çoklu dil route yapısını belirlemek;
- validation ve durum mesajlarını çevirmek;
- çoklu dil SEO kurallarını eklemek.
- otomatik backend ve entegrasyon testleri eklemek;
- schema auto-update yerine Flyway migration kullanmak;
- e-posta teslimat gözlemlenebilirliğini geliştirmek;
- endpoint rate limiting eklemek;
- iletişim formuna spam koruması eklemek.
- kritik frontend akış testlerini eklemek;
- CI kontrolleri eklemek;
- manuel süreç tamamen belgelenip öğrenildikten sonra deployment otomasyonu eklemek.