RestAPI using Java

RestAPI using Java

Hello everyone, today we'll look at how we can use Java and Spring Boot to create a simple book API that performs basic CRUD operations. We will also use MongoDb, a NoSQL database, to store the data.

started

Prerequisite

๐ŸŽฏ Integrated Development Environment (IntelliJ IDEA recommended) ๐ŸŽฏ Have Java installed on your computer ๐ŸŽฏ Java fundamentals ๐ŸŽฏ Postman for API testing ๐ŸŽฏ MongoDB Atlas or MongoDB Compass


โ˜• Project Setup

Go to the Spring initializr website and select the necessary setup. Link

Image description

Next, unzip the folder and open it with your IDE, in my case IntelliJ, and create a project out of the pom.xml file.

Image description


Let's quickly change our port number to '3456'. You can use the default, '8080,' but that port is already in use by another service on my machine.

Image description


Starting your application

To begin, navigate to your main entry file, in my case the YomiApplication class, and click the green play button highlighted below.

Image description

After a successful build, your server should be launched at the port we specified earlier.

Image description


Packages

Let's now create packages to modularize our application.

A package in Java is used to group related classes. Think of it as a folder in a file directory. We use packages to avoid name conflicts, and to write a better maintainable code.

Image description


Model

Let's create our book entity, or the fields that our Book model will have. A model class is typically used in your application to "shape" the data. For example, you could create a Model class that represents a database table or a JSON document.

Create a Book Class within the model package and write the code snippet below.

package com.example.yomi.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.Date;

@Setter //@Setter is a Lombok annotation that generates setter methods for all fields in the class.
@Getter //@Getter is a Lombok annotation that generates getter methods for all fields in the class.
@AllArgsConstructor //@AllArgsConstructor is a Lombok annotation that generates a constructor with all fields in the class.
@NoArgsConstructor  //@NoArgsConstructor is a Lombok annotation that generates a constructor with no arguments.
@Document(collection = "books") // @Document annotation is used to specify the collection name (MongoDB collection name)
public class Book {
    @Id
    private String id; // private means only this class can access this field

    private String name;

    private String title;

    private Boolean published;

    private String author;

    private Date createdAt;

    private Date updatedAt; // Date is a class in Java that represents a date and time
}

Book service

Let's move on to the service now that we've structured our Book A client will use a Service class to interact with some functionality in your application.

We will create a BookService Interface and a BookServiceImplementation Class in the service package.. Image description

We will implement a method to create a single book in the BookService interface.

package com.example.yomi.service;

import com.example.yomi.model.Book; // we import the Book model class

public interface BookService { // interface is a contract that defines the behavior of a class
    public void createBook(Book book); // the `book` in lowercase is the name of the parameter that we will pass to the method
    // the `Book` in uppercase is the name of the class that we will pass to the method
}

Let us now put this createBook method into action in the BookServiceImpl class

package com.example.yomi.service;

import com.example.yomi.model.Book;
import com.example.yomi.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service  // this annotation tells Spring that this class is a service
public class BookServiceImpl implements BookService{ // we implement the BookService

    @Autowired // this annotation tells Spring to inject the BookRepository dependency
    private BookRepository bookRepository; // we inject the BookRepository dependency

    public void createBook(Book book) {
        book.setCreatedAt(new Date(System.currentTimeMillis()));
        bookRepository.save(book);
    }


}

Before proceeding, we must connect our application to MongoDB. We will proceed in two stages.

1) Enter the mongodb configuration in the application.properties file.

spring.data.mongodb.uri=mongodb://127.0.0.1:27017/book-app
spring.data.mongodb.database=book-app

2) In the book repository file BookRepository

package com.example.yomi.repository;

import com.example.yomi.model.Book;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

// Repository should be created as an interface
@Repository
public interface BookRepository extends MongoRepository<Book, String> {

//    we extend the MongoRepository interface and pass the Book model class and the type of the id field
}

Let's finish our 'BookServiceImpl' that we started earlier.

package com.example.yomi.service;

import com.example.yomi.model.Book;
import com.example.yomi.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service  // this annotation tells Spring that this class is a service
public class BookServiceImpl implements BookService{ // we implement the BookService 

    @Autowired // this annotation tells Spring to inject the BookRepository dependency
    private BookRepository bookRepository; // we inject the BookRepository dependency

    public void createBook(Book book) {
        bookRepository.save(book);
    }


}

Before we can test, we must first create our API controller. Let's make a BookController class in the controller package.

package com.example.yomi.controller;

import com.example.yomi.model.Book;
import com.example.yomi.repository.BookRepository;
import com.example.yomi.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController // this annotation tells Spring that this class is a controller
public class BookController {

    @Autowired // this annotation tells Spring to inject the BookRepository dependency
    private BookRepository bookRepository; // we inject the BookService dependency

    @Autowired // this annotation tells Spring to inject the BookService dependency
    private BookService bookService; // we inject the BookService dependency

    @PostMapping("/books") // this annotation tells Spring that this method is a POST request
    public ResponseEntity<?> createBook(@RequestBody Book book) { // the ? means that the return type is a generic type
        bookService.createBook(book);
        bookRepository.save(book);
        return new ResponseEntity<>(book, HttpStatus.CREATED); //ResponseEntity is a generic type that returns a response
    }
}

After restarting your app, use Postman to test the Create Book API.

Image description

We get the document uploaded in the mongo compass when we check the database.

Image description


Let's take a step back and look at the application's connection chain. In this order, we have the model, repository, service, implementation, database, and client.

model -> repository -> service -> serviceImplementation -> controller -> database -> client

Let's now make the get all books request.

Let's make a request for all of the books. We add a new method called getAllbooks to the BookController.

//...
 @GetMapping("/books")
    public ResponseEntity<?> getAllBooks() { // the ? means that the return type is a generic type
       List<Book> books =  bookRepository.findAll();
         return new ResponseEntity<>(books, books.size() > 0 ? HttpStatus.OK: HttpStatus.NOT_FOUND ); //ResponseEntity is a generic type that returns a response
        // books.size() means that if the size of the books list is greater than 0, return HttpStatus.OK, else return HttpStatus.NOT_FOUND
    }
//...

In BookService we create a new method

//..
 List<Book> getAllBooks();
//...

Let's put the getAllBooks method into action by right-clicking on the BookServiceImpl. Image description

And then select methods to implement.

Image description

Then we call the bookRepository.findAll();

@Override
    public List<Book> getAllBooks() {
        List<Book> books = bookRepository.findAll();
        return books;

    }

Let us restart the server and run some tests in Postman. We get:

Image description

Let's add a new book just to make sure we have enough documents.

Image description

Let's retry the read all with the GET request.

Image description

We now have two documents in our database.


We are almost done. Hang in there image Let's use the id to create a get single book, delete it, and update it. All we need to do is edit the controller, bookService, and bookServiceImplementation files in the same format.

I'll include a link to the source code as well as the entire set of snippets.

Simply follow the same steps as we did for the Post and Get requests.

//BookController
package com.example.yomi.controller;

import com.example.yomi.model.Book;
import com.example.yomi.repository.BookRepository;
import com.example.yomi.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController // this annotation tells Spring that this class is a controller
public class BookController {

    @Autowired // this annotation tells Spring to inject the BookRepository dependency
    private BookRepository bookRepository; // we inject the BookService dependency

    @Autowired // this annotation tells Spring to inject the BookService dependency
    private BookService bookService; // we inject the BookService dependency

    @PostMapping("/books") // this annotation tells Spring that this method is a POST request
    public ResponseEntity<?> createBook(@RequestBody Book book) { // the ? means that the return type is a generic type
        bookService.createBook(book);
        bookRepository.save(book);
        return new ResponseEntity<>(book, HttpStatus.CREATED); //ResponseEntity is a generic type that returns a response
    }

    @GetMapping("/books")
    public ResponseEntity<?> getAllBooks() { // the ? means that the return type is a generic type
       List<Book> books =  bookRepository.findAll();
         return new ResponseEntity<>(books, books.size() > 0 ? HttpStatus.OK: HttpStatus.NOT_FOUND ); //ResponseEntity is a generic type that returns a response
        // books.size() means that if the size of the books list is greater than 0, return HttpStatus.OK, else return HttpStatus.NOT_FOUND
    }

    @GetMapping("/books/{id}")
    public ResponseEntity<?> getBookById(@PathVariable("id") String id) { // the ? means that the return type is a generic type
      try {
          Book book = bookService.getBookById(id);
          return new ResponseEntity<>(book, HttpStatus.OK); //ResponseEntity is a generic type that returns a response
      } catch (Exception e) {
          return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
      }
    }

    @PutMapping("/books/{id}")
    public ResponseEntity<?> updateBookById(@PathVariable("id") String id, @RequestBody Book book) {
        try {
            bookService.updateBook(id, book);
            return new ResponseEntity<>("Updated Book with id "+id, HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND); //ResponseEntity is a generic type that returns a response
        }
    }

    @DeleteMapping("/books/{id}")
    public ResponseEntity<?> deleteBookById(@PathVariable("id") String id) {
        try {
            bookService.deleteBook(id);
            return new ResponseEntity<>("Deleted Book with id "+id, HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND); //ResponseEntity is a generic type that returns a response
        }
    }
}

//BookService
package com.example.yomi.service;

import com.example.yomi.model.Book; // we import the Book model class

import java.util.List;

public interface BookService { // interface is a contract that defines the behavior of a class
    public void createBook(Book book);
    // the `book` in lowercase is the name of the parameter that we will pass to the method
    // the `Book` in uppercase is the name of the class that we will pass to the method

    List<Book> getAllBooks();

    public Book getBookById(String id);

    public void updateBook(String id, Book book);

    public void deleteBook(String id);

}

BookServiceImpl

package com.example.yomi.service;

import com.example.yomi.model.Book;
import com.example.yomi.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;

@Service  // this annotation tells Spring that this class is a service
    public class BookServiceImpl implements BookService{ // we implement the BookService

    @Autowired // this annotation tells Spring to inject the BookRepository dependency
    private BookRepository bookRepository; // we inject the BookRepository dependency

    public void createBook(Book book) {
        book.setCreatedAt(new Date(System.currentTimeMillis()));
        bookRepository.save(book);
    }

    @Override
    public List<Book> getAllBooks() {
        List<Book> books = bookRepository.findAll();
        return books;

    }

    @Override
    public Book getBookById(String id) {
        Book book = bookRepository.findById(id).get();
        return book;
    }

    @Override
    public void updateBook(String id, Book book) {
        Book bookToUpdate = bookRepository.findById(id).get();
        bookToUpdate.setTitle(book.getTitle());
        bookToUpdate.setAuthor(book.getAuthor());
        bookToUpdate.setCreatedAt(book.getCreatedAt());
        bookToUpdate.setUpdatedAt(new Date(System.currentTimeMillis()));
        bookRepository.save(bookToUpdate);
    }

    @Override
    public void deleteBook(String id) {
        bookRepository.deleteById(id);
    }


}

Update post

Image description

Result

Image description

Get Single Book using the ID

Image description

Delete book using Id

Image description

Conclusion

I understand that this is a lengthy read. Give yourself enough time to read between the lines. There are some topics that are not covered, such as input validation and entity association. I am hoping to continue in the near future. Keep an eye out.

Continue to learn, and may the force be with you. Peace

Resources

Source code Spring boot and MongoDB REST API CRUD Tutorial

ย