DEV Community

Cover image for Full Stack Reddit Clone - Spring Boot, React, Electron App - Part 2
MaxiCB
MaxiCB

Posted on • Updated on

Full Stack Reddit Clone - Spring Boot, React, Electron App - Part 2

Full Stack Reddit Clone - Spring Boot, React, Electron App - Part 2

Introduction

Welcome to Part 2 of creating a Reddit clone using Spring Boot, and React.

In Part 1 we initialized our project, and added all of the dependencies we will need. In this article we will cover creating all of the entities and repositories we will need to complete our backend!

Important Links

Part 1: Creating Domain Entities ๐Ÿ“

Let's cover all of the different domain entities our application will have. Inside com.your-name.backend create a new package called models, and add the following classes.

Note: We installed Lombok as a dependency in part 1. We will be using varying Lombok Annotations throughout the development process. To access these annotations you will have to enable Annotation Processing in your IDE. For further instructions on this, you can view the Setting up Lombok guide here - https://www.baeldung.com/lombok-ide

Note: In some cases you may need to add the following dependency to your pom.xml file for field validation

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode
  • User: Have a unique userId, a username, password, emailAddress, creationDate, accountStatus
  package com.maxicb.backend.model;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import javax.persistence.*;
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotBlank;
    import java.time.Instant;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Table(name = "users")
    @Entity
    public class User {
        @Id
        @SequenceGenerator(name = "USER_GEN", sequenceName = "SEQ_USER", allocationSize = 1)
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_GEN")
        private Long userId;
        @NotBlank(message = "Username is required")
        private String username;
        @NotBlank(message = "Password is required")
        private String password;
        @Email
        @NotBlank(message = "Email is required")
        private String email;
        private Instant creationDate;
        private boolean accountStatus;
    }

Enter fullscreen mode Exit fullscreen mode
  • Post: Have a unique postId, postName, url, description, voteCount, user, creationDate, subreddit
  package com.maxicb.backend.model;

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.lang.Nullable;

    import javax.persistence.*;
    import javax.validation.constraints.NotBlank;
    import java.time.Instant;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Entity
    public class Post {
        @Id
        @SequenceGenerator(name = "POST_GEN", sequenceName = "SEQ_POST", allocationSize = 1)
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "POST_GEN")
        private Long postId;
        @NotBlank(message = "Post Title is required")
        private String postTitle;
        @Nullable
        private String url;
        @Nullable
        @Lob
        private String description;
        private Integer voteCount;
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "userId", referencedColumnName = "userId")
        private User user;
        private Instant creationDate;
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "id", referencedColumnName = "id")
        private Subreddit subreddit;
    }

Enter fullscreen mode Exit fullscreen mode
  • Subreddit: Have a unique id, name, description, posts, creationDate, users
  package com.maxicb.backend.model;

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import javax.persistence.*;
    import javax.validation.constraints.NotBlank;
    import java.time.Instant;
    import java.util.List;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Entity
    public class Subreddit {
        @Id
        @SequenceGenerator(name = "SUB_GEN", sequenceName = "SEQ_SUB", allocationSize = 1)
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SUB_GEN")
        private Long id;
        @NotBlank(message = "Subreddit name is required")
        private String name;
        @NotBlank(message = "Subreddit description is required")
        private String description;
        @OneToMany(fetch = FetchType.LAZY)
        private List<Post> posts;
        private Instant creationDate;
        @ManyToOne(fetch = FetchType.LAZY)
        private User user;
    }

Enter fullscreen mode Exit fullscreen mode
  • Vote: Have a unique id, post, user
  package com.maxicb.backend.model;

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import javax.persistence.*;
    import javax.validation.constraints.NotNull;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Entity
    public class Vote {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        private Long voteId;
        private VoteType voteType;
        @NotNull
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "postId", referencedColumnName = "postId")
        private Post post;
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "userId", referencedColumnName = "userId")
        private User user;
    }

Enter fullscreen mode Exit fullscreen mode
  • Comment: Have a unique id, text, post, creationDate, user
  package com.maxicb.backend.model;

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import javax.persistence.*;
    import javax.validation.constraints.NotEmpty;
    import java.time.Instant;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Entity
    public class Comment {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        private Long id;
        @NotEmpty
        private String text;
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "postId", referencedColumnName = "postId")
        private Post post;
        private Instant creationDate;
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "userId", referencedColumnName = "userId")
        private User user;
    }

Enter fullscreen mode Exit fullscreen mode
  • VoteType ENUM: Upvote, Downvote
    public enum VoteType {
        UPVOTE(1), DOWNVOTE(-1);

        VoteType(int direction) {}
    }

Enter fullscreen mode Exit fullscreen mode
  • AccountVerificationToken: Have a unique id, token, user, expirationDate
  package com.maxicb.backend.model;

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import javax.persistence.*;
    import java.time.Instant;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Table(name = "token")
    @Entity
    public class AccountVerificationToken {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String token;
        @OneToOne(fetch = FetchType.LAZY)
        private User user;
        private Instant expirationDate;
    }

Enter fullscreen mode Exit fullscreen mode
  • NotificationEmail: subject, recepient, body
  package com.maxicb.backend.model;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class NotificationEmail {
        private String subject;
        private String recepient;
        private String body;
    }

Enter fullscreen mode Exit fullscreen mode

Part 2: Creating Repositories ๐Ÿ‘ฉโ€

Now we need to cover the repositories which will be responsible for storing the entites in the database. Inside com.you-name.backend create a new package called repository, and add the following interfaces.

  • User Repository:
    package com.maxicb.backend.repository;

    import com.maxicb.backend.model.User;
    import org.springframework.data.repository.CrudRepository;

    import java.util.Optional;

    public interface UserRepository extends CrudRepository<User, Long> {
        Optional<User> findByUsername(String username);
    }
Enter fullscreen mode Exit fullscreen mode
  • Post Repository:
    package com.maxicb.backend.repository;

    import com.maxicb.backend.model.Post;
    import com.maxicb.backend.model.Subreddit;
    import com.maxicb.backend.model.User;
    import org.springframework.data.repository.CrudRepository;

    import java.util.List;

    public interface PostRepository extends CrudRepository<Post, Long> {
        List<Post> findAllBySubreddit(Subreddit subreddit);

        List<Post> findByUser(User user);
    }
Enter fullscreen mode Exit fullscreen mode
  • Subreddit Repository:
  package com.maxicb.backend.repository;

  import com.maxicb.backend.model.Subreddit;
  import org.springframework.data.repository.CrudRepository;

  import java.util.Optional;

  public interface SubredditRepository extends CrudRepository<Subreddit, Long> {
      Optional<Subreddit> findByName(String subredditName);
  }
Enter fullscreen mode Exit fullscreen mode
  • Vote Repository:
    package com.maxicb.backend.repository;

    import com.maxicb.backend.model.Post;
    import com.maxicb.backend.model.User;
    import com.maxicb.backend.model.Vote;
    import org.springframework.data.repository.CrudRepository;

    import java.util.Optional;

    public interface VoteRepository extends CrudRepository<Vote, Long> {
        Optional<Vote> findTopByPostAndUserOrderByVoteIdDesc(Post post, User currentUser);
    }
Enter fullscreen mode Exit fullscreen mode
  • Comment Repository:
    package com.maxicb.backend.repository;

    import com.maxicb.backend.model.Comment;
    import com.maxicb.backend.model.Post;
    import com.maxicb.backend.model.User;
    import org.springframework.data.repository.CrudRepository;

    import java.util.List;

    public interface CommentRepository extends CrudRepository<Comment, Long> {
        List<Comment> findByPost(Post post);

        List<Comment> findAllByUser(User user);
    }
Enter fullscreen mode Exit fullscreen mode
  • Token Repository:
  package com.maxicb.backend.repository;

import com.maxicb.backend.model.AccountVerificationToken;
import org.springframework.data.repository.CrudRepository;

import java.util.Optional;

public interface TokenRepository extends CrudRepository<AccountVerificationToken, Long> {
    Optional<AccountVerificationToken> findByToken(String token);
}
Enter fullscreen mode Exit fullscreen mode

Conclusion ๐Ÿ”

  • To ensure everything is configured correctly you can run the application, and ensure there are no error in the console. Towards the bottom of the console you should see output similar to below

alt text

  • In this article we created the entities, and repositories needed within our Spring Boot backend. Laying the foundation for all of the logic that will follow.

Next

Part 3 Implementing registration, email sending, and account activation/verification.

Discussion (8)

Collapse
sproket profile image
sproket

I was trying to create the models without lombok for User and AccountVerificationToken. I generated equals and hashcode and tostring with netbeans but I get org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing

Have you been able to do it without lombok?

Collapse
vuongddang profile image
Vuong Dang

Awesome posts ๐Ÿ‘, looking forward to the React and Electron part. Wondering if you gonna use Next.js or Create React App.

Collapse
maxicb profile image
MaxiCB Author

Hey Voung, thank you for the feedback! I haven't used Next.js before, and planned on using Create React App for the project. Maybe when I've messed with Next.js a bit more, in the future I can make a follow up using it!

Collapse
vuongddang profile image
Vuong Dang

That's cool! Thank you.

Collapse
gnuchu profile image
John Griffiths • Edited

Had to add the javax.validation dependency to get it to build and the TokenRepository code on the page has the SubredditRepository.java code in it.

Collapse
maxicb profile image
MaxiCB Author • Edited

Yeah, I missed it when I made part one, I'll have to go update the post. It's only used in a few places, and I completely blanked on it even being used at all when writing it ๐Ÿค• And nice catch! Ill get that fixed!!

Collapse
gnuchu profile image
John Griffiths

No worries - and thank you - am enjoying working through it.

Collapse
connectwithajayy profile image
Ajay Gupta

why you useed react instead of angular.
I am expecting why fact.