DEV Community

Himanshu Singh
Himanshu Singh

Posted on

Chat Application with Spring boot And React JS

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/.
Image description
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.

Image description
Create the Following Packages

  1. config
  2. controller
  3. dto
  4. model
  5. repository
  6. 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;
  }


}
Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

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.
Image description
Image description
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));
    }



Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

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

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


}

Enter fullscreen mode Exit fullscreen mode

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


    }



Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

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

Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Top comments (0)