CODE WITH SIBIN

Solving Real Problems with Real Code


Spring Boot + Svelte CRUD Fullstack Tutorial: Complete Guide with Code Examples

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

  1. Navigate to the backend directory
  2. Run the Spring Boot application:bashCopy./mvnw spring-boot:run

Frontend Setup

  1. Navigate to the frontend directory
  2. Install dependencies:bashCopynpm install
  3. Start the development server:bashCopynpm run dev

Key Features

  1. Full CRUD Operations:
    • Create, Read, Update, and Delete items
    • Real-time updates to the UI when data changes
  2. Frontend:
    • Svelte components with reactive stores
    • Form validation
    • Notifications for user feedback
    • Clean, responsive UI
  3. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *