π Spring Boot Learning Journey: From Beginner to Full-Stack Java Developer
π Spring Boot Journey #01: Building a Student Management REST API with Spring Boot, MySQL & Spring Data JPA

Search for a command to run...
π Spring Boot Journey #01: Building a Student Management REST API with Spring Boot, MySQL & Spring Data JPA

No comments yet. Be the first to comment.
ava Backend to Cloud is my public learning journey where I document everything I learn while becoming a Java Full Stack & Cloud Developer. Through real-world projects, beginner-friendly tutorials, and hands-on examples, I explore Java, Spring Boot, Spring Data JPA, MySQL, REST APIs, Spring Security, Docker, AWS, and modern backend developmentβone article at a time.
Understanding how bias, accountability, and design choices shape the future of artificial intelligence

The untold story of how a tiny logic puzzle almost killed artificial intelligenceβand how its failure sparked the deep learning revolution

Today wasnβt about building the next ChatGPT. It was about finally understanding what Generative AI actually is β without buzzwords, without hype, without confusion. And honestly?It was simpler than I expected. What I Thought Generative AI Was Befor...

On this page
Part of my Spring Boot learning journey, where I build real-world backend projects and share everything I learn along the way.
Hello Developers! π
Welcome back to another article in my Spring Boot Learning Journey.
As I continue learning Spring Boot, I don't want to just watch tutorials or read documentationβI want to build real projects that help me understand how backend applications work in the real world.
That's why I decided to build a Student Management REST API.
This project may look simple, but while building it, I learned many important backend concepts such as:
Creating REST APIs
Connecting Spring Boot with MySQL
Understanding Layered Architecture
Using Spring Data JPA
Performing CRUD Operations
Testing APIs using Postman
Returning proper HTTP Status Codes
Understanding Soft Delete and Hard Delete
Instead of keeping my notes private, I'm sharing everything I learn so that beginners like me can learn faster.
If you're just starting Spring Boot, this article is for you.
The Student Management REST API is a backend application that allows users to perform CRUD (Create, Read, Update, Delete) operations on student data.
Instead of building a frontend first, I focused completely on understanding backend development.
The application receives HTTP requests from Postman, processes the request inside Spring Boot, performs database operations using Spring Data JPA, and returns JSON responses.
Throughout this project, I followed the Controller β Service β Repository (C-S-R) architecture, which is commonly used in professional Spring Boot applications.
β Create Student
β Get All Students
β Get Student by ID
β Update Student Details
β Delete Student
β Soft Delete Support
β JSON Request & Response
β ResponseEntity
β Layered Architecture
β MySQL Database Integration
β Spring Data JPA
β API Testing using Postman
Java
Spring Boot
Spring Data JPA (Hibernate)
MySQL
Maven
Postman
Learning becomes much easier when we apply concepts in a real project.
Instead of memorizing annotations or APIs, I wanted to understand:
How requests travel inside a Spring Boot application.
How Spring Boot connects to MySQL.
How Spring Data JPA works.
How REST APIs are created.
How professional backend projects are structured.
This project gave me practical experience with all of these concepts.
In the next section, we'll understand the project structure and how Controller, Service, and Repository work together.
One of the first things I noticed while learning Spring Boot was that every project follows a specific folder structure. At first, it looked confusing, but after building this project, I understood why each folder exists.
Here's the structure of my Student Management REST API project:
src
βββ main
βββ java
β βββ me.satyajitmishra
β βββ CRUDSpringBootProject_1
β βββ controller
β β StudentController.java
β β
β βββ services
β β StudentService.java
β β
β βββ repository
β β StudentRepository.java
β β
β βββ entity
β β Student.java
β β
β βββ CRUDSpringBootProject1Application.java
β
βββ resources
βββ application.properties
βββ static
Although there are many folders inside a Spring Boot project, these are the ones I used while building my CRUD application.
The Entity package contains the Java classes that represent our database tables.
In my project, I created a Student class.
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}
When Spring Boot starts, it reads this class and automatically creates a matching table inside the MySQL database.
Simply put,
One Entity Class = One Database Table
This is one of the most useful features provided by Spring Data JPA.
The Repository layer is responsible for communicating with the database.
Instead of writing SQL queries manually, Spring Data JPA provides ready-made methods like:
My repository is very simple.
public interface StudentRepository extends JpaRepository<Student, Long> {
}
By extending JpaRepository, Spring Boot automatically provides all basic CRUD operations.
This means I don't need to write SQL for inserting, updating, deleting, or fetching data.
The Service layer contains the business logic of the application.
Many beginners ask,
"Why not call Repository directly from the Controller?"
The answer is simple.
The Controller should only handle HTTP requests.
The Service should decide what business logic needs to be executed.
For example,
When a client requests to update a student,
The Controller receives the request,
The Service checks whether the student exists,
Then it updates the required fields,
Finally, it asks the Repository to save the changes.
Keeping business logic inside the Service layer makes the project clean and easier to maintain.
The Controller acts as the entry point of our application.
Whenever a client (like Postman or a frontend application) sends an HTTP request, it is received by the Controller.
Example:
@PostMapping("/create")
This endpoint tells Spring Boot,
"When someone sends a POST request to /create, execute this method."
Similarly,
@GetMapping
handles GET requests,
@PutMapping
handles update requests,
and
@DeleteMapping
handles delete requests.
The Controller does not interact with the database directly.
Instead, it calls the Service layer.
This project follows one of the most commonly used architectures in Spring Boot.
Client (Postman)
β
βΌ
StudentController
β
βΌ
StudentService
β
βΌ
StudentRepository
β
βΌ
MySQL Database
Let's understand this flow step by step.
The client sends an HTTP request using Postman.
For example,
POST /api/students/create
The request reaches the Controller.
The Controller accepts the request body and passes it to the Service layer.
The Service performs all business logic.
Examples:
The Service calls the Repository.
The Repository communicates with MySQL using Spring Data JPA.
The Repository returns the result.
The Service processes it if needed.
Finally,
The Controller returns a JSON response to the client.
Separating responsibilities makes the application easier to understand and maintain.
This separation follows the Single Responsibility Principle, making the code cleaner and more scalable as the project grows.
Now that we understand the project structure and architecture, the next part will explain how to connect Spring Boot with MySQL, configure application.properties, and understand how the Entity class maps to a database table.
Until now, our project only contained Java classes.
But where will the student data be stored?
The answer is MySQL.
A database allows us to store data permanently. Even if we stop or restart our application, the data remains safe inside the database.
Spring Boot makes connecting with MySQL very simple. We only need to configure a few properties inside the application.properties file.
application.properties?The application.properties file is the central configuration file of a Spring Boot application.
Instead of hardcoding values like database URL, username, or password inside Java classes, we keep them here.
This approach is called Externalized Configuration.
It makes the application easier to maintain and more secure.
You can find this file here:
src
βββ main
βββ resources
βββ application.properties
Below is a typical configuration used to connect Spring Boot with MySQL.
spring.datasource.url=jdbc:mysql://localhost:3306/studentdb
spring.datasource.username=root
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Let's understand what each property does.
spring.datasource.url=jdbc:mysql://localhost:3306/studentdb
This tells Spring Boot:
Breaking it down:
jdbc β Java Database Connectivitymysql β Database typelocalhost β Database is running on your own computer3306 β Default MySQL portstudentdb β Database namespring.datasource.username=root
This is the username used to log in to MySQL.
For many local installations, the default username is root.
spring.datasource.password=your_password
This is your MySQL password.
β οΈ Important: Never upload your real database password to GitHub. Add application.properties to your .gitignore file or use environment variables for sensitive information.
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
This tells Spring Boot which JDBC driver should be used to communicate with MySQL.
Usually, Spring Boot detects this automatically if the MySQL dependency is added, but explicitly specifying it is also fine.
spring.jpa.hibernate.ddl-auto=update
This is one of the most important properties.
It controls what Hibernate should do with your database tables when the application starts.
Some common values are:
| Value | Description |
|---|---|
create |
Deletes existing tables and creates new ones every time the application starts. |
create-drop |
Creates tables when the application starts and deletes them when it stops. |
update |
Updates the table structure without deleting existing data. |
validate |
Checks whether the database matches your Entity classes. |
none |
Hibernate does not perform any table management. |
During development, I used:
spring.jpa.hibernate.ddl-auto=update
This automatically updates the table whenever I add a new field to my Entity class without losing existing data.
spring.jpa.show-sql=true
This prints every SQL query executed by Hibernate in the console.
Example:
insert into student(name,email)
values(?,?)
This is very helpful while debugging.
spring.jpa.properties.hibernate.format_sql=true
This formats SQL queries in a readable way.
Without it:
select*fromstudentwhereid=?
With formatting:
select
*
from
student
where
id=?
Much easier to read!
This is where Entity classes come into the picture.
My project contains a Student entity.
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}
When Spring Boot starts:
@Entity.The mapping is straightforward:
| Java Entity | MySQL Table |
|---|---|
| Student | student |
| id | id |
| name | name |
In simple words:
One Entity class becomes one database table, and each field becomes a column.
Let's understand the startup process.
Spring Boot Starts
β
βΌ
Reads application.properties
β
βΌ
Connects to MySQL
β
βΌ
Scans @Entity Classes
β
βΌ
Creates/Updates Tables
β
βΌ
Application Ready π
Everything happens automatically.
This is one of the biggest reasons why Spring Boot is so popularβit removes a lot of manual configuration.
β
application.properties stores application configuration.
β Spring Boot connects to MySQL using the datasource properties.
β Hibernate automatically maps Entity classes to database tables.
β
ddl-auto=update keeps the database schema synchronized during development.
β
show-sql=true helps us see the generated SQL queries.
Now that our application is connected to MySQL and the database is ready, it's time to build the core of the project.
In the next part, we'll understand how Spring Data JPA Repository works, why we extend JpaRepository, and how CRUD operations happen without writing SQL queries.
If you've worked with JDBC before, you know that performing database operations usually requires writing SQL queries manually.
For example, to insert a student, you would write something like:
INSERT INTO student(name, email)
VALUES('John', 'john@gmail.com');
Similarly, for fetching data:
SELECT * FROM student;
Updating data:
UPDATE student
SET name='David'
WHERE id=1;
Deleting data:
DELETE FROM student
WHERE id=1;
As the project grows, writing SQL for every operation becomes repetitive and time-consuming.
This is where Spring Data JPA makes life much easier.
Spring Data JPA is a Spring Boot module that simplifies database operations.
Instead of writing SQL queries manually, we simply write Java code, and Spring Boot automatically generates the required SQL behind the scenes.
This saves time, reduces boilerplate code, and makes applications easier to maintain.
A Repository acts as the bridge between our Java application and the database.
Its main responsibility is to:
The Controller never talks directly to the database.
Instead, the flow is:
Controller
β
βΌ
Service
β
βΌ
Repository
β
βΌ
Database
This keeps our application clean and organized.
Creating a repository in Spring Boot is surprisingly simple.
package me.satyajitmishra.CRUDSpringBootProject_1.repository;
import me.satyajitmishra.CRUDSpringBootProject_1.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentRepository
extends JpaRepository<Student, Long> {
}
That's it!
There is no implementation class.
No SQL queries.
No JDBC code.
No Connection objects.
Spring Boot creates everything automatically.
Let's understand this line carefully.
JpaRepository<Student, Long>
It contains two generic parameters.
<Student, Long>
This tells Spring Boot:
"Manage the Student Entity."
This tells Spring Boot:
"The primary key (ID) type of Student is Long."
Since my Student entity looks like this:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
the Repository becomes:
JpaRepository<Student, Long>
The biggest advantage of JpaRepository is that it already provides many useful methods.
Without writing any code, we can use:
studentRepository.save(student);
Purpose:
Spring Boot automatically decides whether it should perform an INSERT or UPDATE.
studentRepository.findAll();
Returns
List<Student>
Equivalent SQL:
SELECT * FROM student;
studentRepository.findById(id);
Returns
Optional<Student>
Equivalent SQL:
SELECT *
FROM student
WHERE id=?
studentRepository.delete(student);
Equivalent SQL:
DELETE
FROM student
WHERE id=?
studentRepository.existsById(id);
Returns
true
or
false
Useful before updating or deleting data.
One question I had while learning was:
Why doesn't
findById()simply return a Student?
The reason is simple.
Imagine we search for:
Student ID = 100
but no student exists with that ID.
Returning null could easily lead to a NullPointerException.
Instead, Spring Boot returns:
Optional<Student>
This forces us to check whether the data exists before using it.
Example:
Optional<Student> student =
studentRepository.findById(id);
if(student.isPresent()){
return student.get();
}
This makes our application much safer.
Whenever I wanted to save a student, my Service layer simply called:
studentRepository.save(student);
To fetch all students:
studentRepository.findAll();
To fetch a single student:
studentRepository.findById(id);
To delete a student:
studentRepository.delete(student);
Notice something interesting.
I never wrote a single SQL query.
Spring Data JPA generated everything automatically.
Let's understand what happens when we create a student.
POST Request
β
βΌ
Controller
β
βΌ
Service
β
βΌ
studentRepository.save(student)
β
βΌ
Hibernate
β
βΌ
Generated SQL
β
βΌ
INSERT INTO student(...)
β
βΌ
MySQL Database
Everything happens automatically behind the scenes.
This is one of the biggest reasons why Spring Boot is loved by developers.
β No manual SQL for basic CRUD
β Less boilerplate code
β Cleaner project structure
β Faster development
β Easy to maintain
β Production-ready architecture
β Easy integration with Hibernate
JpaRepository provides built-in CRUD methods.save() is used for both Insert and Update.findById() returns an Optional to avoid NullPointerException.Now that we understand how the Repository works, it's time to move to the Service Layer.
In the next part, we'll learn why the Service layer is important, how business logic is implemented, and how CRUD operations are handled before interacting with the database.
In the previous part, we learned how the Repository communicates with the database using Spring Data JPA.
Now let's move to another important layer in a Spring Boot application β the Service Layer.
Many beginners ask:
"If the Repository already performs CRUD operations, why do we need a Service layer?"
That's a great question.
The answer is simple:
The Service layer contains the business logic of our application.
Instead of writing all the logic inside the Controller, we move it to the Service class to keep our code clean, reusable, and easier to maintain.
Imagine your application is growing.
Today, you only need to save student information.
Tomorrow, you might need to:
If all of this logic is written inside the Controller, the code quickly becomes messy.
That's why we separate responsibilities.
This separation makes the application cleaner and easier to scale.
The flow of a request looks like this:
Client (Postman)
β
βΌ
StudentController
β
βΌ
StudentService
β
βΌ
StudentRepository
β
βΌ
MySQL Database
The Controller never communicates directly with the database.
Everything passes through the Service layer.
To use the Repository inside the Service class, we inject it.
@Service
public class StudentService {
private final StudentRepository studentRepository;
public StudentService(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
}
This is called Constructor Dependency Injection.
Spring Boot automatically creates the Repository object and provides it to the Service class.
This is cleaner and recommended over field injection.
Let's look at the first CRUD operation.
public Student createStudent(Student student){
return studentRepository.save(student);
}
What happens here?
The Controller receives a POST request.
POST /api/students/create
The request body is converted into a Student object.
The Controller calls
studentService.createStudent(student);
The Service calls
studentRepository.save(student);
Spring Data JPA generates the SQL query.
INSERT INTO student(...)
VALUES(...)
The new student is stored inside MySQL.
Your Service method looks like this:
public Student getStudent(Long id){
Optional<Student> student =
studentRepository.findById(id);
if(student.isPresent()){
return student.get();
}
return null;
}
Let's understand it.
The Repository searches the database.
studentRepository.findById(id)
Since the student may or may not exist, Spring Boot returns an Optional<Student>.
If the student exists,
return student.get();
Otherwise,
return null;
In larger applications, we usually throw a custom exception instead of returning null, but this approach is good for beginners.
Getting all records is very simple.
public List<Student> getAllStudents(){
return studentRepository.findAll();
}
The Repository automatically executes
SELECT *
FROM student;
and returns all students as a list.
Updating data is slightly different from creating data.
We first check whether the student exists.
Optional<Student> existingStudent =
studentRepository.findById(id);
If no student is found,
return null;
Otherwise,
we update the object and save it again.
studentRepository.save(student);
An important thing to remember:
The same
save()method is used for both Insert and Update.
Spring Data JPA decides automatically whether it should perform an INSERT or an UPDATE based on the entity's ID.
Deleting is also straightforward.
First,
find the student.
Optional<Student> student =
studentRepository.findById(id);
If it exists,
delete it.
studentRepository.delete(student.get());
Spring Boot generates
DELETE
FROM student
WHERE id=?
In your project, you've also explored Soft Delete, where the record isn't removed permanently but is marked as deleted.
This approach is commonly used in real-world applications because it allows data recovery and auditing.
Here's how a typical request moves through the application.
HTTP Request
β
βΌ
Controller
β
βΌ
Service
β
βΌ
Repository
β
βΌ
Hibernate
β
βΌ
MySQL Database
β
βΌ
JSON Response
Every layer has a clear responsibility.
Imagine adding features like:
With a proper Service layer, these features can be added without making the Controller complicated.
That's why almost every professional Spring Boot project follows this architecture.
β The Service layer contains business logic.
β Controllers should remain thin and only handle HTTP requests.
β Repository performs database operations.
β
save() works for both insert and update.
β
findById() returns an Optional.
β Clean architecture makes applications easier to maintain.
Now that we've built the backend logic, it's time to expose it through REST APIs.
In the next part, we'll explore the Controller layer, understand annotations like @RestController, @RequestMapping, @GetMapping, @PostMapping, and build all CRUD endpoints step by step.
Until now, we have learned about:
Now it's time to understand the Controller, which is responsible for handling HTTP requests.
Think of the Controller as the main entrance of your application.
Whenever a client (such as Postman, a React application, or a mobile app) sends an HTTP request, the request first reaches the Controller.
The Controller receives the request, calls the appropriate Service method, and finally returns a response to the client.
Let's understand the complete flow.
Client (Postman / Frontend)
β
βΌ
StudentController
β
βΌ
StudentService
β
βΌ
StudentRepository
β
βΌ
MySQL Database
Notice that the Controller never communicates directly with the database.
Its only responsibility is handling HTTP requests and responses.
My controller looks like this:
@RestController
@RequestMapping("/api/students")
public class StudentController {
private final StudentService studentService;
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
}
Let's understand every part.
@RestController
This annotation tells Spring Boot that this class is a REST Controller.
Whenever a method returns data,
Spring Boot automatically converts it into JSON.
Example
Student student = new Student();
becomes
{
"id":1,
"name":"John",
"email":"john@gmail.com"
}
without writing any conversion code.
@RequestMapping("/api/students")
This is the base URL of our controller.
Instead of writing
@PostMapping("/api/students/create")
every time,
we write
@RequestMapping("/api/students")
once,
then every endpoint automatically starts with
/api/students
For example
@PostMapping("/create")
becomes
POST /api/students/create
private final StudentService studentService;
public StudentController(StudentService studentService){
this.studentService = studentService;
}
The Controller needs the Service object.
Spring Boot automatically creates it and injects it through the constructor.
This is called
Constructor Dependency Injection.
Let's look at the Create API.
@PostMapping("/create")
public ResponseEntity<Student> createStudent(
@RequestBody Student student){
Student createdStudent =
studentService.createStudent(student);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(createdStudent);
}
@PostMapping("/create")
This tells Spring Boot,
"When a POST request comes to
/api/students/create
execute this method."
@RequestBody Student student
The JSON received from Postman
{
"name":"Satyajit",
"email":"abc@gmail.com"
}
is automatically converted into
Student student
This process is called
JSON Deserialization.
Instead of returning only data,
I returned
ResponseEntity<Student>
Why?
Because ResponseEntity allows us to send
all together.
Example
return ResponseEntity
.status(HttpStatus.CREATED)
.body(createdStudent);
returns
Status Code
201 Created
along with the Student object.
@GetMapping("/get/{id}")
public ResponseEntity<Student>
getStudent(@PathVariable Long id){
Student student =
studentService.getStudent(id);
if(student != null){
return ResponseEntity.ok(student);
}
return ResponseEntity.notFound().build();
}
@GetMapping
handles HTTP GET requests.
Example
GET /api/students/get/1
Suppose the URL is
/api/students/get/5
Spring Boot automatically extracts
5
and stores it in
Long id
This is called
@PathVariable
@GetMapping("/get")
public ResponseEntity<List<Student>>
getStudents(){
List<Student> students =
studentService.getAllStudents();
return ResponseEntity.ok(students);
}
This endpoint returns
GET /api/students/get
The response is
[
{
"id":1,
"name":"John"
},
{
"id":2,
"name":"David"
}
]
@PutMapping("/update/{id}")
public ResponseEntity<Student>
updateStudent(
@PathVariable Long id,
@RequestBody Student student){
Student updatedStudent =
studentService.updateStudent(id,student);
return ResponseEntity.ok(updatedStudent);
}
PUT is used whenever we want to modify existing data.
Example
PUT /api/students/update/2
@DeleteMapping("/delete/{id}")
public ResponseEntity<String>
deleteStudent(@PathVariable Long id){
String message =
studentService.deleteStudent(id);
return ResponseEntity.ok(message);
}
This endpoint deletes
Student ID = 2
The response
Student Deleted Successfully
| HTTP Method | Endpoint | Description |
|---|---|---|
| POST | /api/students/create |
Create Student |
| GET | /api/students/get |
Get All Students |
| GET | /api/students/get/{id} |
Get Student by ID |
| PUT | /api/students/update/{id} |
Update Student |
| DELETE | /api/students/delete/{id} |
Delete Student |
| Status | Meaning |
|---|---|
| 200 OK | Request Successful |
| 201 Created | Resource Created |
| 404 Not Found | Student Not Found |
Using proper HTTP status codes makes our API more professional and easier for frontend developers to understand.
β Controller receives HTTP requests.
β
@RestController returns JSON automatically.
β
@RequestMapping defines the base URL.
β
@PostMapping, @GetMapping, @PutMapping, and @DeleteMapping handle different HTTP methods.
β
@RequestBody converts JSON into Java Objects.
β
@PathVariable extracts values from the URL.
β
ResponseEntity helps us return proper HTTP status codes and responses.
Now that our REST APIs are ready, it's time to test them using Postman.
In the next part, we'll send real HTTP requests, understand JSON request and response bodies, and verify that our CRUD operations are working correctly.
Building a REST API is only half the job.
The next step is to test whether our APIs are working correctly.
For this project, I used Postman, one of the most popular API testing tools used by developers.
Postman allows us to send HTTP requests without building a frontend application.
Instead of creating a React or Angular UI first, we can directly test our backend APIs.
Imagine you've built a backend application.
How do you know if your API is actually working?
You have two options:
Postman makes testing quick and simple.
With Postman, we can:
That's why almost every backend developer uses it.
All APIs in my project start with:
http://localhost:8080/api/students
Since I used
@RequestMapping("/api/students")
inside the Controller, every endpoint begins with this base URL.
The first API is used to create a new student.
POST /api/students/create
Complete URL
http://localhost:8080/api/students/create
{
"name":"Satyajit Mishra",
"email":"satyajit@gmail.com",
"course":"Computer Science"
}
{
"id":1,
"name":"Satyajit Mishra",
"email":"satyajit@gmail.com",
"course":"Computer Science"
}
201 Created
πΈ Insert your POST API Postman screenshot here.
This endpoint retrieves every student from the database.
GET /api/students/get
http://localhost:8080/api/students/get
SELECT *
FROM student;
[
{
"id":1,
"name":"Satyajit"
},
{
"id":2,
"name":"Rahul"
}
]
200 OK
πΈ Insert your GET (All Students) Postman screenshot here.
Sometimes we only need a single student.
For that, we use Path Variables.
GET /api/students/get/1
The Controller extracts
1
using
@PathVariable
The Service searches for that student.
If found,
it returns the student.
Otherwise,
it returns
404 Not Found
{
"id":1,
"name":"Satyajit",
"email":"satyajit@gmail.com"
}
200 OK
πΈ Insert your GET by ID screenshot here.
Updating existing data is very common.
For example,
a student changes their email.
Instead of creating another record,
we update the existing one.
PUT /api/students/update/1
{
"name":"Satyajit Mishra",
"email":"newemail@gmail.com"
}
{
"id":1,
"name":"Satyajit Mishra",
"email":"newemail@gmail.com"
}
200 OK
πΈ Insert your PUT API screenshot here.
Deleting a student is straightforward.
DELETE /api/students/delete/1
Student Deleted Successfully
200 OK
πΈ Insert your DELETE API screenshot here.
POSTMAN
β
βΌ
Controller
β
βΌ
Service
β
βΌ
Repository
β
βΌ
MySQL Database
β
βΌ
JSON Response
Every request follows the same journey.
The only difference is the HTTP method.
| Method | Purpose |
|---|---|
| GET | Read Data |
| POST | Create Data |
| PUT | Update Data |
| DELETE | Delete Data |
These four methods form the foundation of most REST APIs.
Testing with Postman helped me understand much more than simply writing code.
I learned how HTTP requests are sent, how JSON data is exchanged, how different HTTP methods work, and how important status codes are for communication between the backend and frontend.
More importantly, I gained confidence that my APIs were working correctly before integrating them with any frontend application.
So far, we've built a complete CRUD REST API and tested every endpoint using Postman.
In the next part, I'll explain Hard Delete vs Soft Delete, why real-world applications rarely delete data permanently, and how I implemented both approaches in my Spring Boot project.
Until now, we've learned how to create, read, update, and delete data.
But have you ever wondered what actually happens when we delete a record from the database?
Does it disappear forever?
The answer depends on how we delete it.
There are two common approaches:
Let's understand both with simple examples.
Hard Delete means removing the data permanently from the database.
For example,
Suppose our Student table contains:
| ID | Name | |
|---|---|---|
| 1 | Rahul | rahul@gmail.com |
| 2 | Satyajit | satyajit@gmail.com |
Now if we execute
DELETE FROM student WHERE id = 2;
The table becomes
| ID | Name | |
|---|---|---|
| 1 | Rahul | rahul@gmail.com |
Student Satyajit is completely removed.
There is no way to recover the record unless we have a backup.
This is called Hard Delete.
Although Hard Delete is simple, it has some disadvantages.
Imagine an e-commerce application.
A customer deletes their account.
Should all their orders, invoices, payment history, and shipping records disappear forever?
Probably not.
Similarly,
Imagine a hospital management system.
Deleting patient records permanently could create legal and auditing issues.
That's why many real-world applications avoid Hard Delete.
Soft Delete does not remove the record from the database.
Instead,
it simply marks the record as deleted.
The record still exists,
but users cannot see it.
We add a new column to our table.
isDeleted
Initially,
every record looks like this.
| ID | Name | isDeleted |
|---|---|---|
| 1 | Rahul | false |
| 2 | Satyajit | false |
Now suppose we delete Student ID = 2.
Instead of deleting,
Spring Boot updates the value.
| ID | Name | isDeleted |
|---|---|---|
| 1 | Rahul | false |
| 2 | Satyajit | true |
Notice something interesting.
The record is still present.
Only the flag changed.
To implement Soft Delete,
we simply add a new field.
private boolean isDeleted = false;
Now every new student is automatically marked as
false
meaning
"Active Student"
Instead of
studentRepository.delete(student);
we write
student.setDeleted(true);
studentRepository.save(student);
This updates the record instead of removing it.
Generated SQL
UPDATE student
SET is_deleted = true
WHERE id = ?;
Much safer than deleting permanently.
Now another problem appears.
If every record still exists,
how do we hide deleted students?
Spring Data JPA makes this easy.
Inside Repository,
we create
List<Student> findAllByIsDeletedFalse();
That's all.
No SQL required.
Spring Boot automatically generates
SELECT *
FROM student
WHERE is_deleted = false;
This is one of my favorite features of Spring Data JPA.
Many beginners find this confusing.
Let's break it down.
findAllByIsDeletedFalse()
Spring reads the method name.
findAll
β
By
β
IsDeleted
β
False
and automatically generates the SQL query.
This feature is called
Query Method Creation.
No SQL.
No implementation.
Just a method name.
Soft Delete is widely used in production applications.
Some examples include:
Products can be hidden instead of deleted.
Transactions should never be permanently deleted.
Patient records must remain available for legal purposes.
Deleted students can be restored later if needed.
Former employee records are usually archived instead of removed.
| Hard Delete | Soft Delete |
|---|---|
| Removes data permanently | Keeps data in database |
| Cannot recover easily | Can restore later |
| Uses DELETE query | Uses UPDATE query |
| Faster | Slightly slower |
| Less storage | More storage |
| Suitable for temporary data | Suitable for production systems |
It depends on the application.
If the data is temporary,
Hard Delete is perfectly fine.
But if the application needs
Soft Delete is the better choice.
That's why most enterprise applications prefer Soft Delete.
While building this project,
I realized that deleting data is not always as simple as calling
delete()
Understanding the difference between Hard Delete and Soft Delete helped me think beyond basic CRUD operations and gave me insight into how real-world backend applications handle important data.
β Hard Delete permanently removes data.
β Soft Delete keeps data by marking it as deleted.
β Spring Data JPA can automatically generate query methods like
findAllByIsDeletedFalse()
β Soft Delete is commonly used in production applications.
β Enterprise applications rarely delete important data permanently.
Building this Student Management REST API has been one of the most valuable projects in my Spring Boot learning journey.
I didn't just learn how to perform CRUD operationsβI also understood how Spring Boot, Spring Data JPA, MySQL, and REST APIs work together in a real backend application.
More importantly, I learned the importance of writing clean, layered code using the Controller β Service β Repository architecture and explored practical concepts like Soft Delete, which are commonly used in production systems.
This project is just one milestone in my journey.
Next, I'll continue learning and building with:
Thank you for reading! π
If you're also learning Spring Boot, I hope this article helped you understand the concepts more clearly.
Feel free to share your thoughts, suggestions, or questions in the comments.
Happy Coding! π
Every project comes with challenges, and this one was no exception.
Instead of everything working perfectly on the first try, I made mistakes, faced errors, and spent time debugging them.
Looking back, those challenges taught me much more than simply following a tutorial.
Here are some of the problems I encountered and what I learned from them.
When I first tried running the application, Spring Boot failed to connect to MySQL.
The application wouldn't start, and I saw a long stack trace in the console.
After checking my configuration, I realized the issue was in my application.properties file.
I had either entered the wrong database name or incorrect database credentials.
After fixing the configuration, Spring Boot connected successfully.
Always verify:
Initially, I returned objects directly from my Controller.
return student;
Although it worked, I later learned that using ResponseEntity is a better approach.
It allows us to return:
Example:
return ResponseEntity
.status(HttpStatus.CREATED)
.body(student);
This makes the API more professional and easier for frontend developers to understand.
Always return meaningful HTTP status codes instead of returning only data.
One concept that confused me in the beginning was Optional.
When I wrote:
studentRepository.findById(id);
I expected it to return a Student object.
Instead, it returned:
Optional<Student>
After learning why Optional exists, everything became much clearer.
It helps prevent NullPointerException and forces us to check whether data exists before using it.
Never assume data exists in the database.
Always handle missing data properly.
At first, I tried updating a student without checking whether the record existed.
Later, I realized that the correct approach is:
This prevents unexpected errors and makes the API more reliable.
Validation is just as important as writing the CRUD logic.
Initially, I thought deleting a record simply meant removing it from the database.
While learning Spring Boot, I discovered the concept of Soft Delete, where records are marked as deleted instead of being removed permanently.
This gave me a better understanding of how enterprise applications manage important data.
Real-world applications often prefer Soft Delete over Hard Delete because it allows data recovery and auditing.
This project wasn't just about CRUD operations.
It helped me understand how a real Spring Boot backend application is organized.
Some of the key concepts I learned include:
More importantly, I learned how different components work together to process a request from start to finish.
Learning never stops.
My next goal is to build more advanced Spring Boot projects by exploring:
I also plan to continue documenting my learning journey by sharing practical projects and articles.
This Student Management REST API is one of the first backend projects I've built while learning Spring Boot.
Although it's a beginner-level project, it helped me build a strong foundation in backend development.
If you're also starting your Spring Boot journey, my advice is simple:
π Don't just watch tutorials.
Build projects.
Break things.
Fix them.
That's where real learning happens.
Thank you for reading!
If you found this article helpful, feel free to connect with me and follow my Spring Boot learning journey.
Happy Coding! π