Hi everyone! Today, we are going to make a basic chat application using Spring Boot and React.js. We'll follow the steps assuming you're a complete beginner and only know the basics of Spring Boot and React.js.
First, we’ll create a Spring Boot project. Go to the website https://start.spring.io/.
Currently, I am using Java 17, so I have chosen Java 17 and selected Maven as the build tool. I’m adding some basic dependencies for now; we’ll add more as needed later. After setting everything up, click on "Generate" to download the file. Once it’s downloaded, extract it and open it in your editor. I’m going to use IntelliJ IDEA.
- config
- controller
- dto
- model
- repository
- service
Create a User Entity inside model
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String email;
@Column(unique = true)
private String userName;
private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getUsername() {
return userName;
}
}
I won’t go into detail about what's written here, but since we’ll be using JWT authentication, don’t forget to implement UserDetails. If you’re not familiar with authentication and authorization, you can refer to other blogs for a deeper understanding.
After this, you will encounter an error because we haven’t added the Spring Security dependencies. Let’s add these dependencies first, and we will add more as we progress.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>6.3.1</version>
</dependency>
Now Create Another entity MessageEntity as below
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@Entity
@Table(name = "messages")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class MessageEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "sender")
private String sender;
@Column(name = "receiver")
private String receiver;
@Column(name = "message")
private String message;
@Column(name = "timestamp")
private LocalDateTime timestamp;
}
So far, we have created two entities, but no tables have been created in the database yet. I’m using MySQL, but you can choose your preferred database and add dependencies accordingly. Now, it’s time to set up the application.properties file.
When you run your program in MySQL Workbench, two tables will be created as shown above.
now we have created our entity now lets cerate controller to register and log in.
so inside controller create a class name as AuthenticationController below
@RestController
@CrossOrigin
public class AuthenticationController {
@Autowired
private AuthenticationService authService;
@PostMapping("/register")
public ResponseEntity<AuthenticationResponse> register(
@RequestBody User request
) {
return ResponseEntity.ok(authService.register(request));
}
@PostMapping("/login")
public ResponseEntity<AuthenticationResponse> login(
@RequestBody User request
) {
return ResponseEntity.ok(authService.authenticate(request));
}
now we are going to see a lot of red flag here beacause we had not created repository and service class so lets create it but before this lets add some securityconfig inside config like this.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserService userService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
}
Now let's create the AuthenticationService and UserService. You may see some red flags because we are using certain elements in these services that we haven't implemented yet. Don't worry about that; we will cover them in detail later.
AuthenticationService
@Service
public class AuthenticationService {
@Autowired
private UserRepository repository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtService jwtService;
@Autowired
private AuthenticationManager authenticationManager;
public AuthenticationResponse register(User request) {
User user = new User();
user.setName(request.getName());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setUserName( request.getUsername());
user.setEmail(request.getEmail());
repository.save(user);
String token = jwtService.generateToken(user);
return new AuthenticationResponse(token);
}
public AuthenticationResponse authenticate(User request) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
User user= repository.findByUserName(request.getUsername());
String token=jwtService.generateToken(user);
return new AuthenticationResponse(token);
}
}
UserService
@Repository
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User addUser(User user) {
userRepository.save(user);
return user;
}
public List<User> getUsers() {
return userRepository.findAll();
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUserName(username);
}
}
Now, since we have not created a repository yet, let's create one. By using a repository, we can manage the database. So, inside the repository package, create two repositories: UserRepository and MessageRepository.
UserRepository
public interface UserRepository extends JpaRepository<User,Long>{
User findByUserName(String username);
}
MessageRepository
public interface MessageRepository extends JpaRepository<MessageEntity,Long> {
@Query("SELECT m FROM MessageEntity m WHERE (m.sender = :sender AND m.receiver = :receiver) OR (m.sender = :receiver AND m.receiver = :sender)")
List<MessageEntity> findMessages(@Param("sender") String sender, @Param("receiver") String receiver);
}
Now, we've moved ahead, but since we're using JWT authentication and haven't implemented it yet, let's import the necessary dependencies and create a service and filter for it. We need to add some dependencies, so let's include them.
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- to use Jackson for JSON parsing -->
<version>0.11.5</version>
</dependency>
now lets create jwtservice class
@Service
public class JwtService {
private final String Private_Key="4bb6d1dfbafb64a681139d1586b6f1160d18159afd57c8c79136d7490630407c";
public <T> T extractClaim(String token, Function<Claims, T> resolver) {
Claims claims = extractAllClaims(token);
System.out.println(claims);
return resolver.apply(claims);
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public boolean isValid(String token, UserDetails user) {
String username = extractUsername(token);
return (username.equals(user.getUsername())) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private Claims extractAllClaims(String token) {
return
Jwts.parserBuilder()
.setSigningKey(getSigninKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public String generateToken(User user) {
String token = Jwts
.builder()
.setSubject(user.getUsername())
.claim("id", user.getId()) // Add user ID as a claim
.claim("firstName", user.getName())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 24*60*60*1000 ))
.signWith(getSigninKey())
.compact();
return token;
}
private Key getSigninKey() {
byte[] keyBytes = Decoders.BASE64URL.decode(Private_Key);
return Keys.hmacShaKeyFor(keyBytes);
}
}
now create jwt filter in starting i forget to create the filter package so lets first create that package and inside that create jwt filter with name JwtAuthenticationFilter
JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserService userService;
public JwtAuthenticationFilter(JwtService jwtService, UserService userService) {
this.jwtService = jwtService;
this.userService = userService;
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if(authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request,response);
return;
}
String token = authHeader.substring(7);
String username = jwtService.extractUsername(token);
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
if(jwtService.isValid(token, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
in AuthenticationController we are using AuthenticationResponse this is dto so lets create that also inside dto cerate this class
package com.example.Messenger.dto;
public class AuthenticationResponse {
private String token;
public AuthenticationResponse(String token) {
this.token = token;
}
public String getToken() {
return token;
}
}
So, we have mostly completed our main agenda, but it is still missing. Let's move forward with it, but before that, let's add the WebSocket dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
ater adding we have to configure the reduied congifartion for using this so lets add inside config lets add
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// Enable CORS for WebSocket connections
registry.addEndpoint("/ws")
.setAllowedOrigins("http://localhost:3000")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
}
"I will explain everything separately, but currently, our main focus is on building the application. I will add some links at the end which will explain everything in more detail
@Slf4j
@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class ChatController {
@Autowired
private MessageRepository messageRepository;
@Autowired
private SimpMessagingTemplate messagingTemplate; // Add this line
@MessageMapping("/chat")
public void sendMessage(MessageEntity message) {
message.setTimestamp(LocalDateTime.now());
messageRepository.save(message);
log.info(" I am inside send message");
log.error("something wrong");
// Send message to both sender and receiver
messagingTemplate.convertAndSend("/topic/messages/" + message.getReceiver(), message);
messagingTemplate.convertAndSend("/topic/messages/" + message.getSender(), message);
}
@GetMapping("/api/messages")
public List<MessageEntity> getMessages(
@RequestParam String sender,
@RequestParam String receiver) {
return messageRepository.findMessages(sender, receiver);
}
}
Top comments (0)