2
\$\begingroup\$

I built RESTFUL api for a blog using java and spring boot(without a frontend).
What it does
the code manages all the http methods on a post inside the blog and comments on that post.
the post consists of an id, name of the creator, title, body, amount of likes and dislikes, the comments are very similar they consist of an id, id of the post it was left on, username of the comment poster, its body, likes and dislikes. I have a mysql database for the posts and comments which columns are the same as the model.
for the posts and comments there is a controller that manages all the HTTP methods with a repository object that extends CrudRepository.
this is basically it I have a .http file to run the HTTP methods but it can be achieved through other means and services.

Post and Comment Model

package dev.ellie.blogcontentmanagmentsystem.models;


import org.springframework.data.annotation.Id;


import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;


public record Post(

    @Id
    Integer id,
    @NotBlank(message = "name should not be blank")
    String creatorName,
    @NotBlank(message = "post name should not be blank")
    String postName,
    @NotBlank(message = "content should not be blank")
    @Size(min=20,max=1000, message = "content should be between 20 to 1000 characters long")
    String content,
    int likes,
    int dislikes
) {
    
}
package dev.ellie.blogcontentmanagmentsystem.models;

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;


@Table(name="comment", schema = "blog-content-manager")
public record Comment(
    @Id
    @Column("id")
    int id,
    @Column("postId")
    int postId,
    @NotBlank(message = "username must not be blank")
    @Column("commentUsername")
    String commentUsername,
    @Column("content")
    @NotBlank(message = "content must not be blank")
    @Size(min = 10, max = 200, message = "content must contain between 10 and 200 characters")
    String content,
    @Column("likes")
    int likes,
    @Column("dislikes")
    int dislikes
) {


}

Post and Comment Controllers

package dev.ellie.blogcontentmanagmentsystem.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import dev.ellie.blogcontentmanagmentsystem.models.Post;
import dev.ellie.blogcontentmanagmentsystem.repository.AccessingPostDataMySql;


@RestController
@RequestMapping("/api/posts")
public class PostController {
    //private final PostRepository repository;
    private final AccessingPostDataMySql repository;

    @Autowired
    public PostController(AccessingPostDataMySql repository) {
        this.repository = repository;
    }

    @GetMapping
    public List<Post> findAll() {
        return (List<Post>) repository.findAll();
    }

    @GetMapping("/{id}")
    public Iterable<Post> findById(@PathVariable Iterable<Integer> id) {
        return repository.findAllById(id);
    }

    @PostMapping
    public void insertPost(@RequestBody Post post) {
        System.out.println(post.postName());
        repository.save(post);

    }

    @PutMapping("{id}/likes")
    public int addLike(@PathVariable Integer id) throws Exception {
        repository.addLike(id);
        return 1;
    }

    @PutMapping("{id}/dislikes")
    public int addDislike(@PathVariable Integer id) throws Exception {
        repository.addDislike(id);
        return 1;
    }
}
package dev.ellie.blogcontentmanagmentsystem.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import dev.ellie.blogcontentmanagmentsystem.models.Comment;
import dev.ellie.blogcontentmanagmentsystem.repository.AccessingCommentDataMySql;

@RestController
@RequestMapping("/api/comments")
public class CommentController {
    private final AccessingCommentDataMySql repository;

    @Autowired
    public CommentController(AccessingCommentDataMySql repository) {
        this.repository = repository;
    }

    @GetMapping("{postId}")
    public List<Comment> findAllByPostId(@PathVariable int postId) {
        return (List<Comment>) repository.findAllByPostId(postId);
    }

    @GetMapping()
    public List<Comment> findAll() {
        return (List<Comment>) repository.findAll();
    }

    @PutMapping("{postId}/{id}/likes")
    public int addLike(@PathVariable Integer postId,@PathVariable Integer id) {
        repository.addLike(id);
        return 1;
    } 

    @PutMapping("{postId}/{id}/dislikes")
    public int addDislike(@PathVariable Integer postId,@PathVariable Integer id) {
        repository.addDislike(id);
        return 1;
    } 
}

The Repository Interface for the Posts and Comments

package dev.ellie.blogcontentmanagmentsystem.repository;

import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

import dev.ellie.blogcontentmanagmentsystem.models.Post;

public interface AccessingPostDataMySql extends CrudRepository<Post, Integer> {

    @Modifying
    @Query("UPDATE post p SET likes = likes + 1 WHERE p.id = :id")
    void addLike(@Param("id") Integer id);

    @Modifying
    @Query("UPDATE post p SET dislikes = dislikes + 1 WHERE p.id = :id")
    void addDislike(@Param("id") Integer id);
    
}
package dev.ellie.blogcontentmanagmentsystem.repository;

import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

import dev.ellie.blogcontentmanagmentsystem.models.Comment;

public interface AccessingCommentDataMySql extends CrudRepository<Comment, Integer> {

    @Query("SELECT * FROM comment c WHERE c.postId = :postId")
    Iterable<Comment> findAllByPostId(@Param("postId") int postId);

   
    @Modifying
    @Query("UPDATE comment c SET likes = likes + 1 WHERE c.id = :id")
    void addLike(@Param("id") Integer id);

    @Modifying
    @Query("UPDATE comment c SET dislikes = dislikes + 1 WHERE c.id = :id")
    void addDislike(@Param("id") Integer id);
    
}

MySQL Schema

CREATE TABLE IF NOT EXISTS post (
    id INT PRIMARY KEY,
    creator_name VARCHAR(255),
    post_name VARCHAR(255),
    content VARCHAR(1000),
    likes INT,
    dislikes INT
);

CREATE TABLE IF NOT EXISTS comment (
    id INT PRIMARY KEY,
    postId INT,
    commentUsername VARCHAR(255),
    content VARCHAR(200),
    likes INT,
    dislikes INT
);

I would like some ideas on how to improve my code and what to add to it, thanks.

\$\endgroup\$
1
  • \$\begingroup\$ you can Isolate the AccessingPostDataMySql from the EndPoint. Creating a Service that contains the AccessingPostDataMySql and manipulate the business logic in the Service. \$\endgroup\$ Sep 6, 2023 at 14:28

1 Answer 1

3
\$\begingroup\$

relation between post and comment

in my opinion the relation between comments post is a composition and therefor it should belong to the post.

@RestController
@RequestMapping("/api/posts/{postId}/comments")
public class CommentController {
    ...

    @GetMapping
    public List<Comment> findAllByPostId(@PathVariable int postId) {
        return (List<Comment>) repository.findAllByPostId(postId);
    }

    ...
}

path variables can be set at class level, there is an stackoverflow article describing how

this is what the API would look like after:

GET /api/posts/{postId}/comments - get all
GET /api/posts/{postId}/comments/{commentId}
PUT /api/posts/{postId}/comments/{commentId}/likes
PUT /api/posts/{postId}/comments/{commentId}/dislikes

missing POST for comments

you cannot create commonets with your API, there is no POST to create new ones (as well as there is no PUT nor DELETE).

dangerous API

while it is described in any tutorial to provide a getAll() method, it is dangerous in real applications. When data reaches a certain size, the amount of results will overburden the system.

BUT: it is quite common to use Pagination for that use case.

weird return values

why do you return the magic number 1 in your like/dislike methods? you can use void methods.

using entities as dtos

there is a nice article from Martin Fowler addressing this issue, you should not send db-entities as dtos.

using a logger

just give it a try, you already try to log something... so do it now in a proper way, dont be afraid :-)

@PostMapping
public void insertPost(@RequestBody Post post) {
    //System.out.println(post.postName());
    //use a logger instead
    repository.save(post);
}

summary

your code is very readable and very good to understand! i appreciate what you have achieved! Especially i like how you do dependency injection in the PROPER way! i do not see this very often, nice.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ thanks for the response and the summary (you made my day), I took a break from java for now but thanks for the tips, im currently working with C++ and im sure I can use your advice on my current project as it is somewhat simillar. \$\endgroup\$
    – Ellie
    Sep 20, 2023 at 14:42
  • 1
    \$\begingroup\$ for logging I suggest use @Slf4j from lombok \$\endgroup\$
    – shareef
    Sep 20, 2023 at 17:57
  • \$\begingroup\$ @Guy honestly it was really fun (?pleasure) to read your code, it is clearly written and has very little flaws - i for myself could not do better ^^ \$\endgroup\$ Sep 22, 2023 at 5:19
  • \$\begingroup\$ @MartinFrank thank you very much for the kind words and im sure you can do better than me, im just a begginer :) \$\endgroup\$
    – Ellie
    Sep 23, 2023 at 16:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.