DEV Community

Cover image for Spring Social Media Application using REST API
Sanjeeban Mukhopadhyay
Sanjeeban Mukhopadhyay

Posted on

Spring Social Media Application using REST API

We are going to create a simple backend application which has USER and corresponding POSTS.

For References refer to this github Repository: Github Link

What is REST API?

REST stands for REpresentational State Transfer. It is a standardized software architectural style i.e. a specific type of API that's industry known and used. REST API deals with communication. The main advantage of REST API is that it is simple, standardized and stateless.


Client-Server Communication

What is Stateless?

It simply means we do not have to worry about the state of the data. The Server-side data may be in JSON or XML. But we can fetch the data in our own format.

REST Architecture allows for CRUD operations and is mapped to HTTP Requests.

CRUD


Building the Social Media Application

The first step towards building is to map out the entire thing which we are going to do.

  • Step 1: Create a User Class and Post Class (A User may have many posts) and add validations.
  • Step 2: Add JPA Dependency and make the above classes as @Entity.
  • Step 3: Connect with H2 database and write the SQL to insert values.
  • Step 4: Create the UserResource which contains all the mappings.
  • Step 5: Use Spring Security to add security features

The Steps may seem overwhelming but with nice explanations, we shall easily perform it.


Step 1: Create the User and Post Class

@Entity(name="user_details")
public class User {

    protected User() {
    }

    @Id
    @GeneratedValue
    private Integer id;
    private String name;

    @Past(message="BirthDate should be in past")
    private LocalDate birthDate;

    @OneToMany(mappedBy="user")
    private List<Post> posts;

    public User(Integer id, String name, LocalDate birthDate) {
        super();
        this.id = id;
        this.name = name;
        this.birthDate = birthDate;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public LocalDate getBirthDate() {
        return birthDate;
    }
    public void setBirthDate(LocalDate birthDate) {
        this.birthDate = birthDate;
    }
    public List<Post> getPosts() {
        return posts;
    }
    public void setPosts(List<Post> posts) {
        this.posts = posts;
    }
    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", birthDate=" + birthDate+"]";
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. @Entity -> This annotation marks the class as a database table. JPA will now be able to perform CRUD operations on this class.
  2. @Id-> This is the Primary key.
  3. @GeneratedValue -> We want the primary id to be auto-generated.
  4. @Past and @Size -> These are validation contained within the dependency "spring-boot-starter-validation".

Note: To add validations add the "spring-boot-starter-validation" in the pom.xml file. Also we have to restart the application everytime we make changes in pom.xml

  1. @OneToMany(mappedBy = "user") -> This maps as one user may have many posts.

Rest we have getters, setters and a toString() method along with an empty constructor.

Similarly, we can create the Post class also. The mapping in the Post class will be @ManyToOne(fetch=FetchType.LAZY). This tells that many posts will have one user and the association is lazy-loaded i.e. loaded when necessary.


Step 2: Add JPA Dependency

Note: For @Entity to work the JPA dependency should be there in pom.xml.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

The above code should be present in the pom.xml file to add the JPA dependency.

Next we have to define two Interfaces.

public interface UserRepository  extends JpaRepository<User,Integer>{
}
Enter fullscreen mode Exit fullscreen mode
public interface PostRepository extends JpaRepository<Post,Integer> {
}
Enter fullscreen mode Exit fullscreen mode

These interfaces extend the Spring Data JPA's JpaRepository. This provides standard CRUD operations for the Post and User entities.
<"This is the Primary key","This is the type of the Primary key">.


Step 3: Connect with H2 database and write the SQL to insert values.

The name of our User Table is "user_details" and Post Table is "post". Thus the SQL is as below. Feel fee to insert more values.

// User
insert into user_details(id,birth_date,name)
values(1,current_date(),'Sanjeeban');

insert into user_details(id,birth_date,name)
values(2,current_date(),'Joey');

// Post

insert into post(id,description,user_id)
values(2001,'learn aws',1);

insert into post(id,description,user_id)
values(2002,'Learn cloud',2);

Enter fullscreen mode Exit fullscreen mode

The important thing to note is that while inserting the post, we are mapping the post to specific user.

Now to add H2 database, add the dependency to pom.xml.

<dependency>
        <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Also in application.properties write the following line of code :

spring.datasource.url=jdbc:h2:mem:testdb

This serves as the JDBC URL.

H2-Console


Step 4: Create the UserResource which contains all the mappings.

This is the most important part. Here we are now creating the mappings of the REST API.

First let us understand some of the annotations:

  1. @RestController -> This is used to represent a Controller in Spring MVC. It is used to create RESTful web services.
  2. @GetMapping -> Used to map HTTP GET Requests and return information.
  3. @DeleteMapping -> Used to map HTTP DELETE Requests.
  4. @PostMapping -> Used to map HTTP POST Requests. Used to submit some information.
  5. @Valid -> The annotated parameter should be validated.
  6. @PathVariable -> The parameter will be extracted from the URL.
  7. @RequestBody -> Used to extract the request body and convert it into a java object.

With these annotations in mind, let us dive into the 6 methods.

Method 1: Get all Users.
Method 2: Get User by id.
Method 3: Delete User.
Method 4: Get post of a particular User.
Method 5: Post a new User.
Method 6: Post a new Post for a User.

At the beginning let us mark our class as @RestController and inject the dependencies on UserRepository and PostRepository using Constructor injection.

@RestController
public class UserJpaResource {

    private UserRepository userRepository;
    private PostRepository postRepository;
    public UserJpaResource(UserRepository userRepository, PostRepository postRepository) {
        super();
        this.userRepository = userRepository;
        this.postRepository = postRepository;
    }
Enter fullscreen mode Exit fullscreen mode

- Method 1: Get all Users.

@GetMapping("/jpa/users")
    public List<User> retrieveAllUsers() {
        return userRepository.findAll();
    }
Enter fullscreen mode Exit fullscreen mode

This block tells us to get all the users in a list format when we type the URL : http://localhost:8080/jpa/users.

- Method 2: Get User by id.

    @GetMapping("/jpa/users/{id}")
    public EntityModel<User> retrieveUser(@PathVariable int id) {
        Optional<User> user = userRepository.findById(id);
        if (user.isEmpty()) {
            throw new UserNotFoundException("id: " + id);
        }
        EntityModel<User> entityModel = EntityModel.of(user.get());

        WebMvcLinkBuilder link = linkTo(methodOn(this.getClass()).retrieveAllUsers());
        entityModel.add(link.withRel("all-users"));
        return entityModel;
    } 
Enter fullscreen mode Exit fullscreen mode

Here, there are few points.

  1. Optional is a class used while using Functional Programming. It is used to represent an optional value. It helps us to avoid Null-Pointer Exception.
  2. EntityModel is a class from the Spring HATEOAS that represents a resource with links i.e. going to the get-all-user page from this page. _Note: Use the dependency of Spring HATEOAS in pom.xml. See the github link for the dependency. _
  3. WebMvcLinkBuilder builds the link to the get-all-user page.
  4. @PathVariable means that the id will be extracted from the URL.

- Method 3: Delete User.

    @DeleteMapping("/jpa/users/{id}")
    public void deleteUser(@PathVariable int id) { 
        userRepository.deleteById(id);
    }
Enter fullscreen mode Exit fullscreen mode

This is simple code block. This deletes the user by id.

- Method 4: Get post of a particular User.

    @GetMapping("/jpa/users/{id}/posts")
    public List<Post> retrievePostForUser(@PathVariable int id){
        Optional<User> user = userRepository.findById(id);
        if(user.isEmpty()) {
            throw new UserNotFoundException("id: "+id);
        }
        return user.get().getPosts();
    }
Enter fullscreen mode Exit fullscreen mode

The Exception : UserNotFoundException has to be defined in your code.

@ResponseStatus(code=HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {

    public UserNotFoundException(String message) {
        super(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

- Method 5: Post a new User.

Now comes the complex part.

@PostMapping("/jpa/users")
    public ResponseEntity<User> createUser(@Valid @RequestBody User user){
        User savedUser = userRepository.save(user);

        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(savedUser.getId())
                .toUri();

        return ResponseEntity.created(location).build();

    }
Enter fullscreen mode Exit fullscreen mode

Let's break it down. In jist, we create a new User and return a response with the location of the created user.

  1. ServletUriComponentsBuilder-> This is used to build URI from current request.
  2. .path("[{id}") -> This appends the path segment to the URI.
  3. savedUser.getId() -> This replaces the placeholder {id} with the actual id of the saved user.
  4. toUri() -> Converts to URI Object.
  5. return ResponseEntity.created(location).build() -> Creates a response entity with a status code 201.

- Method 6: Post a new Post for a User.

    @PostMapping("/jpa/users/{id}/posts")
    public ResponseEntity<Object> createPostForUser(@PathVariable int id, @Valid @RequestBody Post post){
        Optional<User> user = userRepository.findById(id);

        if(user.isEmpty()) {
            throw new UserNotFoundException("id: "+id);
        }

        post.setUser(user.get());

        Post savedPost = postRepository.save(post);

        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(savedPost.getId())
                .toUri();

        return ResponseEntity.created(location).build();
    }
Enter fullscreen mode Exit fullscreen mode

This is similar to the above code.

  1. post.setUser(user.get()); -> If a user is found, it sets the found user as the owner of the Post by calling the setUser() method on the Post Object.
  2. postRepository.save(post) -> This saves the Post Object to the database using SpringDataJPARepository.

Step 5: Use Spring Security to add security features

Security is essential for any application. We have to add the starter security dependency in our pom.xml file.

<dependency>         
         <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Also in the application.properties add the following :

spring.jpa.defer-datasource-initialization=true: Defers the initialization of the data source until it is needed.
spring.security.user.name: sanjeeban
spring.security.user.password: password

The last two lines set our security username and password.
Now let us move to the main class.

@Configuration
public class SpringSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        http.authorizeHttpRequests(
                auth-> auth.anyRequest().authenticated());
        // 3. 
        http.httpBasic(withDefaults());
        // 4. 
        http.csrf().disable();
                // 5. 
        return http.build();
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. @Configuration -> Marks the class as a configuration class which can provide application configuration.
  2. The above method configures the security filters for our Spring Security Application, thus allowing more customization.
  3. Specifies that incoming requests must be authenticated.
  4. Enables Basic HTTP Authentication.
  5. CSRF protection is disabled.

At the end we return the newly built SecurityFilterChain on the configured HttpSecurity object and returns it as a bean.

Thus, we come to an end. This article is just for the explanation of the various lines of code used and the steps to be followed while building the application.

After reading the article we can get an idea about how to build a great REST API and the best practices which can be followed.

I hope you enjoyed reading. For further queries feel free to contact me.

Connect with me.
Linkedin
Twitter


Top comments (0)