Full-Stack Development K

Restaurant Analogy
Spring Boot

REST API Backend

Single Page Application Frontend

20-Lesson Intensive Course | 2 Hours Daily

Course Overview
Phase 1

Backend Foundation

  • REST APIs with Spring Boot
  • Database Integration
  • JPA & Hibernate
  • API Best Practices
Phase 2

Frontend Experience

  • Angular Components
  • TypeScript & Services
  • HTTP Client
  • Reactive Forms
Phase 3

Full-Stack Fusion ( Final )

  • Integration
  • Routing & Navigation
  • Deployment
  • Final Project

Phase 1: Backend Foundation

Spring Boot

Lessons 1-5: Building Robust REST APIs

https://github.com/oopsaleem/frontoffice

Lesson 1 - Part 1
Welcome to the Stack!

📚 Theory Overview

  • Full-Stack Architecture
    Client-server model with separation of concerns
  • Spring Boot Introduction
    Opinionated framework for rapid development
  • Project Initialization
    Using Spring Initializr for project setup

🔬 Lab: Environment Setup

  1. Install JDK 17+ on your system
  2. Install IDE (IntelliJ IDEA or VS Code)
  3. Install HTTPie for API testing (https://httpie.io/docs/cli)
  4. Verify installations with terminal commands
Lesson 1 - Part 2
First Spring Boot Application

🔬 Lab: Create First Project

  1. Visit start.spring.io in browser
  2. Select:
    • Project: Maven
    • Language: Java
    • Spring Boot: 3.x
    • Dependencies: Spring Web
  3. Generate and download project
  4. Import into your IDE

🔬 Lab: Create Hello Endpoint

  1. Create new Java class: HelloController
  2. Add @RestController annotation
  3. Create method with @GetMapping("/hello")
  4. Return "Hello, World!" string
  5. Test with `http :8080/hello`

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
}
                    
Lesson 2 - Part 1
RESTful APIs & CRUD

📚 REST Principles

  • HTTP Methods
    GET(read), POST(create), PUT(update), DELETE(remove)
  • Stateless Communication
    Each request contains all necessary information
  • Resource-Based URLs
    URLs represent resources, not actions

🔬 Lab: Book API Structure

  1. Create Book class with id, title, author fields
  2. Create BookController with @RestController
  3. Use List<Book> for in-memory storage
  4. Implement GET /api/book endpoint
  5. Test with `http :8080/api/book`
Lesson 2 - Part 2
Code

// Book.java

public class Book {

    private Long id;
    private String title;
    private String author;

    // constructors, getters, setters
    public Book() {}

    public Book(Long id, String title, String author) {
        this.id = id;
        this.title = title;
        this.author = author;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}
                    
Lesson 2 - Part 3
Advanced REST Endpoints

🔬 Lab: Complete CRUD Operations

  1. Add @PathVariable for GET /api/book/{id}
  2. Implement POST /api/book with @RequestBody
  3. Add auto-increment ID generation
  4. Test all endpoints using HTTPie:
    • `http :8080/api/book` (GET all book)
    • `http :8080/api/book/1` (GET book by ID)
    • `http POST :8080/api/book title="New Book"\n author="Author Name"` (POST new book)
    • `http PUT :8080/api/book/1 title="Updated Title"\n author="Updated Author"` (PUT update book)
    • `http DELETE :8080/api/book/1` (DELETE book)
Lesson 2 - Part 4
Code

// BookController.java (modified for PUT and DELETE - in-memory)
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.http.HttpStatus;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

// Assuming Book class is defined as in Lesson 2 - Part 2

@RestController
@RequestMapping("/api/book")
public class BookController {
    // Re-using the in-memory list and nextId from Lesson 2 - Part 2 for demonstration
    private List<Book> book = new ArrayList<>();
    private Long nextId = 1L;

    public BookController() {
        book.add(new Book(nextId++, "Book Title 1", "Book Author 1"));
        book.add(new Book(nextId++, "Book Title 2", "Book Author 2"));
    }

    @GetMapping
    public List<Book> getAllBook() {
        return book;
    }

    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return book.stream()
                .filter(bookItem -> bookItem.getId().equals(id))
                .findFirst()
                .orElse(null);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book createBook(@RequestBody Book book) {
        book.setId(nextId++);
        this.book.add(book);
        return book;
    }

    @PutMapping("/{id}")
    public Book updateBook(@PathVariable Long id, @RequestBody Book updatedBook) {
        return book.stream()
                .filter(bookItem -> bookItem.getId().equals(id))
                .findFirst()
                .map(bookItem -> {
                    bookItem.setTitle(updatedBook.getTitle());
                    bookItem.setAuthor(updatedBook.getAuthor());
                    return bookItem;
                })
                .orElseThrow(() -> new IllegalArgumentException("Book not found"));
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteBook(@PathVariable Long id) {
        if (!book.removeIf(bookItem -> bookItem.getId().equals(id))) {
            throw new IllegalArgumentException("Book not found");
        }
    }
}
                    
Lesson 3 - Part 1
Database with JPA & H2

📚 JPA & Hibernate

  • Object-Relational Mapping (ORM)
    Map Java objects to database tables automatically
  • Spring Data JPA
    Abstraction layer for database operations
  • Repository Pattern
    Consistent data access interface

🔬 Lab: Setup JPA & H2

  1. Add Spring Data JPA and H2 dependencies to pom.xml
  2. Convert Book class to Entity with @Entity
  3. Add @Id and @GeneratedValue to id field
  4. Create BookRepository interface extending JpaRepository
  5. Configure H2 in application.yaml
Lesson 3 - Part 2
Dependencies 🛠️

          
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-jpa</artifactId>
          </dependency>
          <dependency>
              <groupId>com.h2database</groupId>
              <artifactId>h2</artifactId>
              <scope>runtime</scope>
          </dependency>
                              
Lesson 3 - Part 3
@Entity Annotation

// Book.java (Entity)
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String author;

    // constructors, getters, setters
    public Book() {}

    public Book(Long id, String title, String author) {
        this.id = id;
        this.title = title;
        this.author = author;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

Lesson 3 - Part 4
JpaRepository Interface

// BookRepository.java
import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book, Long> {
}
                    
Lesson 3 - Part 5

// BookController.java (modified for JPA Repository)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;

import java.util.List;

@RestController
@RequestMapping("/api/book")
public class BookController {

    // 1. Inject BookRepository into BookController
    @Autowired
    private BookRepository bookRepository;

    // 2. Replace in-memory list with repository calls
    // 3. Update GET endpoints to use repository.findAll()
    @GetMapping
    public List<Book> getAllBook() {
        return bookRepository.findAll();
    }

    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return bookRepository.findById(id).orElse(null);
    }

    // 4. Update POST endpoint to use repository.save()
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book createBook(@RequestBody Book book) {
        return bookRepository.save(book);
    }

    // 5. Update PUT endpoint to use repository.save()
    @PutMapping("/{id}")
    public Book updateBook(@PathVariable Long id, @RequestBody Book updatedBook) {
        return bookRepository.findById(id)
                .map(book -> {
                    book.setTitle(updatedBook.getTitle());
                    book.setAuthor(updatedBook.getAuthor());
                    return bookRepository.save(book);
                })
                .orElseThrow(() -> new IllegalArgumentException("Book not found"));
    }

    // 6. Update DELETE endpoint to use repository.deleteById()
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteBook(@PathVariable Long id) {
        if (!bookRepository.existsById(id)) {
            throw new IllegalArgumentException("Book not found");
        }
        bookRepository.deleteById(id);
    }
}
                    
Lesson 3 - Part 6
Application Properties

# application.yaml
spring:
  application:
    name: BookIJ
  h2:
    console:
      enabled: true
  datasource:
    url: jdbc:h2:mem:bookdb
    driverClassName: org.h2.Driver
    username: sa
    password:
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
                    

# Test H2 console at: 
http://localhost:8080/h2-console
              
Lesson 4 - Part 1
Production Database

📚 PostgreSQL & Production DB

  • Production Database
    Enterprise-grade relational database
  • Connection Pooling
    Efficient database connection management
  • DataSource Configuration
    Externalized database configuration

🔬 Lab: PostgreSQL Setup

  1. PostgreSQL on your system
  2. Create new database: bookdb
  3. Update `pom.xml` with PostgreSQL dependency
  4. Configure `application.yaml` for PostgreSQL
  5. Test connection with running application
Lesson 4 - Part 2
Installing Docker Engine

📚 Docker Fundamentals

  • What is Docker?
    Platform for developing, shipping, and running applications in containers.
  • Why Docker for Development?
    Ensures consistent environments, simplifies dependencies.
  • Docker Engine
    The client-server application that runs Docker containers.
    Install Docker Engine

🔬 Lab: Docker common commands


docker compose up -d # start the container in detached mode
docker ps # list all containers
docker stop <container_id> # stop a container
docker rm <container_id> # remove a container
docker exec -it <container_id> bash # open a shell to a container
docker logs <container_id> # view logs of a container
docker stats # view stats of all containers
docker top <container_id> # view processes of a container
docker inspect <container_id> # view detailed information of a container
                  
Lesson 4 - Part 3
Docker Compose for PostgreSQL

📚 Docker Compose Basics

  • What is Docker Compose?
    Tool for defining and running multi-container Docker applications.
  • `docker-compose.yaml` file
    YAML file to configure application's services, networks, and volumes.
  • Advantages
    Simplifies complex application setup and deployment.

🔬 Lab: Run PostgreSQL with Docker Compose

  1. Create a `docker-compose.yaml` file in your project root
  2. Define a PostgreSQL service with `bookdb` database
  3. Set environment variables for PostgreSQL (user, password, db name)
  4. Map port 5432 from container to host
  5. Run `docker-compose up -d` to start the service
  6. Verify PostgreSQL container is running: `docker ps`
Lesson 4 - Part 4

# docker-compose.yaml
name: frontoffice
services:
  postgresql:
    image: postgres:17.4
    # volumes:
    #   - ~/Downloads/frontoffice/frontoffice_db/postgresql/:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER=frontoffice_user
      - POSTGRES_DB=frontoffice_db
      - POSTGRES_HOST_AUTH_METHOD=trust
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready --username=$${POSTGRES_USER} --dbname=$${POSTGRES_DB}']
      interval: 5s
      timeout: 5s
      retries: 10
    # If you want to expose these ports outside your dev PC,
    # remove the "127.0.0.1:" prefix
    ports:
      - 127.0.0.1:5432:5432

                    
Lesson 4 - Part 5
Code - application.yaml

spring:
  application:
    name: frontoffice

  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/frontoffice_db
    username: frontoffice_user
    password:
  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    hibernate:
      ddl-auto: update
    show-sql: true
              

                
                
              
Lesson 5 - Part 1
Robust API Design

📚 API Best Practices

  • DTO Pattern
    Separate API contracts from internal entities
  • Exception Handling
    Global error handling strategies
  • @RestControllerAdvice
    Centralized exception management

🔬 Lab: DTO Implementation

  1. Create BookDTO class without JPA annotations
  2. Update controller to use BookDTO instead of Book entity
  3. Add mapping logic between Entity and DTO
  4. Test API responses using `http :8080/api/book` and `http POST :8080/api/book id:=1 title="DTO Book" author="DTO Author"`
Lesson 5 - Part 2
The DTO Pattern: A Restaurant Analogy 🍽️
  • Kitchen = Database
    Where the raw ingredients and cooking happen, secret recipes, etc.
  • Dining Room = API
    Where customers interact with your restaurant, they see the menu, order food, pay, etc.
  • Waiters = Controllers
    Bridge between kitchen and customers
Restaurant Analogy
Lesson 5 - Part 3
The Problem: Exposing Your Kitchen 🔥
  • Using Entity directly in Controller
    Equivalent to showing customers your messy kitchen with all its secrets!

    // BAD: Showing the kitchen to customers
    @RestController
    public class DishController {
        public Dish getDish() {
            return dishRepository.findById(1L);
            // Returns everything: IDs, timestamps,
            // internal fields, kitchen secrets!
        }
    }
                        
What's wrong with this?
  • Customers see internal kitchen details (IDs, timestamps)
  • You can't change your kitchen without affecting customers
  • Security risks (exposing sensitive data)
  • Tight coupling between kitchen and dining room
Messy Kitchen
Lesson 5 - Part 4
The Solution: DTOs = The Menu 📋
📚 DTOs Explained
  • Entity (Kitchen Version)
    What you see in the kitchen.
  • DTO (Data Transfer Object)
    What customers see on the menu, not what you see in the kitchen.

// GOOD: Show customers only what they need to see

public class DishDTO {
    private String name;        // What customers see
    private String type;        // What customers see
    private String price;       // What customers see
    private String ingredients; // What customers see
    // NO internal details!
}

@RestController
public class DishController {
    public DishDTO getDish() {
        Dish dish = dishRepository.findById(1L);
        return convertToMenu(dish); // Only show menu items
    }
}
                      
Lesson 5 - Part 5
Real Code Example:
Entity (Kitchen Version):

@Entity
public class Book {
    @Id
    private Long id;           // ID
    private String title;      // Menu item
    private String author;     // Menu item  
    private String internalCode; // secret (internal)
}
                    
DTO (Menu Version):

public class BookDTO {

    private String title;      // What customers see
    private String author;     // What customers see
    // No IDs, no timestamps, no internal codes!
}
                    
Lesson 5 - Part 6
Why This Matters: 🛡️

1. Security


// Entity exposes everything:

{
    "id": 123,
    "title": "Spring Boot Guide",
    "author": "John Doe",
    "internalCode": "SECRET_123",  // OOPS! Security leak!
    "createdBy": "admin"           // OOPS! Exposed internal user!
}
// DTO shows only safe fields:
{
    "title": "Spring Boot Guide",
    "author": "John Doe"
    // No security risks!
}
                    

2. Flexibility


// You can change your kitchen without changing the menu:

public class Book {
    // Changed internal field names
    private Long internalId;       // Was 'id'
    private String bookTitle;      // Was 'title'
    // Kitchen changed, menu stays the same!
}
public class BookDTO {
    private String title;          // Menu unchanged!
    private String author;         // Menu unchanged!
}
                    

3. Performance


// DTO lets you serve exactly what's needed:

public class BookDTO {
    private String title;
    private String author;
    // No unnecessary data from database
}
                    
Lesson 5 - Part 7
Code

// Book.java

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String author;
    private String internalCode;

    // Constructors
    public Book() {}

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getInternalCode() {
      return internalCode;
    }

    public void setInternalCode(String internalCode) {
        this.internalCode = internalCode;
    }
}

// BookDTO.java
public class BookDTO {
    private Long id;
    private String title;
    private String author;

    // Constructors
    public BookDTO() {}

    public BookDTO(Long id, String title, String author) {
        this.id = id;
        this.title = title;
        this.author = author;
    }

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

// BookService.java 
package nl.tochbedrijf.frontoffice.services;


import nl.tochbedrijf.frontoffice.domain.Book;
import nl.tochbedrijf.frontoffice.repository.BookRepository;
import nl.tochbedrijf.frontoffice.services.dtos.BookDTO;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class BookService {
    private final BookRepository bookRepository;

    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public List<BookDTO> getAllBooks() {
        return bookRepository.findAll()
                .stream()
                .map(this::convertToDto)
                .collect(Collectors.toList());
    }

    public BookDTO getBookById(Long id) {
        return bookRepository.findById(id)
                .map(this::convertToDto)
                .orElseThrow(() ->
                        new RuntimeException(
                                "Book not found with ID: " + id));
    }

    public List<BookDTO> findBooksByTitleContains(String title) {
        return bookRepository.findBooksByTitleContains(title)
                .stream()
                .map(this::convertToDto)
                .collect(Collectors.toList());
    }

    public BookDTO createBook(BookDTO bookDTO) {
        Book newBook = convertToEntity(bookDTO);
        Book savedBook = bookRepository.save(newBook);
        return convertToDto(savedBook);
    }

    public BookDTO updateBook(Long id, BookDTO updatedBook) {
        return bookRepository.findById(id)
                .map(bookItem -> {
                    bookItem.setTitle(updatedBook.getTitle());
                    bookItem.setAuthor(updatedBook.getAuthor());
                    return convertToDto(bookRepository.save(bookItem));
                })
                .orElseThrow(() ->
                        new RuntimeException(
                                "Book not found with ID: " + id));
    }

    public void deleteBook(Long id) {
        if (bookRepository.existsById(id)) {
            bookRepository.deleteById(id);
        } else {
            throw new RuntimeException("Book not found with ID: " + id);
        }
    }


    // Utils code
    private BookDTO convertToDto(Book book) {
        return new BookDTO(book.getId(), book.getTitle(), book.getAuthor());
    }

    private Book convertToEntity(BookDTO bookDTO) {
        Book book = new Book();
        book.setId(bookDTO.getId()); // ID might be null for new book
        book.setTitle(bookDTO.getTitle());
        book.setAuthor(bookDTO.getAuthor());
        book.setInternalCode(UUID.randomUUID().toString());
        return book;
    }
}


// BookController.java (modified)
import nl.tochbedrijf.frontoffice.services.BookService;
import nl.tochbedrijf.frontoffice.services.dtos.BookDTO;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/books")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @GetMapping
    public ResponseEntity<List<BookDTO>> getAllBooks() {
        return ResponseEntity.ok(bookService.getAllBooks());
    }

    @GetMapping("/{id}")
    public ResponseEntity<BookDTO> getBookById(@PathVariable Long id) {
        return ResponseEntity.ok(bookService.getBookById(id));
    }

    @GetMapping("/titleContains/{title}")
    public ResponseEntity<List<BookDTO>> findBooksByTitleContains(@PathVariable String title) {
        return ResponseEntity.ok(bookService.findBooksByTitleContains(title));
    }

    @PostMapping
    public ResponseEntity<BookDTO> createBook(@RequestBody BookDTO bookDTO) {
        return ResponseEntity.ok(bookService.createBook(bookDTO));
    }

    @PutMapping("/{id}")
    public ResponseEntity<BookDTO> updateBook(@PathVariable Long id, @RequestBody BookDTO bookDTO) {
        return ResponseEntity.ok(bookService.updateBook(id, bookDTO));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteBook(@PathVariable Long id) {
        bookService.deleteBook(id);
        return ResponseEntity.noContent().build();
    }
}

                    
Lesson 5 - Part 8
Exception Handling

📚 Exception Handling Strategies

  • Custom Exceptions
    Define specific exception types for domain-specific errors.
  • `@ControllerAdvice`
    Global handling of exceptions across multiple controllers.
  • `@ExceptionHandler`
    Annotate methods to handle specific exception types.
  • Error Response Structure
    Standardize error responses (e.g., JSON with error code and message).

🔬 Lab: Implement Custom Exception

  1. Create a custom `BookNotFoundException`
  2. Create `@ControllerAdvice` class to handle `BookNotFoundException`
  3. Return appropriate HTTP status code (e.g., 404 Not Found)
  4. Modify `BookController` to throw `BookNotFoundException`
  5. Test with an invalid book ID: `http :8080/api/book/99`
Lesson 5 - Part 9
Custom Exception & Advice Code

// BookNotFoundException.java
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class BookNotFoundException extends RuntimeException {
    public BookNotFoundException(String message) {
        super(message);
    }
}

// GlobalExceptionHandler.java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BookNotFoundException.class)
    public ResponseEntity<String> handleBookNotFoundException(BookNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    // Generic exception handler
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGenericException(Exception ex) {
        return new ResponseEntity<>("An unexpected error occurred: " 
                + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

// BookController.java (modified for exception handling)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;

import java.util.List;

@RestController
@RequestMapping("/api/book")
public class BookController {

    @Autowired
    private BookRepository bookRepository;

    @GetMapping
    public List<Book> getAllBook() {
        return bookRepository.findAll();
    }

    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return bookRepository.findById(id)
                .orElseThrow(() -> new BookNotFoundException("Book not found with ID: " + id));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book createBook(@RequestBody Book book) {
        return bookRepository.save(book);
    }

    @PutMapping("/{id}")
    public Book updateBook(@PathVariable Long id, @RequestBody Book updatedBook) {
        return bookRepository.findById(id)
                .map(book -> {
                    book.setTitle(updatedBook.getTitle());
                    book.setAuthor(updatedBook.getAuthor());
                    return bookRepository.save(book);
                })
                .orElseThrow(() -> new BookNotFoundException("Book not found with ID: " + id));
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteBook(@PathVariable Long id) {
        if (!bookRepository.existsById(id)) {
            throw new BookNotFoundException("Book not found with ID: " + id);
        }
        bookRepository.deleteById(id);
    }
}
                    
Lesson 5 - Part 10
Final code refactore


// BookController.java (move business logic to service layer)

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/book")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @GetMapping
    public List<BookDTO> getBookList() {
        return bookService.getAll();
    }

    @GetMapping("/{id}")
    public BookDTO getBookById(@PathVariable Long id) {
        return bookService.getById(id);
    }

    @GetMapping("/titleContains/{title}")
    public List<BookDTO> getBooksByTitleContains(
      @PathVariable String title) {
        return bookService.findByTitleContains(title);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public BookDTO createBook(@RequestBody BookDTO bookDTO) {
        return bookService.create(bookDTO);
    }

    @PutMapping("/{id}")
    public BookDTO updateBook(@PathVariable Long id
                            , @RequestBody BookDTO updatedBook) {
        return bookService.update(id, updatedBook);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteBook(@PathVariable Long id) {
        bookService.delete(id);
    }
}
                    


// BookRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;


public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findBooksByTitleContains(String title);
}

// BookService.java
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class BookService {
    private final BookRepository bookRepository;

    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public List<BookDTO> getAll() {
        return bookRepository.findAll()
                .stream()
                .map(this::convertToDto)
                .collect(Collectors.toList());
    }

    public BookDTO getById(Long id) {
        Optional<Book> optById = bookRepository.findById(id);
        if (optById.isPresent()) {
            return convertToDto(optById.get());
        }  else {
            throw new BookNotFoundException(
              "Book not found with ID: " + id);
        }
    }

    public List<BookDTO> findByTitleContains(String title) {
        return bookRepository.findBooksByTitleContains(title)
                .stream()
                .map(this::convertToDto)
                .collect(Collectors.toList());
    }

    public BookDTO create(BookDTO bookDTO) {
        Book newBook = convertToEntity(bookDTO);
        Book savedBook = bookRepository.save(newBook);
        return convertToDto(savedBook);
    }

    public BookDTO update(Long id, BookDTO updatedBook) {
        return bookRepository.findById(id)
                .map(bookItem -> {
                    bookItem.setTitle(updatedBook.getTitle());
                    bookItem.setAuthor(updatedBook.getAuthor());
                    return convertToDto(bookRepository.save(bookItem));
                })
                .orElseThrow(() -> 
                    new BookNotFoundException(
                      "Book not found with ID: " + id));
    }

    public void delete(Long id) {
        if (bookRepository.existsById(id)) {
            bookRepository.deleteById(id);
        } else  {
            throw new BookNotFoundException("Book not found with ID: " + id);
        }
    }

    // Utils code
    private BookDTO convertToDto(Book book) {
        return new  BookDTO(book.getId(), book.getTitle(), book.getAuthor());
    }

    private Book convertToEntity(BookDTO bookDTO) {
        Book book = new Book();
        book.setId(bookDTO.getId()); // ID might be null for new book
        book.setTitle(bookDTO.getTitle());
        book.setAuthor(bookDTO.getAuthor());
        book.setInternalCode(UUID.randomUUID().toString());
        return book;
    }
}
                

Phase 2: Frontend

Quick tutorials

Course Completion

What You've Achieved

Backend Skills

  • REST API Development
  • Spring Boot Configuration
  • JPA & Database Integration
  • Exception Handling
  • API Security Basics

Frontend Skills

  • Component Architecture
  • TypeScript Programming
  • Reactive Forms
  • HTTP Client Integration
  • Single Page Applications
Next Steps in Your Journey

🛡️ Security

  • JWT Authentication
  • OAuth2 Integration
  • Spring Security
  • Role-Based Access

🚀 Advanced Topics

  • Microservices
  • Docker & Kubernetes
  • WebSockets
  • Performance Optimization

🎯 Specialization

  • Mobile Development
  • Cloud Platforms (AWS/Azure)
  • DevOps Practices
  • Testing Strategies

🎓 Congratulations!

You are now a Full-Stack Developer!

Keep Building, Keep Learning! 🚀

#FullStackMastery