DEV Community

Cover image for Guide - Making RESTful APIs in Springboot
Aaryan Kumar Sinha
Aaryan Kumar Sinha

Posted on

Guide - Making RESTful APIs in Springboot

Spring Boot Project

Guide to building a RESTful API using Spring Boot and PostgreSQL.


A brief flow chart on how it will all work:

App Architecture


Project Settings:

  1. Project
    • Maven
  2. Language
    • Java 21
  3. Springboot
    • Version: 3.5.3

Dependencies Used:

  • Spring Web
  • Spring Data JPA
  • PostgreSQL Data Driver
  • Lombok

Full Code Available at:

GitHub logo aaryansinhaa / restful-guide

A guide on making RESTful API using Java+ Springboot and PGSQL


1. Setting Up PostgreSQL:

  1. Sudo into your postgres user to use psql:


    sudo -u postgres -i

  2. Once inside your postgres user, open up psql

    • psql
  3. 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 importing DotEnv in your pom.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
Enter fullscreen mode Exit fullscreen mode

3. Packages

Inside src/main/java/com/guide/erp/:

Project Structure
The Packages are:

  • 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 to product 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

  1. 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;
}
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

This is because of the Cyclic mapping at:

@Column(name = "emp_manager")
private Employee manager;
Enter fullscreen mode Exit fullscreen mode

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;`
Enter fullscreen mode Exit fullscreen mode

You are now ready to run the application.

Output
ignore the warnings for now

After running the application, Check the pgSQL database,

pgSQL Updates

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);
Enter fullscreen mode Exit fullscreen mode

This will generate:

SELECT * FROM employee WHERE emp_department = ?;
Enter fullscreen mode Exit fullscreen mode

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> {}
Enter fullscreen mode Exit fullscreen mode

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 
}
Enter fullscreen mode Exit fullscreen mode

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;

}
Enter fullscreen mode Exit fullscreen mode

Now, after you have made an EmployeeDto, we would want to make an Employee mapper, to map:

we shall create a new package:

packages
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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

Service Layer
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);
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

What does the above code snippet do?

  1. It takes an EmployeeDto object as input.
  2. It maps the DTO to an Employee entity using EmployeeMapper.
  3. It saves the Employee entity to the database using employeeRepository.
  4. It maps the saved Employee entity back to an EmployeeDto and returns it as the result. Here is a flowchart:

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);

    }
}
Enter fullscreen mode Exit fullscreen mode

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?

  1. Returns data directly (usually JSON or XML) instead of rendering a view (like Thymeleaf or JSP).
  2. Automatically serializes Java objects to JSON using Jackson.
  3. 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
         }'
Enter fullscreen mode Exit fullscreen mode

Now, let's query our employee table and check if it got added to the database:

SQL Output
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:

Workflow


2. Get Methods for Employee

  1. in src/main/java/com/guide/erp/service/EmployeeService.java:

    EmployeeDto getEmployeeById(Long id);
    
  2. 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);
    }
    
  3. 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);
    }
    }
    
  4. 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}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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

  1. in src/main/java/com/guide/erp/service/EmployeeService.java:

    EmployeeDto updateEmployee(Long id, EmployeeDto employeeDto);
    
  2. 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);
    }
    
  3. 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
         }'
Enter fullscreen mode Exit fullscreen mode

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}]
Enter fullscreen mode Exit fullscreen mode

**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

  1. in src/main/java/com/guide/erp/service/EmployeeService.java:

    void deleteEmployee(Long id);
    
  2. 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());
    
    }
    
  3. 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
Enter fullscreen mode Exit fullscreen mode

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}]
Enter fullscreen mode Exit fullscreen mode

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)