Wednesday, July 9, 2025

Building a Java Swing Desktop Version of the Spring Boot + React To-Do List App

 If you haven’t followed the earlier guides, the app consists of a backend built with Spring Boot, a frontend built with React.js, and a PostgreSQL database. You can check them out here:

Why a Desktop Version?

Sometimes it's convenient to have a native GUI app that connects to the same backend. This Java Swing version uses the same PostgreSQL database as the web app, providing a consistent experience regardless of platform.


Project Structure

  • Language: Java (Swing GUI)

  • Database: PostgreSQL (same as backend)

  • Access: Uses JDBC to connect directly

  • Features:

    • View all tasks

    • Mark tasks as complete

    • Delete tasks

    • Add new tasks


Dependencies

  1. PostgreSQL JDBC Driver

    Download the .jar from here, for example:

    • postgresql-42.2.29.jre7.jar

    Save it in the same folder as your Swing project.

  2. Java 17+

    Ensure you're using a compatible Java version (same as Spring Boot).


Directory Layout

Assuming your project is in C:\todo_app\desktop, the folder should contain:


Database Table Format

The table should already be created by your Spring Boot app via JPA: 

1
2
3
4
5
CREATE TABLE todo (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255),
    completed BOOLEAN
);

If it's empty, add sample tasks from the web app or using psql.


Source Code: TodoApp.java

Here's the complete Swing code that mirrors the behavior and look of the React frontend:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.awt.font.TextAttribute;

public class TodoApp extends JFrame {
    private Connection conn;
    private JPanel taskPanel;
    private JTextField inputField;

    public TodoApp() {
        setTitle("To-Do List");
        setSize(400, 600);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setLayout(new BorderLayout());

        inputField = new JTextField();
        JButton addButton = new JButton("Add");

        JPanel inputPanel = new JPanel(new BorderLayout());
        inputPanel.add(inputField, BorderLayout.CENTER);
        inputPanel.add(addButton, BorderLayout.EAST);

        taskPanel = new JPanel();
        taskPanel.setLayout(new BoxLayout(taskPanel, BoxLayout.Y_AXIS));
        JScrollPane scrollPane = new JScrollPane(taskPanel);

        add(inputPanel, BorderLayout.NORTH);
        add(scrollPane, BorderLayout.CENTER);

        try {
            conn = DriverManager.getConnection("jdbc:postgresql://localhost:5432/todo_db", <user>, <paword>);
        } catch (SQLException e) {
            JOptionPane.showMessageDialog(this, "Database connection failed: " + e.getMessage());
            System.exit(1);
        }

        loadTasks();

        addButton.addActionListener(e -> addTask());
    }

    private void loadTasks() {
        taskPanel.removeAll();
        try {
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT * FROM todo");

            while (rs.next()) {
                long id = rs.getLong("id");
                String title = rs.getString("title");
                boolean completed = rs.getBoolean("completed");

               
		JPanel panel = new JPanel(new BorderLayout());
		panel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 40)); // Set row height to 40px
		panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // Optional spacing
                JCheckBox checkBox = new JCheckBox();
                checkBox.setSelected(completed);

                JLabel label = new JLabel(title);
                if (completed) {
                    Map<TextAttribute, Object> attributes = new HashMap<>(label.getFont().getAttributes());
                    attributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
                    label.setFont(label.getFont().deriveFont(attributes));
                }

                checkBox.addActionListener(e -> toggleComplete(id, checkBox.isSelected()));

                JButton deleteBtn = new JButton("X");
                deleteBtn.addActionListener(e -> deleteTask(id));

                panel.add(checkBox, BorderLayout.WEST);
                panel.add(label, BorderLayout.CENTER);
                panel.add(deleteBtn, BorderLayout.EAST);

                taskPanel.add(panel);
            }

            taskPanel.revalidate();
            taskPanel.repaint();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void addTask() {
        String title = inputField.getText().trim();
        if (title.isEmpty()) return;

        try {
            PreparedStatement ps = conn.prepareStatement("INSERT INTO todo (title, completed) VALUES (?, false)");
            ps.setString(1, title);
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        inputField.setText("");
        loadTasks();
    }

    private void deleteTask(long id) {
        try {
            PreparedStatement ps = conn.prepareStatement("DELETE FROM todo WHERE id = ?");
            ps.setLong(1, id);
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        loadTasks();
    }

    private void toggleComplete(long id, boolean completed) {
        try {
            PreparedStatement ps = conn.prepareStatement("UPDATE todo SET completed = ? WHERE id = ?");
            ps.setBoolean(1, completed);
            ps.setLong(2, id);
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        loadTasks();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            new TodoApp().setVisible(true);
        });
    }
}

Building and Running

Compile:

javac -cp ".;postgresql-42.2.29.jre7.jar" TodoApp.java

Run:

java -cp ".;postgresql-42.2.29.jre7.jar" TodoApp

 

Final Output

The desktop app should:

  • Display tasks just like the React UI

  • Use checkboxes for status

  • Show an "X" button to delete

  • Allow typing and adding new tasks at the bottom

Example:



No comments:

Post a Comment

Deploying a React Frontend to Railway: A Complete Guide (To-Do App)

 If you've already deployed your Java Spring Boot backend to Railway, congratulations — the hardest part is done! ๐ŸŽ‰ Now it’s time to ...