A full-stack code snippet manager built with Spring Boot 3 and Vanilla JS. Store, search, and organize reusable code snippets across multiple programming languages — accessible instantly from any browser, with zero external infrastructure.
DevVault is a personal snippet library. You can:
- Save code snippets with a title, description, language, and tags
- Search across title, tags, and language in real time (debounced input)
- Filter by programming language from a dynamically populated dropdown
- View snippets with syntax highlighting and copy them to the clipboard in one click
- Create, edit, and delete snippets through the web interface or directly via the REST API
Eight sample snippets (Scala/Spark, Java/Spring, Python, SQL, Bash) are pre-loaded on every startup.
| Choice | Alternative | Reason |
|---|---|---|
| Spring Boot 3 | Quarkus, Micronaut | De-facto enterprise standard, extensive ecosystem, immediately recognizable by any Java recruiter. |
| Java 17 | Java 21, Java 11 | LTS release, stable, widely available in enterprise CI/CD pipelines. |
| H2 in-memory | PostgreSQL, SQLite | Zero setup: the database starts with the app and is pre-seeded from data.sql. Anyone can clone and run in seconds without installing a database. Data resets on every restart, keeping demos clean. |
| Maven | Gradle | Standard in enterprise Java. pom.xml is explicit and easy for teams to audit. Spring Initializr defaults to Maven. |
| Vanilla JS + HTML | React, Vue, Angular | Demonstrates that a clean, functional SPA doesn't require a framework at this complexity level. No build step, no node_modules, no bundler — the entire frontend is one file. |
| Spring Data JPA | JDBC Template, MyBatis | Repository abstraction removes boilerplate CRUD; custom JPQL queries are still possible when needed. |
Browser (index.html)
│ fetch() calls
▼
┌─────────────────────────────────────────────┐
│ Spring Boot 3 / Embedded Tomcat (:8080) │
│ │
│ SnippetController (@RestController) │
│ │ delegates to │
│ SnippetService (@Service) │
│ │ delegates to │
│ SnippetRepository (@Repository) │
│ │ Hibernate ORM │
│ H2 In-Memory DB (devvaultdb) │
└─────────────────────────────────────────────┘
Layer responsibilities:
- Controller — HTTP only: parses path variables, query params, request bodies, returns correct status codes. No business logic.
- Service — business rules: guards against null ids, prevents client-supplied ids on create, owns transaction boundaries (
@Transactional). - Repository — data access: inherits CRUD from
JpaRepository, adds two custom JPQL queries (full-text search, distinct language list). - GlobalExceptionHandler —
@RestControllerAdvicethat intercepts all exceptions and returns a uniform JSON error payload, keeping error handling out of individual controllers.
Data model:
Snippet
├── id Long PK, auto-generated
├── titolo String(100) required
├── descrizione String(300) optional
├── codice TEXT required
├── linguaggio String(50) e.g. "Java", "Python"
├── tag String(500) comma-separated, e.g. "spark,join"
├── createdAt LocalDateTime set on @PrePersist, never updated
└── updatedAt LocalDateTime refreshed on @PreUpdate
Snippet.java uses @PrePersist / @PreUpdate lifecycle callbacks to manage timestamps automatically — no caller ever sets them. Bean Validation annotations (@NotBlank, @Size) live alongside the field declarations so constraints are enforced both at the API layer (via @Valid) and at the persistence layer.
SnippetRepository.java adds two hand-written JPQL queries on top of the Spring Data auto-generated CRUD. The search query uses LOWER(CONCAT('%', :q, '%')) for case-insensitive partial matching across title, tags, and language. Empty strings short-circuit to return all rows, so the same query handles both "search" and "list all" scenarios.
SnippetService.java owns the @Transactional boundaries (read-only by default, explicit for mutations). The create method forces snippet.setId(null) so a client can never inject a custom id. The update method loads the existing entity first — throwing ResourceNotFoundException if absent — and only transfers the mutable fields, protecting id and createdAt.
SnippetController.java is intentionally thin: it maps HTTP verbs and delegates everything to the service. The /languages mapping is declared before /{id} because Spring MVC resolves literal path segments before path variables — without this order, GET /api/snippets/languages would be misinterpreted as a request for a snippet with id "languages".
Frontend (index.html) is a single-file SPA. State is a plain JS object (state.view, state.selectedId). All seven REST calls are wrapped in an api object. Search triggers loadSnippets() after 300 ms of inactivity to avoid hammering the API on every keystroke. Syntax highlighting is done with a regex-based highlightCode() function — no external library needed.
| Method | Endpoint | Params | Description |
|---|---|---|---|
GET |
/api/snippets |
— | All snippets, newest first |
GET |
/api/snippets/{id} |
id |
One snippet or 404 |
GET |
/api/snippets/search |
q, language (optional) |
Full-text + language filter |
GET |
/api/snippets/languages |
— | Distinct language list |
POST |
/api/snippets |
JSON body | Create a snippet, returns 201 |
PUT |
/api/snippets/{id} |
id + JSON body |
Update a snippet |
DELETE |
/api/snippets/{id} |
id |
Delete a snippet, returns 204 |
Request body (POST / PUT):
| Field | Type | Required | Constraints |
|---|---|---|---|
titolo |
string | yes | max 100 chars |
descrizione |
string | no | max 300 chars |
codice |
string | yes | no limit |
linguaggio |
string | no | max 50 chars |
tag |
string | no | max 500 chars, comma-separated |
Sample create request:
POST /api/snippets
Content-Type: application/json
{
"titolo": "Hello World in Go",
"descrizione": "Minimal Go program",
"codice": "package main\n\nimport \"fmt\"\n\nfunc main() {\n fmt.Println(\"Hello, World!\")\n}",
"linguaggio": "Go",
"tag": "go,hello-world"
}Error response format (all 4xx / 5xx):
{
"status": 404,
"message": "Snippet non trovato con id: 99",
"errors": null,
"timestamp": "2024-06-01T10:31:00"
}Prerequisites: Java 17+, Maven 3.6+. No database installation needed.
cd devvault
mvn spring-boot:runThe app starts in ~5 seconds. Open http://localhost:8080.
Access points:
| URL | Description |
|---|---|
http://localhost:8080 |
Web UI |
http://localhost:8080/api/snippets |
REST API |
http://localhost:8080/h2-console |
H2 Database Console (dev only) |
H2 Console login:
| Field | Value |
|---|---|
| JDBC URL | jdbc:h2:mem:devvaultdb |
| Username | sa |
| Password | (leave empty) |
Build a self-contained JAR:
mvn package
java -jar target/devvault-1.0.0.jardevvault/
├── pom.xml
└── src/main/
├── java/com/devvault/
│ ├── DevVaultApplication.java # entry point
│ ├── controller/
│ │ ├── SnippetController.java # REST endpoints
│ │ └── GlobalExceptionHandler.java # centralized error handling
│ ├── dto/
│ │ └── ErrorResponse.java # uniform error JSON structure
│ ├── exception/
│ │ └── ResourceNotFoundException.java
│ ├── model/
│ │ └── Snippet.java # JPA entity
│ ├── repository/
│ │ └── SnippetRepository.java # Spring Data + custom JPQL
│ └── service/
│ └── SnippetService.java # business logic + transactions
└── resources/
├── application.properties
├── data.sql # 8 sample snippets
└── static/
└── index.html # single-page frontend