A lightweight personal blog with public read access and admin panel for content management. Built with Vaadin 25, Java 21, and MariaDB.
Note
This project is based on the Personal Blog Challenge from roadmap.sh.
- Public Views: Home page with post list, individual post detail view
- Admin Panel: Dashboard to manage posts (create, edit, delete)
- HTTP Basic Auth: Secure admin access via environment variables
- Markdown Support: Write posts in Markdown, rendered to HTML
- Responsive Design: Clean, minimal interface using Vaadin Lumo theme
| Layer | Technology |
|---|---|
| Frontend | Vaadin 25.1 (Flow) |
| Backend | Java 21 |
| Database | MariaDB 11.7 |
| ORM | jOOQ 3.21 |
| Migrations | Flyway 12 |
| Server | Jetty 12 (vaadin-boot) |
| Build | Gradle (Groovy DSL) |
- Docker & Docker Compose
- Java 21 (for local development)
git clone https://github.com/vekzz-dev/personal-blog-lite.git
cd personal-blog-lite
cp .env.example .envEdit .env with your credentials:
DB_ROOT_PASSWORD=your_root_password
DB_NAME=blog_lite
DB_USER=blog_user
DB_PASSWORD=your_password
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_admin_passworddocker compose up -d db./gradlew runClick the Admin button in the header or navigate to /admin/dashboard. You'll be prompted for HTTP Basic Auth credentials.
Shows the public blog homepage displaying the list of posts with title, excerpt, and created date.
Full article view with Markdown content rendered to HTML, showing title, content, and back navigation.
Protected admin area with table of all posts, showing actions to edit or delete each post.
Forms for creating and editing posts with title and Markdown content fields. Delete confirmation dialog shown before removing a post.
personal-blog-lite/
├── src/
│ ├── main/
│ │ ├── java/io/vekzzdev/personal_blog_lite/
│ │ │ ├── Main.java # Entry point (Jetty launcher)
│ │ │ ├── config/
│ │ │ │ ├── Bootstrap.java # Servlet context listener, DB setup, DI
│ │ │ │ └── BlogInstantiatorFactory.java # Vaadin view dependency injection
│ │ │ ├── model/
│ │ │ │ └── Post.java # Post domain model
│ │ │ ├── service/
│ │ │ │ ├── PostService.java # Post CRUD business logic
│ │ │ │ └── MarkdownService.java # Markdown to HTML rendering
│ │ │ ├── repository/
│ │ │ │ └── jooq/
│ │ │ │ └── JooqPostRepository.java # jOOQ implementation
│ │ │ ├── security/
│ │ │ │ └── BasicAuthFilter.java # HTTP Basic Auth for /admin/*
│ │ │ ├── ui/
│ │ │ │ ├── components/
│ │ │ │ │ └── BlogHeader.java # Header with admin navigation
│ │ │ │ └── view/
│ │ │ │ ├── HomeView.java # Public: post list
│ │ │ │ ├── PostDetailView.java # Public: single post with markdown
│ │ │ │ └── admin/
│ │ │ │ ├── AdminDashboardView.java # Admin: post management table
│ │ │ │ ├── PostFormView.java # Admin: edit existing post
│ │ │ │ └── NewPostView.java # Admin: create new post
│ │ │ └── generated/
│ │ │ └── jooq/ # Auto-generated jOOQ classes (DO NOT EDIT)
│ │ └── resources/
│ │ ├── db/migration/ # Flyway SQL migrations
│ │ │ ├── V1__initial_schema.sql
│ │ │ └── V2__insert_initial_data.sql
│ │ └── logback.xml # Logging configuration
│ └── test/
│ └── java/io/vekzzdev/personal_blog_lite/
│ └── service/
│ ├── PostServiceTest.java # Unit tests with Mockito
│ └── MarkdownServiceTest.java # Unit tests
├── assets/ # Project screenshots for README
├── build.gradle # Gradle build configuration
├── docker-compose.yml # Docker Compose (MariaDB + app)
├── Dockerfile # Multi-stage Docker build
├── .env.example # Environment template
├── LICENSE # MIT License
├── CHANGELOG.md # Version changelog
└── README.md # This file
| Variable | Description | Required |
|---|---|---|
DB_URL |
JDBC database URL | Yes |
DB_USER |
Database username | Yes |
DB_PASSWORD |
Database password | Yes |
ADMIN_USERNAME |
Admin HTTP Basic Auth username | Yes |
ADMIN_PASSWORD |
Admin HTTP Basic Auth password | Yes |
This project includes a complete Docker Compose setup with MariaDB and the Vaadin application.
- Docker 20.10+
- Docker Compose 2.0+
- Copy the example environment file:
cp .env.example .env- Edit
.envwith your desired credentials:
# Database Configuration
DB_ROOT_PASSWORD=root_pass_2026
DB_NAME=blog_lite
DB_USER=blog_user
DB_PASSWORD=blog_pass_2026
# Admin Credentials (HTTP Basic Auth)
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123
# Database URL (for Docker, use container name 'db')
DB_URL=jdbc:mariadb://db:3306/blog_lite
# MariaDB Version
MARIADB_VERSION=11.7Start all services (database + app):
docker compose up -dThis will start:
- MariaDB on port 3306
- Vaadin App on port 8080
# View running containers
docker compose ps
# View logs
docker compose logs -f app
docker compose logs -f db# Stop all services
docker compose down
# Stop and remove volumes (deletes database data)
docker compose down -v- Public URL: http://localhost:8080
- Admin URL: http://localhost:8080/admin/dashboard (requires Basic Auth)
- Database: localhost:3306 (if port is exposed in docker-compose.yml)
The Docker setup includes health checks for both services:
- MariaDB: Uses
mysqladmin ping - App: Uses
curlto check if the server responds on port 8080
# Restart a specific service
docker compose restart app
docker compose restart db
# View detailed logs
docker compose logs app --tail=100
# Access database directly
docker compose exec db mariadb -u root -p blog_lite
# Rebuild and start
docker compose down
docker compose build --no-cache
docker compose up -dThis section covers local development workflows, testing, and build commands.
- Java 21 (OpenJDK or Temurin)
- Gradle 8.x (wrapper included)
- Docker & Docker Compose (for MariaDB)
- An IDE with Java 21 support (IntelliJ IDEA, VS Code with Extension Pack for Java)
- Clone the repository:
git clone https://github.com/vekzz-dev/personal-blog-lite.git
cd personal-blog-lite- Copy environment template:
cp .env.example .env- Start MariaDB:
docker compose up -d db- Verify database is running:
docker compose ps
# Should show "healthy" for db service./gradlew runThe app starts in development mode with:
- Vaadin Dev Tools enabled
- Live reload for frontend changes
- Debug port available (5005)
# Run with custom Gradle tasks
./gradlew runDev # Uses ADMIN_PASSWORD from env or defaults to 'dev123'
./gradlew runProd # Requires all env vars to be set# Run all unit tests
./gradlew test
# Run a specific test class
./gradlew test --tests "io.vekzzdev.personal_blog_lite.service.PostServiceTest"
# Run tests with coverage report
./gradlew testAfter schema changes, regenerate jOOQ classes:
./gradlew jooqCodegenThis reads the database schema and generates:
BlogLite.java- catalogTables.java- table referencesPosts.java- table definitionPostsRecord.java- record classPostsDao.java- data access object
# Migrate (apply pending migrations)
docker compose exec app java -cp "build/classes/java/main:build/resources/main" org.flywaydb.core.Flyway migrate
# Repair (fix checksum mismatches)
docker compose exec app java -cp "build/classes/java/main:build/resources/main" org.flywaydb.core.Flyway repair
# Info (show migration status)
docker compose exec app java -cp "build/classes/java/main:build/resources/main" org.flywaydb.core.Flyway info./gradlew clean build -Pvaadin.productionModeThis:
- Compiles Java code
- Generates frontend bundle (production mode)
- Runs tests
- Creates distribution in
build/install/
./gradlew build -x test -Pvaadin.productionMode# After building, run the distribution
./build/install/personal-blog-lite/bin/personal-blog-liteOr with Docker:
docker compose build
docker compose up -d apppersonal-blog-lite/
├── src/
│ ├── main/
│ │ ├── java/io/vekzzdev/personal_blog_lite/
│ │ │ ├── Main.java # Entry point (Jetty launcher)
│ │ │ ├── config/
│ │ │ │ ├── Bootstrap.java # Servlet context listener (DI setup)
│ │ │ │ └── BlogInstantiatorFactory.java # Vaadin DI
│ │ │ ├── model/
│ │ │ │ └── Post.java # Domain model
│ │ │ ├── service/
│ │ │ │ ├── PostService.java # Business logic
│ │ │ │ └── MarkdownService.java # Markdown → HTML
│ │ │ ├── repository/
│ │ │ │ └── jooq/
│ │ │ │ └── JooqPostRepository.java # jOOQ implementation
│ │ │ ├── security/
│ │ │ │ └── BasicAuthFilter.java # HTTP Basic Auth
│ │ │ ├── ui/
│ │ │ │ ├── components/
│ │ │ │ │ └── BlogHeader.java # Header with admin button
│ │ │ │ └── view/
│ │ │ │ ├── HomeView.java # Public: post list
│ │ │ │ ├── PostDetailView.java # Public: single post
│ │ │ │ └── admin/
│ │ │ │ ├── AdminDashboardView.java
│ │ │ │ ├── PostFormView.java
│ │ │ │ └── NewPostView.java
│ │ │ └── generated/
│ │ │ └── jooq/ # Auto-generated (DO NOT EDIT)
│ │ └── resources/
│ │ ├── db/migration/
│ │ │ ├── V1__initial_schema.sql
│ │ │ └── V2__insert_initial_data.sql
│ │ └── logback.xml # Logging config
│ └── test/
│ └── java/.../service/
│ ├── PostServiceTest.java
│ └── MarkdownServiceTest.java
├── build.gradle # Gradle build config
├── docker-compose.yml # Docker Compose config
├── Dockerfile # Multi-stage build
├── .env.example # Environment template
└── README.md
| Issue | Solution |
|---|---|
DB_URL not set |
Ensure .env file exists and is loaded |
| jOOQ errors | Run ./gradlew jooqCodegen after schema changes |
| Port 8080 in use | Stop other services or change port in docker-compose.yml |
| Migration failures | Check DB credentials in .env, ensure MariaDB is running |
| Vaadin Dev Tools missing | Ensure NOT running with -Pvaadin.productionMode |
# Run with debug port (attach from IDE)
./gradlew run --debug
# View application logs
tail -f logs/application.log
# Check dependency tree
./gradlew dependencies
# Show build tasks
./gradlew tasksMIT License - see LICENSE for details.







