- Docker (for PostgreSQL)
- Java 17+ (e.g. Java 21 via SDKMAN)
- Maven 3.x
- Node.js 18+
docker compose up -dThis starts a PostgreSQL instance on port 5432.
cd backend
JAVA_HOME=/path/to/java17+ mvn spring-boot:runThe Spring Boot API will start on http://localhost:8080. Mock data is automatically seeded on first run.
Note: There is no Maven wrapper (
mvnw) — use a systemmvninstall. Spring Boot 3.x requires Java 17 or higher.
cd frontend
npm install
npm run devThe React app will start on http://localhost:5173 (or the next available port).
This project is an alternative to Reddit built with:
- React frontend
- Spring Boot backend
- PostgreSQL database
It supports the core features required for an MVP:
- Users
- Communities (like subreddits)
- Posts
- Threaded comments (nested)
- Voting
- Read-heavy system → optimize for fast reads
- Use denormalized fields (score, comment count)
- Avoid complex recursion in SQL
- Use materialized path for comment threading
root/
frontend/
backend/
Use Spring Initializr with:
- Spring Web
- Spring Data JPA
- PostgreSQL Driver
- Lombok (optional)
spring:
datasource:
url: jdbc:postgresql://localhost:5432/comunatee
username: postgres
password: password
jpa:
hibernate:
ddl-auto: update
show-sql: true@Entity
public class User {
@Id @GeneratedValue
private Long id;
@Column(unique = true)
private String username;
private String passwordHash;
private String profilePicUrl;
private int postRating = 0;
private int commentRating = 0;
}@Entity
public class Community {
@Id @GeneratedValue
private Long id;
@Column(unique = true)
private String name;
private String description;
@ManyToOne
private User creator;
private int subscriberCount = 0;
}@Entity
public class Post {
@Id @GeneratedValue
private Long id;
@ManyToOne
private Community community;
@ManyToOne
private User author;
private String title;
@Column(columnDefinition = "TEXT")
private String body;
private int score = 0;
private int commentCount = 0;
private LocalDateTime createdAt = LocalDateTime.now();
}@Entity
public class Comment {
@Id @GeneratedValue
private Long id;
@ManyToOne
private Post post;
@ManyToOne
private User author;
private Long parentId; // null for top-level
private String path; // e.g. "1.5.9"
private int depth; // nesting level
@Column(columnDefinition = "TEXT")
private String body;
private int score = 0;
private boolean isDeleted = false;
private LocalDateTime createdAt = LocalDateTime.now();
}@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "post_id", "comment_id"}))
public class Vote {
@Id @GeneratedValue
private Long id;
@ManyToOne
private User user;
private Long postId;
private Long commentId;
private int voteType; // +1 or -1
}public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByCommunityIdOrderByScoreDesc(Long communityId);
}
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByPostIdOrderByPath(Long postId);
}@RestController
@RequestMapping("/api/posts")
public class PostController {
@Autowired
private PostRepository postRepository;
@GetMapping("/community/{id}")
public List<Post> getPosts(@PathVariable Long id) {
return postRepository.findByCommunityIdOrderByScoreDesc(id);
}
@PostMapping
public Post createPost(@RequestBody Post post) {
return postRepository.save(post);
}
}@RestController
@RequestMapping("/api/comments")
public class CommentController {
@Autowired
private CommentRepository commentRepository;
@GetMapping("/post/{postId}")
public List<Comment> getComments(@PathVariable Long postId) {
return commentRepository.findByPostIdOrderByPath(postId);
}
}cd backend
./mvnw spring-boot:runcd frontend
npm create vite@latest
npm install
npm install axios react-router-domimport axios from "axios";
const API = axios.create({
baseURL: "http://localhost:8080/api"
});
export default API;Comments behave like file paths:
/1
/1/2
/1/2/3
Sorting by path = correct thread order.