Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Comunatee (React + Spring Boot)

Running the App

Prerequisites

  • Docker (for PostgreSQL)
  • Java 17+ (e.g. Java 21 via SDKMAN)
  • Maven 3.x
  • Node.js 18+

1. Start the database

docker compose up -d

This starts a PostgreSQL instance on port 5432.

2. Start the backend

cd backend
JAVA_HOME=/path/to/java17+ mvn spring-boot:run

The 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 system mvn install. Spring Boot 3.x requires Java 17 or higher.

3. Start the frontend

cd frontend
npm install
npm run dev

The React app will start on http://localhost:5173 (or the next available port).


Overview

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

Core Design Principles

  • Read-heavy system → optimize for fast reads
  • Use denormalized fields (score, comment count)
  • Avoid complex recursion in SQL
  • Use materialized path for comment threading

Project Structure

root/
  frontend/
  backend/

Backend (Spring Boot)

1. Setup

Use Spring Initializr with:

  • Spring Web
  • Spring Data JPA
  • PostgreSQL Driver
  • Lombok (optional)

2. Database Config

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/comunatee
    username: postgres
    password: password
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

Core Entities

User

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

Community

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

Post

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

Comment (Materialized Path)

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

Vote

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

Repositories

public interface PostRepository extends JpaRepository<Post, Long> {
    List<Post> findByCommunityIdOrderByScoreDesc(Long communityId);
}

public interface CommentRepository extends JpaRepository<Comment, Long> {
    List<Comment> findByPostIdOrderByPath(Long postId);
}

Controllers

Posts

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

Comments

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

Run Backend

cd backend
./mvnw spring-boot:run

Frontend (React)

Setup

cd frontend
npm create vite@latest
npm install
npm install axios react-router-dom

API Layer

import axios from "axios";

const API = axios.create({
  baseURL: "http://localhost:8080/api"
});

export default API;

Final Mental Model

Comments behave like file paths:

/1
/1/2
/1/2/3

Sorting by path = correct thread order.


About

A rebuild of comunatee, an altenative to reddit

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages