Spring Boot Project
Guide to building a RESTful API using Spring Boot and PostgreSQL.
A brief flow chart on how it will all work:
Project Settings:
- Project
- Maven
- Language
- Java 21
- Springboot
- Version: 3.5.3
Dependencies Used:
- Spring Web
- Spring Data JPA
- PostgreSQL Data Driver
- Lombok
Full Code Available at:
aaryansinhaa
/
restful-guide
A guide on making RESTful API using Java+ Springboot and PGSQL
1. Setting Up PostgreSQL:
-
Sudo into your postgres user to use psql:
sudo -u postgres -i
-
Once inside your postgres user, open up psql
psql
-
Create your Database and tables
-
CREATE DATABASE ems;
You have now created a database called ems, and this will be the --database that we will use in our guide.
-
2. Configuring applications.properties
Make sure that you properly configure your environment variables by using a
.env
file and importingDotEnv
in yourpom.xml
Inside src/main/resources/application.properties
:
#pgsql config
spring.application.name=erp
spring.datasource.url=jdbc:postgresql://localhost:5432/ems
spring.datasource.username= ${DB_USERNAME}
spring.datasource.password= ${DB_PASSWORD
#jpa configs
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
3. Packages
Inside src/main/java/com/guide/erp/
:
-
Controller: It handles the HTTP(GET, POST, PUT, DELETE) API requests such as
api/product/{id}
. It acts as the interface between the frontend and the backend. - DTO: Data Transfer Object: Transfers Data Between the layers(client and server).
-
Entity: Table of a postgres database. a
Product
Entity is annotated with@Entity
and is mapped toproduct
table in pgSQL. - Exception: Handles custom errors and exceptions across the application.
-
Repository: Performs the database operations(CRUD). Extends Spring Data JPA interfaces (
JpaRepository
,CrudRepository
) to interact with the database. - Service: It contains the business logic. Coordinates between controller and repository, ensuring proper processing and validation before data access.
4. Making Entity
- Create an Employee Entity:
package com.guide.erp.entity;
import java.util.Date;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "employee")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "emp_id")
private Long empId;
@Column(name = "emp_name", nullable = false)
private String empName;
@Column(name = "emp_email", nullable = false, unique = true)
private String empEmail;
@Column(name = "emp_phone", nullable = false, unique = true)
private String empPhone;
@Column(name = "emp_address", nullable = false)
private String empAddress;
@Column(name = "emp_position", nullable = false)
private String empPosition;
@Column(name = "emp_salary", nullable = false)
private double empSalary;
@Column(name = "emp_department", nullable = false)
private String empDepartment;
@Column(name = "emp_hire_date", nullable = false)
private Date empHireDate;
@Column(name = "emp_manager")
private Employee manager;
}
On running this Entity, you might get an error:
Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Could not determine recommended JdbcType for Java type 'com.guide.erp.entity.Employee'
This is because of the Cyclic mapping at:
@Column(name = "emp_manager")
private Employee manager;
Fix: Use @ManyToOne for self-reference
Update the field to this:
@ManyToOne
@JoinColumn(name = "emp_manager") // this will be the foreign key column in the 'employee' table
private Employee manager;`
You are now ready to run the application.
After running the application, Check the pgSQL database,
Now, that you have created an employee entity
, we shall create employee repository
, to perform CRUD
.
5. Making Repository
First, let's understand what is JpaRepository:
What is JpaRepository
?
JpaRepository<T, ID>
is a Spring Data interface that provides a full set of CRUD operations and more, out of the box.
It provides:
Method | Description |
---|---|
findAll() |
Get all records |
findById(ID id) |
Get a record by primary key |
save(entity) |
Insert or update a record |
delete(entity) |
Delete a record |
deleteById(ID id) |
Delete by primary key |
count() |
Total number of records |
existsById(ID id) |
Checks if a record exists by ID |
findBy<FieldName>(...) |
Auto-query generation using method naming |
Custom Query Example
List<Employee> findByEmpDepartment(String department);
This will generate:
SELECT * FROM employee WHERE emp_department = ?;
for more info on JPA Repositories
As SimpleJpaRepository
indirectly implements JpaRepository
interface and it's methods. And as SimpleJpaRepository
is annotated with @Repository
and all it's methods are @Transactional
, we don't need to annotate our EmployeeRepository
I hope that makes the context clear,
package com.guide.erp.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.guide.erp.entity.Employee;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {}
6. Making DTO
While when first seeing the code for the DTO, it is easy to assume that DTO is just mapping of an object with the employee table, but rather,
A DTO (Data Transfer Object) is not directly mapped to the table like an Entity. Instead, it's used to transfer only the required data between layers (e.g., backend β frontend), often derived from one or more entities.
A DTO class object is not persisted
For eg:
Say you're returning a list of employees from your REST API:
- You donβt want to send the full
Employee
object (with its manager object, and that manager's manager, and so on). - You use a DTO to send only what's needed:
{
"empId": 101,
"empName": "Aaryan",
"empEmail": "aaryan@erp.com",
"managerId": 3
}
in src/main/java/com/guide/erp/dto/EmployeeDto.java
:
package com.guide.erp.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class EmployeeDto {
private Long empId;
private String empName;
private String empEmail;
private String empPhone;
private String empAddress;
private String empPosition;
private double empSalary;
private String empDepartment;
private String empHireDate;
private Long managerId;
}
Now, after you have made an EmployeeDto, we would want to make an Employee mapper, to map:
we shall create a new package:
now, inside src/main/java/com/guide/erp/mapper/EmployeeMapper.java
:
package com.guide.erp.mapper;
import com.guide.erp.dto.EmployeeDto;
import com.guide.erp.entity.Employee;
public class EmployeeMapper {
public static EmployeeDto mapEmployeeToDto(Employee employee) {
if (employee == null) {
return null;
}
EmployeeDto dto = new EmployeeDto();
dto.setEmpId(employee.getEmpId());
dto.setEmpName(employee.getEmpName());
dto.setEmpEmail(employee.getEmpEmail());
dto.setEmpPhone(employee.getEmpPhone());
dto.setEmpAddress(employee.getEmpAddress());
dto.setEmpPosition(employee.getEmpPosition());
dto.setEmpSalary(employee.getEmpSalary());
dto.setEmpDepartment(employee.getEmpDepartment());
dto.setEmpHireDate(employee.getEmpHireDate());
if (employee.getManager() != null) {
dto.setManagerId(employee.getManager().getEmpId());
}
return dto;
}
public static Employee mapDtoToEmployee(EmployeeDto dto) {
if (dto == null) {
return null;
}
Employee employee = new Employee();
employee.setEmpId(dto.getEmpId());
employee.setEmpName(dto.getEmpName());
employee.setEmpEmail(dto.getEmpEmail());
employee.setEmpPhone(dto.getEmpPhone());
employee.setEmpAddress(dto.getEmpAddress());
employee.setEmpPosition(dto.getEmpPosition());
employee.setEmpSalary(dto.getEmpSalary());
employee.setEmpDepartment(dto.getEmpDepartment());
employee.setEmpHireDate(dto.getEmpHireDate());
if (dto.getManagerId() != null) {
Employee manager = new Employee();
manager.setEmpId(dto.getManagerId());
employee.setManager(manager);
} else {
employee.setManager(null);
}
return employee;
}
}
7. Make the Service Layer
Before making the service layer, we shall make an implementation package first, to clearly define what services to make:
so your com.guide.erp.service
should look like:
It is interesting to note that while the EmployeeServicesImpl.java
is a class, the EmployeeService.java
is an interface.
So now, as (hopefully) you can see that we will be declaring our methods
in EmployeeService.java
while defining them inside EmployeeServiceImpl.java
Your EmployeeService.java
should look like:
package com.guide.erp.service;
import com.guide.erp.dto.EmployeeDto;
public interface EmployeeService {
EmployeeDto createEmployee(EmployeeDto employeeDto);
}
and your EmployeeServiceImpl.java
shall look like:
@Service
@AllArgsConstructor
public class EmployeeServicesImpl implements EmployeeService{
private EmployeeRepository employeeRepository;
@Override
public EmployeeDto createEmployee(EmployeeDto employeeDto) {
Employee employee = EmployeeMapper.mapDtoToEmployee(employeeDto);
employee = employeeRepository.save(employee);
return EmployeeMapper.mapEmployeeToDto(employee);
}
}
What does the above code snippet do?
- It takes an
EmployeeDto
object as input. - It maps the DTO to an Employee entity using
EmployeeMapper
. - It saves the Employee entity to the database using
employeeRepository
. - It maps the saved Employee entity back to an
EmployeeDto
and returns it as the result. Here is a flowchart:
@Service
is a Spring annotation used to mark a class as a service provider. It indicates that the class holds business logic and is a service layer component. It is applied to classes to register them as Spring beans and make them eligible for dependency injection.
Internally, it's a specialization of
@Component
, so it's also a candidate for component scanning.
8. Making Controllers
Before you start, I would Appreciate if you recollect everything we have discussed till now and try and research more deeply about the concepts discussed and why specific decisions were taken.
1. Post Controller to add Employee:
Inside src/main/java/com/guide/erp/controller/EmployeeController.java
:
@AllArgsConstructor
@RestController
@RequestMapping("/api/employee")
public class EmployeeController {
private EmployeeService employeeService;
//Add Employee
@PostMapping("/add")
public ResponseEntity<EmployeeDto> createEmployee(@RequestBody EmployeeDto employee) {
EmployeeDto savedEmployee = employeeService.createEmployee(employee);
return new ResponseEntity<>(savedEmployee, HttpStatus.CREATED);
}
}
What is @RestController
?
@RestController
is a specialized version of @Controller
in Spring Boot, introduced to simplify building RESTful web services.
It is a class-level annotation that combines:
-
@Controller
@ResponseBody
What does it do?
- Returns data directly (usually JSON or XML) instead of rendering a view (like Thymeleaf or JSP).
- Automatically serializes Java objects to JSON using Jackson.
- Ideal for building REST APIs in Spring Boot.
What is @RequestBody
?
@RequestBody
is a Spring annotation used to bind the HTTP request body (typically JSON or XML) to a Java object.
It is commonly used in POST, PUT, and PATCH APIs where the client sends structured data (like a JSON object) in the body of the request.
Spring automatically:
- Parses the JSON,
- Converts it to an
EmployeeDto
object, - Injects it into the method.
Now, we have been coding and learning for quite a while, let's actually test our application, to see if it actually works.
I will be using curl
to test my API, it is easily available at your nearest linux system,
curl -X POST http://localhost:8080/api/employee/add \
-H "Content-Type: application/json" \
-d '{
"empName": "Aaryan",
"empEmail": "aaryan@example.com",
"empPhone": "9876543210",
"empAddress": "Delhi",
"empPosition": "Engineer",
"empSalary": 50000,
"empDepartment": "Tech",
"empHireDate": "2024-06-01",
"managerId": 1
}'
Now, let's query our employee
table and check if it got added to the database:
Congrats, you have successfully built your very first RESTful API POST
request.
Warning: Ensure that the json attributes are the same as variable name as in EmployeeDto
Before Moving Forward, I would like to generalise the workflow that we are using to create new endpoints and serving HTTP requests:
2. Get Methods for Employee
-
in
src/main/java/com/guide/erp/service/EmployeeService.java
:
EmployeeDto getEmployeeById(Long id);
-
in
src/main/java/com/guide/erp/service/impl/EmployeeServicesImpl.java
:
@Override public EmployeeDto getEmployeeById(Long id) { Employee employee = employeeRepository.findById(id).orElseThrow( ()-> new ResourceNotFoundException("Employee not found with id: " + id) ); return EmployeeMapper.mapEmployeeToDto(employee); }
-
in
src/main/java/com/guide/erp/exception/ResourceNotFoundException.java
package com.guide.erp.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException{ public ResourceNotFoundException(String message) { super(message); } }
-
in
src/main/java/com/guide/erp/controller/EmployeeController.java
//Get Employee by ID @GetMapping("/{id}") public ResponseEntity<EmployeeDto> getEmployeeById(@PathVariable String id) { EmployeeDto employee = employeeService.getEmployeeById(Long.parseLong(id)); return new ResponseEntity<>(employee, HttpStatus.OK); }
So, In these 4 steps you can easily handle any new HTTP request that you wish to serve.
Now, let's test the get request:
on curl -X GET http://localhost:8080/api/employee/1
you should get
{"empId":1,"empName":"Aaryan","empEmail":"aaryan@example.com","empPhone":"9876543210","empAddress":"Delhi","empPosition":"Engineer","empSalary":50000.0,"empDepartment":"Tech","empHireDate":"2024-06-01","managerId":1}
What is @PathVariable
?
@PathVariable
is a Spring annotation used to extract values from the URL path and bind them to method parameters in controller methods.
@PathVariable
does:
- Binds URL template variables to method parameters.
- Used for RESTful routes, like
/users/{userId}/orders/{orderId}
. - Can rename the variable using
@PathVariable("urlName")
.
With Custom Variable Name:
@GetMapping("/users/{uid}")
public String getUser(@PathVariable("uid") Long userId) {
// userId will have value of uid from URL
}
What if we wish to get all the employee by, any other parameter, say, name?
We will just have to write that custom query inside our repository, and then implement a service, write a new controller, handle some exceptions. and You're all set!
note: I have implemented a few other methods than the primary ones mentioned in this guide, feel free to check them out on the github repo!
3. Put Method to Update Employee
-
in
src/main/java/com/guide/erp/service/EmployeeService.java
:
EmployeeDto updateEmployee(Long id, EmployeeDto employeeDto);
-
in
src/main/java/com/guide/erp/service/impl/EmployeeServicesImpl.java
:
@Override public EmployeeDto updateEmployee(Long id, EmployeeDto employeeDto) { Employee existingEmployee = employeeRepository.findById(id).orElseThrow( () -> new ResourceNotFoundException("Employee not found with id: " + id) ); // Update the existing employee's fields with the new values from employeeDto existingEmployee.setEmpName(employeeDto.getEmpName()); existingEmployee.setEmpEmail(employeeDto.getEmpEmail()); existingEmployee.setEmpPhone(employeeDto.getEmpPhone()); existingEmployee.setEmpAddress(employeeDto.getEmpAddress()); existingEmployee.setEmpPosition(employeeDto.getEmpPosition()); existingEmployee.setEmpSalary(employeeDto.getEmpSalary()); existingEmployee.setEmpDepartment(employeeDto.getEmpDepartment()); existingEmployee.setEmpHireDate(employeeDto.getEmpHireDate()); if (employeeDto.getManagerId() != null) { Employee manager = new Employee(); manager.setEmpId(employeeDto.getManagerId()); existingEmployee.setManager(manager); } else { existingEmployee.setManager(null); } // Save the updated employee back to the repository existingEmployee = employeeRepository.save(existingEmployee); // Map the updated employee back to EmployeeDto and return it return EmployeeMapper.mapEmployeeToDto(existingEmployee); }
-
in
src/main/java/com/guide/erp/controller/EmployeeController.java
:
//Update Employee @PutMapping("/update/{id}") public ResponseEntity<EmployeeDto> updateEmployee(@PathVariable String id, @RequestBody EmployeeDto employee) { EmployeeDto updatedEmployee = employeeService.updateEmployee(Long.parseLong(id), employee); return new ResponseEntity<>(updatedEmployee, HttpStatus.OK); }
Now, let's test the Put request:
on
curl -X PUT http://localhost:8080/api/employee/update/1 \
-H "Content-Type: application/json" \
-d '{
"empName": "Aaryan",
"empEmail": "aaryansinha@example.com",
"empPhone": "9876543210",
"empAddress": "Delhi",
"empPosition": "Engineer",
"empSalary": 50000,
"empDepartment": "Tech",
"empHireDate": "2024-06-01",
"managerId": 1
}'
now on curl -X GET http://localhost:8080/api/employee/all
you should get
[{"empId":3,"empName":"Xyz","empEmail":"xyz@example.com","empPhone":"0011223344","empAddress":"Delhi","empPosition":"Engineer","empSalary":50000.0,"empDepartment":"Tech","empHireDate":"2024-06-01","managerId":1},{"empId":1,"empName":"Aaryan","empEmail":"aaryansinha@example.com","empPhone":"9876543210","empAddress":"Delhi","empPosition":"Engineer","empSalary":50000.0,"empDepartment":"Tech","empHireDate":"2024-06-01","managerId":1}]
**NOTE: an extra employee was added behind the scenes when testing, so please don't panic if you see some other output, focus on how the value have been updated.
4. Delete method to delete an Employee
-
in
src/main/java/com/guide/erp/service/EmployeeService.java
:
void deleteEmployee(Long id);
-
in
src/main/java/com/guide/erp/service/impl/EmployeeServicesImpl.java
:
@Override public void deleteEmployee(Long id){ Employee employee = employeeRepository.findById(id).orElseThrow( () -> new ResourceNotFoundException("No employee by the id: " + id) ); employeeRepository.deleteById(employee.getEmpId()); }
-
in
src/main/java/com/guide/erp/controller/EmployeeController.java
:
//Delete Employee @DeleteMapping(path = "/delete/{id}") public ResponseEntity<String> deleteEmployee(@PathVariable String id){ employeeService.deleteEmployee(Long.parseLong(id)); return new ResponseEntity<>("employee with id:" + id + " deleted Successfully",HttpStatus.OK); }
Now, let's test the DELETE request:
on
curl -X DELETE http://localhost:8080/api/employee/delete/3
now on curl -X GET http://localhost:8080/api/employee/all
you should get
[{"empId":1,"empName":"Aaryan","empEmail":"aaryansinha@example.com","empPhone":"9876543210","empAddress":"Delhi","empPosition":"Engineer","empSalary":50000.0,"empDepartment":"Tech","empHireDate":"2024-06-01","managerId":1}]
With this, you have finally learnt how to create RESTful API with Java, Spring Boot, Spring JPA and PostgreSQL.
Thanks for Reading, hope it was a delight to follow along.
Top comments (0)