Spring Boot සහ React එකට පාවිච්චි කරමු - Full-Stack Application හදමු! | SC Guide

Spring Boot සහ React එකට පාවිච්චි කරමු - Full-Stack Application හදමු! | SC Guide

ඉස්සූ... කොහොමද යාලුවනේ?

දැන් කාලේ වෙබ් ඩිවලොප්මන්ට් කියන්නේ නිකන්ම නිකන් HTML, CSS වලින් විතරක් ගොඩ දාන්න පුළුවන් වැඩක් නෙවෙයි. ටිකක් සංකීර්ණ, හැබැයි හරිම බලගතු (powerful) පද්ධති හදන්න අපිට Full-Stack දැනුමක් ඕනේ. ඉතින් ඔයාලා දන්නවා ඇති Backend එකට Java එක්ක Spring Boot කියන්නේ පට්ට Option එකක් කියලා. Frontend එකටත් React වගේ Framework එකක් ගත්තම වැඩේ මාරම විදියට පහසු වෙනවා. හැබැයි මේ දෙක එකට සම්බන්ධ කරන්නේ කොහොමද, ඒක කොච්චර ලේසිද කියලා සමහර අයට පොඩි ප්‍රශ්නයක් ඇති නේද? "අයියා, මේ දෙකම එකට කොහොමද වැඩ කරන්නේ?" කියලා අහන පිරිසකුත් ඉන්නවා.

අද අපි බලමු Spring Boot Backend එකක් එක්ක React Frontend එකක් කොහොමද සුපිරියටම Integration කරලා, නියම Full-Stack Application එකක් හදන්නේ කියලා. මේක නිකන් කතාවක් විතරක් නෙවෙයි, අපි පොඩි Project එකක් හදලාම වැඩේ බලමු. ඔයාලට මේක බැලුවට පස්සේ බය නැතුව ඔයාලගේම Project වලට මේ දෙක යොදාගන්න පුළුවන් වෙයි කියලා මට විශ්වාසයි. එහෙනම්, අපි වැඩේට බහිමු!

ඇයි අපි Spring Boot සහ React එකට පාවිච්චි කරන්නේ? (Why use Spring Boot and React together?)

හරි, මුලින්ම බලමු ඇයි මේ දෙකම එකට හොඳම Combinations වලින් එකක් වෙන්නේ කියලා.

  1. වෙන වෙනම සංවර්ධනය (Separate Development): Spring Boot Backend එක API (Application Programming Interface) එකක් විදියට වැඩ කරනවා. ඒ කියන්නේ ඒක දත්ත (data) Manage කරනවා, Business logic එක හසුරුවනවා. React Frontend එක User Interface (UI) එක විතරයි හසුරුවන්නේ. මේ වෙන්වීම නිසා Backend Team එකටයි Frontend Team එකටයි එකිනෙකාට බාධා නැතුව වැඩ කරන්න පුළුවන්. වැඩේ වේගවත් වෙනවා, නේද?

  2. පරිමාණය කිරීමේ හැකියාව (Scalability): මේ දෙක වෙන් වෙලා තියෙන නිසා, අපිට Backend එක වෙනම Scale කරන්න පුළුවන්, Frontend එක වෙනම Scale කරන්න පුළුවන්. උදාහරණයක් විදියට, Usersලා ගොඩක් වැඩි වුණොත් Backend Servers ගාන වැඩි කරන්න පුළුවන්, Frontend එක CDN (Content Delivery Network) එකක් හරහා serve කරන්න පුළුවන්. මේක ලොකු Project වලට අත්‍යවශ්‍ය දෙයක්.

  3. සම්පූර්ණ UI (Rich User Interface): React කියන්නේ Components වලින් ගොඩනැගෙන UI එකක්. මේකෙන් අපිට අලංකාර, Interactive සහ වේගවත් UI එකක් හදන්න පුළුවන්. Single Page Application (SPA) හැදීමේදී React තමා හොඳම විසඳුමක්. Spring Boot එකෙන් අපිට පුළුවන් මේකට අවශ්‍ය stable, secure Backend එක සපයන්න.

  4. විශාල ප්‍රජාවක් (Vast Communities): Spring Boot සහ React කියන්නේ ලෝකයේ පුරාම මිලියන ගණනක් Developersලා පාවිච්චි කරන Technologies. ඒ නිසා ඔයාලට මොකක් හරි ප්‍රශ්නයක් ආවොත්, Stack Overflow එකේ හරි වෙනත් Forum එකක හරි උදව් හොයාගන්න එක ලේසියි. Resourses, Tutorials, Libraries ගොඩක් හොයාගන්න පුළුවන්.

  5. කාර්ය සාධනය (Performance): Spring Boot වලට පුළුවන් Requests ගොඩක් එකපාර Handle කරන්න. React වලට පුළුවන් UI එක වේගවත්ව Render කරන්න. මේ දෙක එකතු වුණාම User Experience එක සුපිරියටම වැඩි වෙනවා.

හරි, දැන් ඔයාලට තේරෙනවා ඇති ඇයි මේ දෙක මේ තරම් ජනප්‍රියයි කියලා. එහෙනම් අපි වැඩේට බහිමු.

Spring Boot Backend එක හදමු – පියවරෙන් පියවර (Let's build the Spring Boot Backend – Step by Step)

අපි හදන්නේ පොඩි To-Do List Application එකක් වගේ දෙයක්. ඒකේ Tasks එකතු කරන්න, බලන්න, Update කරන්න, Delete කරන්න පුළුවන් වෙයි.

1. Project එක ආරම්භ කරමු (Start the Project)

මුලින්ම Spring Initializr එකට යන්න. මේ Details ටික දාගන්න:

  • Project: Maven Project
  • Language: Java
  • Spring Boot: 3.2.x (latest stable version)
  • Group: com.example
  • Artifact: task-manager-backend
  • Packaging: Jar
  • Java: 17 (හෝ ඔබ කැමති Version එකක්)

Dependencies විදියට මේ ටික එකතු කරගන්න:

  • Spring Web
  • Spring Data JPA
  • H2 Database (Development වලට ලේසියි, Production වලට MySQL/PostgreSQL වගේ එකක් පාවිච්චි කරන්න)
  • Lombok (Optional, Code Reduce කරගන්න)

Generate කරලා Project එක Download කරගෙන Extract කරලා Intellij IDEA, VS Code වගේ IDE එකක Open කරගන්න.

2. Data Source සහ JPA Configuration

src/main/resources/application.properties file එකට මේ ටික එකතු කරගන්න. H2 Database එකටයි JPA වලටයි Config කරන්න ඕනේ මේක.

spring.datasource.url=jdbc:h2:mem:taskdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update

3. Entity එක හදමු (Create an Entity)

com.example.taskmanagerbackend.model package එකක් හදලා ඒක ඇතුලේ Task.java කියලා Class එකක් හදන්න.

package com.example.taskmanagerbackend.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;

@Entity
@Data // From Lombok, generates getters, setters, etc.
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String description;
    private boolean completed;

    // Constructors (if not using Lombok's @NoArgsConstructor, @AllArgsConstructor)
    // Getters and Setters (if not using Lombok's @Data)
}

4. Repository එක හදමු (Create a Repository)

com.example.taskmanagerbackend.repository package එකක් හදලා ඒක ඇතුලේ TaskRepository.java කියලා Interface එකක් හදන්න.

package com.example.taskmanagerbackend.repository;

import com.example.taskmanagerbackend.model.Task;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TaskRepository extends JpaRepository<Task, Long> {
}

5. Service එක හදමු (Create a Service)

com.example.taskmanagerbackend.service package එකක් හදලා ඒක ඇතුලේ TaskService.java කියලා Class එකක් හදන්න. මේකෙන් තමයි Business Logic එක Handle කරන්නේ.

package com.example.taskmanagerbackend.service;

import com.example.taskmanagerbackend.model.Task;
import com.example.taskmanagerbackend.repository.TaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class TaskService {

    @Autowired
    private TaskRepository taskRepository;

    public List<Task> getAllTasks() {
        return taskRepository.findAll();
    }

    public Optional<Task> getTaskById(Long id) {
        return taskRepository.findById(id);
    }

    public Task createTask(Task task) {
        return taskRepository.save(task);
    }

    public Task updateTask(Long id, Task taskDetails) {
        Task task = taskRepository.findById(id).orElseThrow(() -> new RuntimeException("Task not found"));
        task.setTitle(taskDetails.getTitle());
        task.setDescription(taskDetails.getDescription());
        task.setCompleted(taskDetails.isCompleted());
        return taskRepository.save(task);
    }

    public void deleteTask(Long id) {
        taskRepository.deleteById(id);
    }
}

6. REST Controller එක හදමු (Create a REST Controller)

com.example.taskmanagerbackend.controller package එකක් හදලා ඒක ඇතුලේ TaskController.java කියලා Class එකක් හදන්න. මේකෙන් තමයි API Endpoints හදන්නේ.

package com.example.taskmanagerbackend.controller;

import com.example.taskmanagerbackend.model.Task;
import com.example.taskmanagerbackend.service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/tasks")
@CrossOrigin(origins = "http://localhost:3000") // React app runs on port 3000
public class TaskController {

    @Autowired
    private TaskService taskService;

    @GetMapping
    public List<Task> getAllTasks() {
        return taskService.getAllTasks();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Task> getTaskById(@PathVariable Long id) {
        return taskService.getTaskById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<Task> createTask(@RequestBody Task task) {
        return new ResponseEntity<>(taskService.createTask(task), HttpStatus.CREATED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Task> updateTask(@PathVariable Long id, @RequestBody Task task) {
        return new ResponseEntity<>(taskService.updateTask(id, task), HttpStatus.OK);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteTask(@PathVariable Long id) {
        taskService.deleteTask(id);
        return ResponseEntity.noContent().build();
    }
}

මතක තියාගන්න: @CrossOrigin(origins = "http://localhost:3000") කියන Annotation එක දාන්න අමතක කරන්න එපා. නැත්නම් CORS (Cross-Origin Resource Sharing) Error එකක් එයි. මොකද, ඔයාගේ React App එකයි Spring Boot Backend එකයි වෙන වෙනම Ports වල (http://localhost:3000 සහ http://localhost:8080) Run වෙන නිසා Browser එකෙන් Security වලට අදාළව මේක Block කරනවා. ඒ නිසා අපි මේකට අවසර දෙන්න ඕනේ.

දැන් Spring Boot Application එක Run කරලා බලන්න. http://localhost:8080/api/tasks එකට ගිහින් බලන්න. මුලින්ම Empty Array එකක් එයි. Postman වගේ Tool එකකින් POST Request එකක් දාලා Task එකක් Add කරලා බලන්නත් පුළුවන්.

React Frontend එක හදමු – පියවරෙන් පියවර (Let's build the React Frontend – Step by Step)

දැන් අපි Frontend එකට බහිමු.

1. React Project එක හදමු (Create a React Project)

Backend Project එක තියෙන Folder එකෙන්ම Command Prompt/Terminal එක Open කරලා මේ Command එක Run කරන්න:

npx create-react-app frontend
cd frontend
npm start

මේකෙන් frontend කියලා අලුත් Folder එකක් හැදිලා ඒක ඇතුලේ React Project එකක් Create වෙනවා. npm start කරාම Browser එකේ http://localhost:3000 එකේ React App එක Open වෙයි.

2. Components ගොඩනගමු (Build Components)

frontend/src Folder එක ඇතුලේ components කියලා අලුත් Folder එකක් හදමු.

TaskList.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';

function TaskList() {
  const [tasks, setTasks] = useState([]);

  useEffect(() => {
    fetchTasks();
  }, []);

  const fetchTasks = async () => {
    try {
      const response = await axios.get('http://localhost:8080/api/tasks');
      setTasks(response.data);
    } catch (error) {
      console.error('Error fetching tasks:', error);
    }
  };

  const handleDelete = async (id) => {
    try {
      await axios.delete(`http://localhost:8080/api/tasks/${id}`);
      fetchTasks(); // Refresh the list after deleting
    } catch (error) {
      console.error('Error deleting task:', error);
    }
  };

  const handleToggleComplete = async (id, currentStatus) => {
    try {
      const taskToUpdate = tasks.find(task => task.id === id);
      if (taskToUpdate) {
        await axios.put(`http://localhost:8080/api/tasks/${id}`, { 
          ...taskToUpdate, 
          completed: !currentStatus 
        });
        fetchTasks(); // Refresh the list
      }
    } catch (error) {
      console.error('Error toggling task status:', error);
    }
  };

  return (
    <div>
      <h2>Task List</h2>
      {tasks.length === 0 ? (
        <p>No tasks available. Add some!</p>
      ) : (
        <ul>
          {tasks.map(task => (
            <li key={task.id} style={{ textDecoration: task.completed ? 'line-through' : 'none' }}>
              {task.title} - {task.description}
              <button onClick={() => handleToggleComplete(task.id, task.completed)}>
                {task.completed ? 'Mark Incomplete' : 'Mark Complete'}
              </button>
              <button onClick={() => handleDelete(task.id)}>Delete</button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default TaskList;
TaskForm.js
import React, { useState } from 'react';
import axios from 'axios';

function TaskForm({ onTaskAdded }) {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await axios.post('http://localhost:8080/api/tasks', { title, description, completed: false });
      setTitle('');
      setDescription('');
      onTaskAdded(); // Notify parent to refresh task list
    } catch (error) {
      console.error('Error adding task:', error);
    }
  };

  return (
    <div>
      <h2>Add New Task</h2>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Task Title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          required
        />
        <input
          type="text"
          placeholder="Task Description"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
          required
        />
        <button type="submit">Add Task</button>
      </form>
    </div>
  );
}

export default TaskForm;

Note: මෙතනදී අපි Data Fetching වලට Axios Library එක පාවිච්චි කරනවා. ඒක `fetch` වලට වඩා ටිකක් පහසුයි. Axios Install කරගන්න:

npm install axios

3. App.js අංගසංගත කරමු (Update App.js)

frontend/src/App.js file එක මේ විදියට වෙනස් කරන්න:

import React, { useState } from 'react';
import TaskList from './components/TaskList';
import TaskForm from './components/TaskForm';
import './App.css'; // Or remove if not using CSS

function App() {
  const [refreshTasks, setRefreshTasks] = useState(false);

  const handleTaskAdded = () => {
    // This will trigger TaskList to re-fetch tasks
    setRefreshTasks(prev => !prev);
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>Spring Boot & React To-Do App</h1>
      </header>
      <main>
        <TaskForm onTaskAdded={handleTaskAdded} />
        {/* Key prop ensures re-rendering when refreshTasks changes */}
        <TaskList key={refreshTasks} />
      </main>
    </div>
  );
}

export default App;

TaskList component එක TaskForm එකෙන් අලුත් Task එකක් add කරාම refresh වෙන්න, අපි key={refreshTasks} කියන Trick එක පාවිච්චි කරනවා. refreshTasks State එක වෙනස් වෙන හැම වෙලාවකම TaskList component එක Re-mount වෙනවා. ඒ නිසා useEffect Hook එක නැවත Run වෙලා අලුත් Tasks ටික Fetch වෙනවා.

Backend සහ Frontend එකට සම්බන්ධ කරමු: API Integration (Connecting Backend and Frontend: API Integration)

හරි, දැන් ඔයාලා Backend එකයි Frontend එකයි වෙන වෙනම හදලා ඉවරයි. දැන් බලමු මේ දෙක එකට වැඩ කරන්නේ කොහොමද කියලා.

  1. CORS Configurations (වැදගත්ම දේ!): අපි Spring Boot TaskController එකේ @CrossOrigin(origins = "http://localhost:3000") කියලා දැම්මා නේද? මේකෙන් වෙන්නේ http://localhost:3000 කියන URL එකෙන් එන Requests වලට Backend එකට Access වෙන්න අවසර දෙන එක. නැත්නම් Browser එක Security වලට අදාළව මේ Requests Block කරනවා. Production වලදී මේක * (සියල්ලටම) විදියට දාන්නේ නැතුව, ඔයාගේ Frontend එක තියෙන Real Domain එක දාන්න ඕනේ.

  2. API Endpoints වලට Request කරන හැටි:

    • GET Requests: React වල TaskList.js එකේ අපි axios.get('http://localhost:8080/api/tasks') කියලා දැම්මා. මේකෙන් වෙන්නේ Spring Boot Backend එකේ /api/tasks කියන Endpoint එකට ගිහින් හැම Task එකක්ම Request කරලා ගන්න එක.
    • POST Requests: TaskForm.js එකේ අපි axios.post('http://localhost:8080/api/tasks', { title, description, completed: false }) කියලා දැම්මා. මේකෙන් අලුත් Task එකක් Backend එකට යවනවා.
    • PUT Requests: TaskList.js එකේ handleToggleComplete Function එක ඇතුලේ අපි axios.put(http://localhost:8080/api/tasks/${id}, { ...taskToUpdate, completed: !currentStatus }) කියන Code එක දැම්මා. මේකෙන් වෙන්නේ තියෙන Task එකක් Update කරන එක.
    • DELETE Requests: TaskList.js එකේ handleDelete Function එක ඇතුලේ axios.delete(http://localhost:8080/api/tasks/${id}) කියලා දැම්මා. මේකෙන් Backend එකෙන් Task එකක් Delete කරනවා.

  3. දෙකම එකට Run කරමු:

    1. මුලින්ම Spring Boot Backend Project එක (task-manager-backend) එක ඔයාගේ IDE එකෙන් Run කරන්න (main Class එක Run කරන්න). Console එකේ "Started TaskManagerBackendApplication" වගේ Message එකක් එයි. Backend එක Port 8080 එකේ Run වෙනවා.
    2. ඊළඟට, frontend Folder එකට Terminal එකෙන් ගිහින් (cd frontend) npm start Command එක Run කරන්න. React App එක Port 3000 එකේ Open වෙයි.

දැන් Browser එකේ http://localhost:3000 එකට ගිහින් බලන්න. ඔයාලට පුළුවන් Task එකතු කරන්න, Delete කරන්න, Complete/Incomplete කරන්න. Backend එකෙන් දත්ත Save වෙනවා, Frontend එකෙන් ඒක Display වෙනවා. සුපිරි වැඩේ නේද?

දැනට මේක Development Mode එකේ තියෙන්නේ. Production වලට Deploy කරනවා නම් Frontend එක Build කරලා (npm run build) ඒ Build Folder එක Spring Boot Application එකේ src/main/resources/static Folder එක ඇතුලට දාලා, එකම Jar File එකක් විදියට Deploy කරන්න පුළුවන්. ඒ වගේම වෙන වෙනම Server වල Deploy කරන්නත් පුළුවන්. ඒ ගැන අපි වෙන Post එකකින් කතා කරමු.

අවසානය (Conclusion)

ඉතින් යාලුවනේ, ඔයාලට දැන් තේරෙනවා ඇති Spring Boot සහ React එකට පාවිච්චි කරලා කොහොමද Powerful Full-Stack Web Applications හදන්නේ කියලා. මේක නිකන් Tool Sets දෙකක් විතරක් නෙවෙයි, මේ දෙක එකට එකතු වුණාම ඔයාලගේ Development Process එක මාරම විදියට Effective වෙනවා.

Backend එකේ Robustness, Security, Scalability එක Spring Boot වලින් දෙනවා. Frontend එකේ User Experience, Interactivity, Richness එක React වලින් දෙනවා. මේ දෙකේ තියෙන Separation of Concerns එක නිසා Project Management එකත් ලේසි වෙනවා, Troubleshooting එකත් ලේසි වෙනවා. මේ වගේ Combo එකක් තමයි දැන් Data Intensive Applications වලට ගොඩක් අය Recommend කරන්නේ.

මචං, මේක නිකන් බැලුවට වැඩක් නෑ. ඔයාලත් මේ Code ටික Download කරගෙන (හරි Copy-Paste කරගෙන!) ඔයාලගේම Machine එකේ Run කරලා බලන්න. පොඩි පොඩි දේවල් වෙනස් කරලා බලන්න. එතකොට තමා දැනුම ඔලුවට යන්නේ. මොකක් හරි ප්‍රශ්නයක් ආවොත්, Comment Section එකේ අහන්න. මම පුළුවන් විදියට උදව් කරන්නම්. වැඩේ එලද? එහෙනම් තවත් අලුත් දෙයක් එක්ක ඉක්මනින්ම හම්බවෙමු! ජයවේවා!