Lampray Technical Deep Dive: Architecture, Design, and Getting Started
Lampray is an open-source blog platform built from the ground up with Spring Boot 3 on the backend and Vue 3 on the frontend. It's designed for creators, developers, and communities who want full control over their publishing infrastructure without sacrificing modern development practices.
In this post, we'll take a deep dive into Lampray's technical architecture, explore the design decisions behind it, and walk through how to get it running in your own environment.
Architecture Overview
Lampray follows a modular monolith architecture. Rather than jumping straight into microservices, the project is organized as a multi-module Gradle project where each module has a clear, bounded responsibility. This keeps deployment simple while maintaining clean separation of concerns.
lampray/
├── lampray-common/ # Shared utilities and base abstractions
├── lampray-common-data/ # Data layer: entities, repositories, migrations
├── lampray-content/ # Content management: articles, pages, taxonomies
├── lampray-file/ # File storage abstraction (local, S3, etc.)
├── lampray-iam/ # Identity and access management
├── lampray-push/ # Notification and push services
├── lampray-system/ # System configuration and monitoring
├── lampray-user/ # User profile and account management
├── lampray-web/ # Web entry point, REST controllers, security config
├── lampray-frontend/ # Vue 3 single-page application
Each module is a Gradle subproject with its own build.gradle.kts. The lampray-web module acts as the application entry point, pulling in dependencies from all other modules and exposing RESTful APIs consumed by the Vue frontend.
Why a Modular Monolith?
The modular monolith approach gives us the best of both worlds:
- Single deployable artifact — One JAR file to build, ship, and run. No orchestration overhead.
- Clear module boundaries — Each module has a well-defined API surface. Modules communicate through interfaces, not implementation leakage.
- Future-proof — When a module grows large enough, extracting it into a standalone microservice is straightforward because the boundaries already exist.
Technology Stack
Backend
| Component | Technology |
|---|---|
| Framework | Spring Boot 3 |
| Language | Java 17, Kotlin |
| Build System | Gradle (Kotlin DSL) |
| Security | Spring Security |
| Database | SQLite, H2, MySQL, PostgreSQL, MariaDB, Oracle, SQL Server |
| ORM | Spring Data JPA / Hibernate |
Frontend
| Component | Technology |
|---|---|
| Framework | Vue 3 (Composition API) |
| UI Library | Naive UI |
| Language | TypeScript |
| Build | Vite |
Multi-Database Strategy
One of Lampray's standout features is its support for seven database engines out of the box. This was not an afterthought — it was a deliberate architectural decision from day one.
How It Works
Lampray uses Spring Data JPA with Hibernate as the ORM layer. Database dialect selection is automatic based on the configured type field in the TOML configuration file. The application supports three target formats for maximum flexibility:
- Network address —
host:portsyntax (e.g.,localhost:3306for MySQL) - File-based —
file:./data/app.dborfile:/absolute/path/to/db - In-memory —
memory(useful for development and testing)
Here's a MySQL configuration example:
[database]
type = "mysql"
target = "localhost:3306"
name = "lampray"
username = "root"
password = "password"
options = ["useSSL=false", "serverTimezone=UTC"]
And a production-ready SQLite file-based configuration:
[database]
type = "sqlite"
target = "file:./data/lampray.db"
name = "lampray"
username = ""
password = ""
Dialect Detection
The type field maps to a DatabaseType enum that resolves the appropriate Hibernate dialect, JDBC driver, and connection strategy. This abstraction lives in lampray-common-data and is one of the earliest modules designed in the project.
public enum DatabaseType {
SQLITE,
H2,
MYSQL,
POSTGRESQL,
MARIADB,
ORACLE,
SQLSERVER;
public String dialect() {
return switch (this) {
case SQLITE -> "org.hibernate.dialect.SQLiteDialect";
case H2 -> "org.hibernate.dialect.H2Dialect";
case MYSQL -> "org.hibernate.dialect.MySQLDialect";
// ...
};
}
}
Schema Initialization
When the application starts for the first time, Lampray automatically creates all required tables and indexes. This is powered by Hibernate's ddl-auto feature combined with Flyway-style migration scripts for production-safe schema evolution.
Build Pipeline and Distribution
Lampray's Gradle build pipeline is designed to produce three artifact types depending on the deployment target.
Standard JAR
./gradlew build
This produces an executable JAR in lampray-web/build/libs/. It includes an embedded Tomcat server and all dependencies.
Distribution Package
./gradlew package
This creates a distribution tarball (lampray-{version}-dist.tar.gz) that includes:
- The base JAR file
- Startup scripts (
bin/lampray start|stop|status) - Default configuration templates
- A
conf/directory for externalized configuration
The startup script handles JVM options, classpath construction, and PID file management — making it suitable for production environments without additional tooling.
JAVA_OPTS="-Xmx1024m -Xms64m" bin/lampray start
Docker Image
./gradlew buildImage
This produces a Docker image tagged lampray:{version} using the project's Containerfile. The image is optimized for small size and fast startup.
docker run \
-d \
-p 5100:5100 \
-p 5101:5101 \
--network lampray \
-v /path/to/conf:/app/lampray/conf \
--name lampray lampray:{version}
Configuration Architecture
Lampray uses TOML for its configuration file format. TOML was chosen over YAML and JSON for its clarity and strict typing — it's harder to make ambiguous configuration errors.
The configuration system supports:
- Automatic file resolution — Looks for
lampray.tomlin the current directory, then falls back toconf/lampray.tomlrelative to the working directory. - Command-line override — Use
--configor-cto specify an explicit path. - Environment variable passthrough —
JAVA_OPTSis respected for JVM-level tuning.
bin/lampray start --config /etc/lampray/production.toml
Security Architecture
Lampray uses Spring Security as its authentication and authorization framework. The lampray-iam module handles:
- User registration and authentication — Session-based auth with JWT support for API access
- Role-based access control — Roles map to granular permissions on content operations
- Password hashing — BCrypt with configurable strength
- Session management — Concurrent session control and timeout configuration
The security configuration in lampray-web wires everything together declaratively:
@Configuration
@EnableWebSecurity
class SecurityConfig(
private val userDetailsService: UserDetailsService
) {
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.authorizeHttpRequests {
it.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
}
.formLogin { /* ... */ }
.logout { /* ... */ }
return http.build()
}
}
Content Management System
The content module (lampray-content) is the heart of the platform. It provides:
- Article CRUD — Create, read, update, and delete articles with markdown rendering
- Tagging and categorization — Flexible taxonomy system for organizing content
- Pagination and search — Server-side pagination with full-text search support
- Media attachments — File upload integrated with
lampray-filefor storage abstraction
Articles are stored with both raw markdown and rendered HTML, allowing the API to serve either format depending on the consuming client.
Frontend Architecture
The Vue 3 frontend (lampray-frontend) is built with the Composition API and Naive UI component library. Key architectural decisions include:
- Pinia for state management — Lightweight, TypeScript-native store pattern
- Vue Router — Client-side routing with lazy-loaded route components
- Axios HTTP client — Centralized API client with interceptors for auth token management
- Vite — Fast HMR during development, optimized production builds
The frontend communicates with the backend through a RESTful API designed with consistent resource naming and standardized error responses.
Getting Started
Ready to try Lampray? Here's the quickest path from zero to running:
Prerequisites
- Java 17+
- Gradle (or use the included Gradle wrapper)
Clone and Build
git clone https://github.com/roll-w/lampray.git
cd lampray
./gradlew assemble -x test
Configure
Create a lampray.toml file in the project root:
[database]
type = "sqlite"
target = "file:./data/lampray.db"
name = "lampray"
username = ""
password = ""
Run
java -jar lampray-web/build/libs/lampray-web-*.jar start
The application starts on port 5100 by default. Open http://localhost:5100 in your browser.
What's Next
Lampray is under active development. The current roadmap includes:
- Plugin system — Extend functionality through a modular plugin API
- Multi-tenancy — Run multiple sites from a single instance
- Webhook support — Integrate with external services through event-driven webhooks
- Rich text editor — In-browser WYSIWYG editing alongside markdown
- Performance optimizations — Caching layer, CDN integration, and query optimization
The project is fully open source under the Apache License 2.0. Contributions, bug reports, and feature requests are welcome on GitHub.