Spring Boot CRUD Operations with Supabase
Spring Boot is widely used for building REST APIs and backend applications in Java. When an application needs persistent storage, Spring Boot can connect to traditional databases such as PostgreSQL, MySQL, or Oracle. Supabase provides another convenient option because it offers a managed PostgreSQL database along with several additional backend services. In this tutorial, we will build a simple Spring Boot CRUD REST API and connect it to a PostgreSQL database hosted on Supabase. The application will manage employee records and demonstrate how to create, read, update, and delete data. The example intentionally keeps the architecture simple. We will use Spring Web, Spring Data JPA, PostgreSQL, and Supabase. No unnecessary abstraction or additional framework is required.
1. Overview
1.1 What is Supabase?
Supabase is a backend development platform built around PostgreSQL. A Supabase project provides a managed PostgreSQL database and can also provide features such as authentication, storage, realtime functionality, and APIs. An important point for Spring Boot developers is that the Supabase database is still PostgreSQL. Therefore, a Spring Boot application can connect to it using the normal PostgreSQL JDBC driver and Spring Data JPA.
1.2 Creating a Supabase Project
- Open Supabase and create an account.
- Create a new project.
- Choose a project name.
- Configure a database password.
- Select an appropriate region.
- Wait for the project and PostgreSQL database to be provisioned.
After creating the project, obtain the PostgreSQL connection information from the project’s database/connect settings. Depending on the connection option selected in Supabase, you will receive values such as the database host, port, database name, username, and password. Do not commit the real database password to source control. For this tutorial, placeholders are used in the configuration.
1.3 Creating the Employee Table
Open the SQL Editor in Supabase and execute the following SQL:
CREATE TABLE employees (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL UNIQUE,
department VARCHAR(100)
);
The BIGSERIAL column generates the employee ID automatically. The email column is marked as unique so that two employees cannot use the same email address.
1.4 Adding Dummy Data
Insert a few records so that the GET API can be tested immediately:
INSERT INTO employees (name, email, department)
VALUES
('Amit Sharma', 'amit@example.com', 'Engineering'), ('Priya Singh', 'priya@example.com', 'Finance'), ('Rahul Verma', 'rahul@example.com', 'Operations');.
You can verify the records using:
SELECT * FROM employees;
2. Spring Boot CRUD Code Example
Create a Spring Boot application using Java 17 or later and add the following dependencies: Spring Web, Spring Data JPA, PostgreSQL Driver.
2.1 Maven Dependencies
To build the Spring Boot application and connect it with Supabase, we need a few essential Maven dependencies. These dependencies provide support for creating REST APIs, working with databases through JPA, and establishing a connection with the PostgreSQL database provided by Supabase. If Maven is being used, add the following dependencies to the pom.xml file:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
In the above configuration, spring-boot-starter-web provides the components required to build REST APIs and handle HTTP requests, while spring-boot-starter-data-jpa provides Spring Data JPA and Hibernate support for performing database operations using Java entities and repositories. The postgresql dependency provides the PostgreSQL JDBC driver that allows the Spring Boot application to establish a database connection with Supabase. Its scope is set to runtime because the driver is required when the application runs rather than for compiling the application code. Together, these three dependencies provide everything required for the basic Spring Boot, JPA, and Supabase PostgreSQL integration used in this CRUD example.
2.2 Configuring the Supabase Database
After adding the required Maven dependencies, the next step is to configure the connection between the Spring Boot application and the Supabase PostgreSQL database. Spring Boot reads database connection settings from the application.properties file and uses them to automatically configure the datasource and JPA. Add the following database configuration to src/main/resources/application.properties. Replace the placeholders with the actual connection details provided by your Supabase project.
spring.datasource.url=jdbc:postgresql://YOUR_HOST:5432/postgres spring.datasource.username=YOUR_USERNAME spring.datasource.password=YOUR_PASSWORD spring.jpa.hibernate.ddl-auto=validate spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true
In the above configuration, spring.datasource.url specifies the JDBC connection URL of the Supabase PostgreSQL database, while spring.datasource.username and spring.datasource.password provide the credentials required to establish the connection. Replace YOUR_HOST, YOUR_USERNAME, and YOUR_PASSWORD with the values provided in your Supabase database connection settings. The spring.jpa.hibernate.ddl-auto=validate property tells Hibernate to verify that the existing database schema matches the JPA entities without creating or modifying tables. The spring.jpa.show-sql=true property displays SQL statements generated by Hibernate in the application console, which is useful while testing the CRUD operations, and spring.jpa.properties.hibernate.format_sql=true formats those SQL statements to make them easier to read.
2.3 Creating the Main Application Class
Next, create the main application class that serves as the entry point for the Spring Boot application. This class starts the application, initializes the Spring context, and enables Spring Boot’s automatic configuration and component scanning. Create a file named SupabaseApplication.java and add the following code:
package com.example.supabase;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SupabaseApplication {
public static void main(String[] args) {
SpringApplication.run(SupabaseApplication.class, args);
}
}
In the above code, the @SpringBootApplication annotation marks SupabaseApplication as the main Spring Boot configuration class. It combines commonly used Spring annotations for configuration, auto-configuration, and component scanning, allowing Spring Boot to automatically discover components such as controllers and repositories within the application package. The main() method is the standard Java entry point, while SpringApplication.run() starts the Spring Boot application, creates the application context, performs the required auto-configuration, and starts the embedded web server so that the REST endpoints can accept HTTP requests.
2.4 Creating the Employee Entity
Next, create the Employee entity class that represents the employee records stored in the Supabase PostgreSQL database. Using JPA annotations, this Java class is mapped to the employees table created earlier, allowing Spring Data JPA and Hibernate to perform database operations using Java objects. Create a file named Employee.java inside the com.example.supabase.entity package and add the following code:
package com.example.supabase.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, unique = true)
private String email;
private String department;
public Employee() {
}
public Employee(String name, String email, String department) {
this.name = name;
this.email = email;
this.department = department;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
}
In the above code, the @Entity annotation marks the Employee class as a JPA entity, while @Table(name = "employees") maps it to the employees table in Supabase. The id field is marked with @Id to identify it as the primary key, and @GeneratedValue(strategy = GenerationType.IDENTITY) indicates that its value is automatically generated by PostgreSQL when a new employee is inserted. The name and email fields use @Column(nullable = false) to make them mandatory, while unique = true ensures that each email address is unique. The department field is mapped automatically to a column with the same name. The no-argument constructor is required by JPA when creating entity instances, while the parameterized constructor provides a convenient way to create new employees. Finally, the getter and setter methods allow the application and Hibernate to read and modify the entity’s field values during CRUD operations.
2.5 Creating the Repository
After creating the Employee entity, the next step is to create a repository that provides access to employee data stored in the Supabase PostgreSQL database. Spring Data JPA simplifies this process by providing built-in CRUD operations, so we do not need to write SQL queries or database access code for common operations. Create a file named EmployeeRepository.java inside the com.example.supabase.repository package and add the following code:
package com.example.supabase.repository;
import com.example.supabase.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
In the above code, EmployeeRepository is an interface that extends Spring Data JPA’s JpaRepository. The first generic parameter, Employee, specifies the entity managed by the repository, while Long specifies the data type of the entity’s primary key. By extending JpaRepository, the repository automatically inherits commonly required database methods such as save() for creating and updating employees, findAll() for retrieving all employees, findById() for retrieving an employee by ID, existsById() for checking whether an employee exists, and deleteById() for deleting an employee. Spring Data JPA creates the repository implementation automatically at runtime, allowing the application to perform CRUD operations against the Supabase PostgreSQL database without manually implementing the repository or writing SQL queries.
2.6 Creating the REST Controller
Now that the entity and repository are ready, the next step is to create a REST controller that exposes the employee CRUD operations through HTTP endpoints. The controller receives requests from clients, uses EmployeeRepository to interact with the Supabase PostgreSQL database, and returns the appropriate data and HTTP responses. Create a file named EmployeeController.java inside the com.example.supabase.controller package and add the following code:
package com.example.supabase.controller;
import com.example.supabase.entity.Employee;
import com.example.supabase.repository.EmployeeRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
private final EmployeeRepository employeeRepository;
public EmployeeController(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@PostMapping
public Employee createEmployee(@RequestBody Employee employee) {
return employeeRepository.save(employee);
}
@GetMapping
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Employee> getEmployeeById(@PathVariable Long id) {
return employeeRepository.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PutMapping("/{id}")
public ResponseEntity<Employee> updateEmployee(
@PathVariable Long id,
@RequestBody Employee employeeDetails) {
return employeeRepository.findById(id)
.map(employee -> {
employee.setName(employeeDetails.getName());
employee.setEmail(employeeDetails.getEmail());
employee.setDepartment(employeeDetails.getDepartment());
Employee updatedEmployee =
employeeRepository.save(employee);
return ResponseEntity.ok(updatedEmployee);
})
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteEmployee(@PathVariable Long id) {
if (!employeeRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
employeeRepository.deleteById(id);
return ResponseEntity.noContent().build();
}
}
In the above code, @RestController marks EmployeeController as a REST controller, while @RequestMapping("/api/employees") defines the common URL path for all employee endpoints. The EmployeeRepository is provided through constructor injection and is used to communicate with the Supabase PostgreSQL database. The @PostMapping endpoint accepts employee data through @RequestBody and uses save() to create a new record. The first @GetMapping uses findAll() to retrieve all employees, while @GetMapping("/{id}") uses @PathVariable and findById() to retrieve a specific employee, returning HTTP 404 Not Found when the record does not exist. The @PutMapping("/{id}") endpoint retrieves the existing employee, updates its name, email, and department, and calls save() to persist the changes. Finally, @DeleteMapping("/{id}") checks whether the employee exists using existsById() before deleting it with deleteById(); a successful deletion returns HTTP 204 No Content, while a missing employee returns HTTP 404 Not Found. Together, these endpoints provide the complete create, read, update, and delete functionality required for the example.
2.7 Code Run and Code Output
After completing the database configuration and creating the required Spring Boot classes, the application is ready to run. Make sure the Supabase project is active, the employees table exists, and the correct database connection details are configured in application.properties. Open a terminal in the project directory and start the application using the following Maven command:
mvn spring-boot:run
If the application starts successfully, Spring Boot connects to the Supabase PostgreSQL database, validates the employees table against the Employee entity, and starts the embedded web server on port 8080. The REST API can then be accessed using tools such as Postman, cURL, or any HTTP client. A successful startup produces output similar to the following:
Tomcat initialized with port 8080 (http) HikariPool-1 - Starting... HikariPool-1 - Start completed. Initialized JPA EntityManagerFactory Tomcat started on port 8080 (http) Started SupabaseApplication
Once the application is running, send a GET request to http://localhost:8080/api/employees to retrieve the dummy employee records inserted earlier. The response should look similar to the following:
[
{
"id": 1,
"name": "Amit Sharma",
"email": "amit@example.com",
"department": "Engineering"
},
{
"id": 2,
"name": "Priya Singh",
"email": "priya@example.com",
"department": "Finance"
},
{
"id": 3,
"name": "Rahul Verma",
"email": "rahul@example.com",
"department": "Operations"
}
]
The above output confirms that the Spring Boot application has successfully connected to the Supabase PostgreSQL database and retrieved employee records through the REST API. You can similarly test the POST, GET by ID, PUT, and DELETE endpoints to verify the complete CRUD functionality. Because spring.jpa.show-sql=true is enabled, the SQL statements generated by Hibernate will also appear in the application console while these operations are performed.
3. Conclusion
Integrating Spring Boot with Supabase is straightforward because Supabase provides a PostgreSQL database that can be accessed using the standard PostgreSQL JDBC driver. Existing Spring technologies such as Spring Data JPA and Hibernate can therefore be used without introducing a special database abstraction. In this example, we created an employees table in Supabase, inserted dummy data, configured a Spring Boot application to connect to the database, created a JPA entity and repository, and exposed REST endpoints for create, read, update, and delete operations. The resulting application uses a deliberately small architecture where the REST Controller communicates with the Spring Data JPA Repository, which interacts with the Supabase PostgreSQL database. This is sufficient for understanding the core integration and for small applications. As an application grows, additional concerns such as DTOs, validation, exception handling, a service layer, migrations, automated tests, and secure secret management can be introduced when they provide a concrete benefit.




