Fullstack app: Developing the Back-End with Spring Boot and PostgreSQL
Matheus Bernardes Spilari
Posted on September 30, 2024
In this article we are going to create the backend that will be consumed by our Vite/React frontend.
Create the project
At the Spring Initializr create a project with the following dependencies:
- Spring Data JPA
- Spring Web
- PostgreSQL Driver
I'm using Java 21 with Maven, Jar and Spring Boot version 3.3.3.
The group name is com.spring, the artifact and name is backend.
Generate the project and extract the directory.
Move the backend
directory to the fullstackapp
directory you created when you build the frontend.
Code
1.Configuring application.properties
In your application.properties put the code below:
spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/database}
spring.datasource.username=user
spring.datasource.password=1234
spring.jpa.hibernate.ddl-auto=update
cors.allowed-origins=${CORS_ALLOWED_ORIGINS:http://localhost:5173}
2. Configs directory
Inside the directories java > com > spring > backend create the configs directory
, inside of that create a file called WebConfig.java
.
This file will be responsible for configuring CORS:
WebConfog.java
package com.spring.render.configs;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig {
private String allowedOrigins;
public WebConfig(@Value("${cors.allowed-origins}") String allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(allowedOrigins.split(","))
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*");
}
};
}
}
Explanation
- cors.allowed-origins: This is an environment variable that will be passed through Docker.
3. Models directory
Inside the directories java > com > spring > backend create the models directory
, inside of that create a file called MessageModel.java
.
This will be our model, with the respective getters, setters, and constructors.
MessageModel.java
package com.spring.render.models;
import java.util.UUID;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Table(name = "tb_messages")
@Entity
public class MessageModel {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String message;
public MessageModel() {
}
public MessageModel(String message) {
this.message = message;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
4. Repositories directory
Inside the directories java > com > spring > backend create the repositories directory
, inside of that create a file called MessageRepository.java
.
This file comes with some built-in methods for working with your database, such as save
, delete
, findAll
, and findById
. You can also write additional queries or methods inside the interface.
MessageRepository.java
package com.spring.render.repositories;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.spring.render.models.MessageModel;
@Repository
public interface MessageRepository extends JpaRepository<MessageModel, UUID> {
}
5. Services directory
Inside the directories java > com > spring > backend create the services directory
, inside of that create a file called MessageService.java
.
This is where the business logic of your app resides. We have three methods:
- allMessages: Retrieves all messages from the database.
- createMessage: Saves a new message to the database.
- deleteMessage: Deletes a message from the database based on its ID.
package com.spring.render.services;
import java.util.List;
import java.util.UUID;
import org.springframework.stereotype.Service;
import com.spring.render.models.MessageModel;
import com.spring.render.repositories.MessageRepository;
@Service
public class MessageService {
private MessageRepository messageRepository;
public MessageService(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
public List<MessageModel> allMessages() {
return messageRepository.findAll();
}
public MessageModel createMessage(String message) {
var newMessage = new MessageModel(message);
return messageRepository.save(newMessage);
}
public void deleteMessage(String messageId) {
messageRepository.deleteById(UUID.fromString(messageId));
}
}
6. Controllers directory
Inside the directories java > com > spring > backend create the controllers directory
, inside of that create a file called MessageController.java
.
Here we have three endpoints to our api:
- /allmessages: Retrieve all messages from the database.
- /sendmessage: To send a new message to the database.
- /deletemessage/{id}: Delete a message passing his ID as a parameter.
MessageController.java
package com.spring.render.controllers;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.spring.render.dto.MessageDto;
import com.spring.render.models.MessageModel;
import com.spring.render.services.MessageService;
@RestController
public class MessageController {
private MessageService messageService;
public MessageController(MessageService messageService) {
this.messageService = messageService;
}
@GetMapping("/allmessages")
public ResponseEntity<List<MessageModel>> getAllMessages() {
var messageList = messageService.allMessages();
return ResponseEntity.ok().body(messageList);
}
@PostMapping("/sendmessage")
public ResponseEntity<MessageModel> postMessage(@RequestBody MessageDto messageDto) {
var newMessage = messageService.createMessage(messageDto.message());
return ResponseEntity.status(HttpStatus.CREATED).body(newMessage);
}
@DeleteMapping("/deletemessage/{id}")
public ResponseEntity<String> deleteMessage(@PathVariable("id") String id) {
messageService.deleteMessage(id);
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
}
}
7. Create the Dockerfile
Inside the backend
directory, create the Dockerfile, which will assist with deployment.
If you are using another version of Java, change the image version from eclipse-temurin
.
I'm using the Alpine version of the image because it's lightweight. Install additional packages if needed.
Dockerfile
FROM eclipse-temurin:21-jdk-alpine
COPY . .
RUN ./mvnw clean install -DskipTests
ENTRYPOINT ["java","-jar","target/render-0.0.1-SNAPSHOT.jar"]
Conclusion
With that, our backend is ready to be consumed by our frontend and is prepared for deployment.
We will cover the deployment in the next article.
Thanks for reading !!
📍 Reference
👋 Talk to me
Posted on September 30, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 30, 2024