Here's a complete project structure for a fullstack application using Spring Boot for the backend and Svelte for the frontend, implementing CRUD operations.
Project Structure

Backend Implementation (Spring Boot)
1. Item Entity (backend/src/main/java/com/example/demo/model/Item.java
)
package com.example.demo.model;
import jakarta.persistence.*;
@Entity
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
// Constructors, getters, and setters
public Item() {}
public Item(String name, String description) {
this.name = name;
this.description = description;
}
// Getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
}
2. Repository (backend/src/main/java/com/example/demo/repository/ItemRepository.java
)
package com.example.demo.repository;
import com.example.demo.model.Item;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ItemRepository extends JpaRepository<Item, Long> {
}
3. Service Layer (backend/src/main/java/com/example/demo/service/ItemService.java
)
package com.example.demo.service;
import com.example.demo.model.Item;
import com.example.demo.repository.ItemRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ItemService {
@Autowired
private ItemRepository itemRepository;
public List<Item> getAllItems() {
return itemRepository.findAll();
}
public Item getItemById(Long id) {
return itemRepository.findById(id).orElse(null);
}
public Item createItem(Item item) {
return itemRepository.save(item);
}
public Item updateItem(Long id, Item itemDetails) {
Item item = itemRepository.findById(id).orElse(null);
if (item != null) {
item.setName(itemDetails.getName());
item.setDescription(itemDetails.getDescription());
return itemRepository.save(item);
}
return null;
}
public void deleteItem(Long id) {
itemRepository.deleteById(id);
}
}
4. REST Controller (backend/src/main/java/com/example/demo/controller/ItemController.java
)
package com.example.demo.controller;
import com.example.demo.model.Item;
import com.example.demo.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/items")
@CrossOrigin(origins = "http://localhost:5173") // Allow Svelte dev server
public class ItemController {
@Autowired
private ItemService itemService;
@GetMapping
public List<Item> getAllItems() {
return itemService.getAllItems();
}
@GetMapping("/{id}")
public Item getItemById(@PathVariable Long id) {
return itemService.getItemById(id);
}
@PostMapping
public Item createItem(@RequestBody Item item) {
return itemService.createItem(item);
}
@PutMapping("/{id}")
public Item updateItem(@PathVariable Long id, @RequestBody Item itemDetails) {
return itemService.updateItem(id, itemDetails);
}
@DeleteMapping("/{id}")
public void deleteItem(@PathVariable Long id) {
itemService.deleteItem(id);
}
}
5. Application Properties (backend/src/main/resources/application.properties
)
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
Frontend Implementation (Svelte)
1. Main App Component (frontend/src/App.svelte
)
<script>
import ItemList from './lib/components/ItemList.svelte';
import ItemForm from './lib/components/ItemForm.svelte';
import Notification from './lib/components/Notification.svelte';
let notification = { show: false, message: '', type: '' };
function showNotification(message, type = 'success') {
notification = { show: true, message, type };
setTimeout(() => notification.show = false, 3000);
}
</script>
<div class="container">
<h1>Items Management</h1>
<Notification bind:notification />
<div class="grid">
<div>
<h2>Add/Edit Item</h2>
<ItemForm on:notify={showNotification} />
</div>
<div>
<h2>Items List</h2>
<ItemList on:notify={showNotification} />
</div>
</div>
</div>
<style>
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.grid {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 20px;
}
h1 {
color: #333;
margin-bottom: 30px;
}
h2 {
color: #444;
margin-bottom: 15px;
}
</style>
2. Item Service (frontend/src/lib/services/itemService.js
)
const API_URL = 'http://localhost:8080/api/items';
export const getItems = async () => {
const response = await fetch(API_URL);
return await response.json();
};
export const getItem = async (id) => {
const response = await fetch(`${API_URL}/${id}`);
return await response.json();
};
export const createItem = async (item) => {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(item),
});
return await response.json();
};
export const updateItem = async (id, item) => {
const response = await fetch(`${API_URL}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(item),
});
return await response.json();
};
export const deleteItem = async (id) => {
await fetch(`${API_URL}/${id}`, {
method: 'DELETE',
});
};
3. Item Store (frontend/src/lib/stores/items.js
)
import { writable } from 'svelte/store';
import { getItems, createItem, updateItem, deleteItem } from '../services/itemService';
function createItemsStore() {
const { subscribe, set, update } = writable([]);
return {
subscribe,
loadItems: async () => {
const items = await getItems();
set(items);
},
addItem: async (item) => {
const newItem = await createItem(item);
update(items => [...items, newItem]);
return newItem;
},
updateItem: async (id, item) => {
const updatedItem = await updateItem(id, item);
update(items => items.map(i => i.id === id ? updatedItem : i));
return updatedItem;
},
removeItem: async (id) => {
await deleteItem(id);
update(items => items.filter(i => i.id !== id));
}
};
}
export const items = createItemsStore();
4. Item List Component (frontend/src/lib/components/ItemList.svelte
)
<script>
import { items } from '../stores/items';
import { onMount } from 'svelte';
let selectedId = null;
onMount(() => {
items.loadItems();
});
function handleEdit(id) {
selectedId = id;
dispatch('edit', id);
}
function handleDelete(id) {
if (confirm('Are you sure you want to delete this item?')) {
items.removeItem(id)
.then(() => dispatch('notify', { message: 'Item deleted successfully', type: 'success' }))
.catch(() => dispatch('notify', { message: 'Error deleting item', type: 'error' }));
}
}
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
</script>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each $items as item (item.id)}
<tr class:selected={item.id === selectedId}>
<td>{item.name}</td>
<td>{item.description}</td>
<td>
<button on:click={() => handleEdit(item.id)}>Edit</button>
<button on:click={() => handleDelete(item.id)}>Delete</button>
</td>
</tr>
{/each}
</tbody>
</table>
<style>
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #f2f2f2;
}
tr.selected {
background-color: #e6f7ff;
}
button {
margin-right: 5px;
padding: 5px 10px;
cursor: pointer;
}
</style>
5. Item Form Component (frontend/src/lib/components/ItemForm.svelte
)
<script>
import { items } from '../stores/items';
let id = null;
let name = '';
let description = '';
function handleSubmit() {
const item = { name, description };
if (id) {
items.updateItem(id, item)
.then(() => {
dispatch('notify', { message: 'Item updated successfully', type: 'success' });
resetForm();
})
.catch(() => dispatch('notify', { message: 'Error updating item', type: 'error' }));
} else {
items.addItem(item)
.then(() => {
dispatch('notify', { message: 'Item created successfully', type: 'success' });
resetForm();
})
.catch(() => dispatch('notify', { message: 'Error creating item', type: 'error' }));
}
}
function resetForm() {
id = null;
name = '';
description = '';
}
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
$: if ($editingId) {
const item = $items.find(i => i.id === $editingId);
if (item) {
id = item.id;
name = item.name;
description = item.description;
}
} else {
resetForm();
}
export let editingId = null;
$: $editingId = editingId;
</script>
<form on:submit|preventDefault={handleSubmit}>
<input type="hidden" bind:value={id} />
<div class="form-group">
<label for="name">Name</label>
<input id="name" type="text" bind:value={name} required />
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea id="description" bind:value={description}></textarea>
</div>
<div class="form-actions">
<button type="submit">{id ? 'Update' : 'Create'}</button>
{#if id}
<button type="button" on:click={resetForm}>Cancel</button>
{/if}
</div>
</form>
<style>
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
textarea {
min-height: 100px;
}
.form-actions {
margin-top: 20px;
}
button {
padding: 8px 15px;
margin-right: 10px;
cursor: pointer;
}
</style>
6. Notification Component (frontend/src/lib/components/Notification.svelte
)
<script>
export let notification;
</script>
{#if notification.show}
<div class="notification {notification.type}">
{notification.message}
</div>
{/if}
<style>
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 4px;
color: white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
animation: fadeIn 0.3s;
}
.success {
background-color: #4CAF50;
}
.error {
background-color: #F44336;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
Setup Instructions
Backend Setup
- Navigate to the
backend
directory - Run the Spring Boot application:bashCopy./mvnw spring-boot:run
Frontend Setup
- Navigate to the
frontend
directory - Install dependencies:bashCopynpm install
- Start the development server:bashCopynpm run dev
Key Features
- Full CRUD Operations:
- Create, Read, Update, and Delete items
- Real-time updates to the UI when data changes
- Frontend:
- Svelte components with reactive stores
- Form validation
- Notifications for user feedback
- Clean, responsive UI
- Backend:
- RESTful API with Spring Boot
- Spring Data JPA for database operations
- H2 in-memory database for development
- CORS configured for development
This example provides a solid foundation that you can extend with additional features like authentication, pagination, or more complex data relationships.