Skip to content

Instantly share code, notes, and snippets.

@devded
Created June 7, 2026 14:19
Show Gist options
  • Select an option

  • Save devded/bc501c76180523219ba753ec9db09f94 to your computer and use it in GitHub Desktop.

Select an option

Save devded/bc501c76180523219ba753ec9db09f94 to your computer and use it in GitHub Desktop.

Spring Boot — Complete Beginner's Guide & Notes

Based on: Merchandise Store hands-on tutorial by Anerud


Table of Contents

  1. What is Spring Boot?
  2. Problem Statement & System Design
  3. Database Design (ER Diagram)
  4. API Design
  5. Project Setup
  6. Project Structure Explained
  7. Configuration (application.yml + .env)
  8. Layer Architecture (MVC)
  9. Entities Layer
  10. Repository Layer
  11. Service Layer
  12. Controller Layer
  13. DTO Layer
  14. Order Flow — Full Implementation
  15. Swagger / OpenAPI Documentation
  16. Testing with an API Client
  17. Key Concepts Glossary
  18. Assignments / Take-Home Tasks

1. What is Spring Boot?

Spring Framework

  • An open-source Java framework that provides ready-made solutions for common developer problems: DB connectivity, REST APIs, authentication, messaging, etc.
  • Analogy: like frozen/ready-made food — someone already did the cooking (DB drivers, ORM, auth), you just configure and use it.

Spring Boot

  • Built on top of Spring Framework.
  • Takes an opinionated approach — sensible defaults out of the box, so you get up and running faster.
  • Analogy: if Spring Framework is the ingredients + recipe, Spring Boot is the meal that just needs reheating.

Why enterprises use it

  • Used by Netflix, Walmart, Amazon, Google, and many fintech companies.
  • Handles: microservices, web apps, cloud, serverless, generative AI backends.
  • Developers focus on business logic, not boilerplate infrastructure code.

2. Problem Statement & System Design

The Scenario

An influencer wants a merchandise store to sell items like t-shirts, mugs, and stickers.

Key Questions (Gathered from the client)

Question Why it matters
What is the price of an item? Core product info
When was an order placed? Order tracking
How many products are left in stock? Inventory management
What items are in a particular order? Dispute resolution / refunds

Abilities

As a store owner (creator):

  • Create a product
  • Read a product / all products
  • Update a product
  • Delete a product

As a customer:

  • Read all products
  • Read a single product
  • Place an order
  • View order history

Core Entities Identified

Two main things the entire system revolves around:

  1. Product
  2. Order

3. Database Design (ER Diagram)

Products Table

Column Type Notes
id Long (PK) Auto-generated, primary key
name String NOT NULL
description String Nullable
category String Nullable
price Decimal NOT NULL, must be > 0
stock_quantity Integer NOT NULL, must be >= 0
created_at Timestamp Auto-set on insert
updated_at Timestamp Auto-set on update

Orders Table

Column Type Notes
id Long (PK) Auto-generated
customer_name String NOT NULL
customer_email String NOT NULL
status String e.g. "confirmed"
total_amount Decimal Computed total
created_at Timestamp Auto-set on insert

Order Items Table (Junction Table)

Column Type Notes
id Long (PK) Auto-generated
order_id FK → Orders NOT NULL
product_id FK → Products NOT NULL
quantity Integer NOT NULL
price_at_purchase Decimal Snapshot of price at time of order

Why price_at_purchase? Product prices change over time (sales, demand). This field freezes the price at the moment the order was placed — essential for accurate revenue analytics.

Relationships

Orders 1 ──────< OrderItems >────── Products
         (one-to-many)    (many-to-one)
  • One Order → Many Order Items
  • One Product → Many Order Items (product can appear in many orders until stock runs out)

4. API Design

Product Endpoints

Method Endpoint Action
POST /api/products Create a product
PUT /api/products/{id} Update a product
GET /api/products Get all products
GET /api/products/{id} Get a single product
DELETE /api/products/{id} Delete a product

Order Endpoints

Method Endpoint Action
POST /api/orders Place an order
GET /api/orders/{id} Get a single order
GET /api/orders Get all orders

5. Project Setup

Tools Needed

  • IntelliJ IDEA (Community edition is free)
  • JDK (install via IntelliJ if not present)
  • Maven (bundled with the project as mvnw)
  • Supabase (or Docker + local Postgres) for the database

Creating the Project (IntelliJ)

  1. New Project → Spring Boot
  2. Language: Java
  3. Build tool: Maven
  4. Group ID: reverse domain (e.g. com.yourname)
  5. Configuration format: YML (preferred over .properties)
  6. Java version: latest stable

Dependencies to Add at Creation

Dependency Purpose
Spring Web Enables REST API creation
Spring Data JPA ORM layer (Hibernate) — maps Java classes to DB tables
PostgreSQL Driver Connects Java app to Postgres DB
Lombok Auto-generates getters, setters, constructors (reduces boilerplate)
SpringDoc OpenAPI Auto-generates Swagger documentation

Adding Dependencies Later (pom.xml)

Search Maven Repository for any package, copy the XML snippet, and paste it inside <dependencies> in pom.xml. Then sync Maven (IntelliJ will prompt you).


6. Project Structure Explained

creator-store/
├── .idea/                  ← IntelliJ IDE config (don't touch)
├── .mvn/                   ← Maven wrapper config
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com.yourname.creatorstore/
│   │   │       ├── CreatorStoreApplication.java  ← Main class (entry point)
│   │   │       ├── entities/                     ← DB table models
│   │   │       ├── repositories/                 ← DB access interfaces
│   │   │       ├── services/                     ← Business logic
│   │   │       ├── controllers/                  ← REST API endpoints
│   │   │       └── dto/                          ← Data Transfer Objects
│   │   └── resources/
│   │       └── application.yml                   ← App configuration
│   └── test/               ← Unit tests
├── .env                    ← Secret credentials (NOT committed to git)
├── .gitignore
└── pom.xml                 ← Dependencies (like package.json for Java)
File/Folder JavaScript Equivalent
pom.xml package.json
application.yml .env / config files
Maven Registry npm registry (npmjs.com)
target/ folder dist/ or build/ folder

7. Configuration (application.yml + .env)

application.yml

spring:
  datasource:
    url: ${DATABASE_URL}
    username: ${DATABASE_USERNAME}
    password: ${DATABASE_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: update        # Auto-creates/updates tables from entity classes
    show-sql: true            # Logs SQL queries (useful for debugging)
    properties:
      hibernate:
        format_sql: true      # Makes logged SQL human-readable

ddl-auto: update — Hibernate reads your Java entity classes and automatically creates or alters the database tables to match. You never write raw SQL CREATE TABLE commands yourself.

.env file (secrets — never commit!)

DATABASE_URL=jdbc:postgresql://your-db-host:5432/postgres?...
DATABASE_USERNAME=your_username
DATABASE_PASSWORD=your_password

Loading .env in Main Class

Add the env-java dependency from Maven Repository, then in CreatorStoreApplication.java:

Dotenv env = Dotenv.configure().ignoreIfMissing().load();
env.entries().forEach(e -> System.setProperty(e.getKey(), e.getValue()));

This loads your .env file and makes values available to application.yml via ${KEY_NAME} syntax.

Getting DB Credentials from Supabase

  1. Create project on supabase.com
  2. Go to ConnectSession PoolerJDBC (not direct connection)
  3. Copy the JDBC URL, username, and password into your .env

8. Layer Architecture (MVC)

Spring Boot follows a clean 4-layer architecture. Each layer has a single responsibility.

Client (Postman / Frontend / Mobile)
        │  HTTP Request
        ▼
┌─────────────────────┐
│   CONTROLLER LAYER  │  ← Receives requests, routes to service, returns response
└─────────┬───────────┘
          │ calls
┌─────────▼───────────┐
│   SERVICE LAYER     │  ← Business logic lives here
└─────────┬───────────┘
          │ calls
┌─────────▼───────────┐
│  REPOSITORY LAYER   │  ← Talks to the database (CRUD)
└─────────┬───────────┘
          │ maps to
┌─────────▼───────────┐
│   ENTITY LAYER      │  ← Java classes that represent DB tables
└─────────────────────┘

SOLID Principle in action: The controller doesn't hold business logic. It only maps endpoints and delegates work to the service. This keeps things maintainable and testable.


9. Entities Layer

Entities are Java classes annotated to tell Hibernate: "Create a database table from this class."

Product Entity

@Entity
@Table(name = "products")
@Getter @Setter
@NoArgsConstructor @AllArgsConstructor
@Builder
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    @NotBlank(message = "Product name is required")
    private String name;

    private String description;  // nullable

    private String category;     // nullable

    @Column(nullable = false)
    @NotNull(message = "Price is required")
    @DecimalMin(value = "0.0", inclusive = false, message = "Price must be greater than zero")
    private BigDecimal price;

    @Column(name = "stock_quantity", nullable = false)
    @NotNull(message = "Stock quantity is required")
    @Min(value = 0, message = "Stock cannot be less than zero")
    private Integer stockQuantity;

    @OneToMany(mappedBy = "product")
    @JsonIgnore  // prevents infinite loop when serializing
    private List<OrderItem> orderItems;
}

Order Entity

@Entity
@Table(name = "orders")
@Getter @Setter
@NoArgsConstructor @AllArgsConstructor
@Builder
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "customer_name", nullable = false)
    private String customerName;

    @Column(name = "customer_email", nullable = false)
    private String customerEmail;

    @Column(nullable = false)
    private String status;

    @Column(name = "total_price", nullable = false)
    private BigDecimal totalPrice;

    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    @JsonManagedReference
    private List<OrderItem> orderItems;

    @PrePersist
    public void prePersist() {
        this.createdAt = LocalDateTime.now();
    }
}

OrderItem Entity (Junction Table)

@Entity
@Table(name = "order_items")
@Getter @Setter
@NoArgsConstructor @AllArgsConstructor
@Builder
public class OrderItem {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "order_id", nullable = false)
    @JsonBackReference
    private Order order;

    @ManyToOne
    @JoinColumn(name = "product_id", nullable = false)
    private Product product;

    @Column(nullable = false)
    private Integer quantity;

    @Column(name = "price_at_purchase", nullable = false)
    private BigDecimal priceAtPurchase;
}

Key Annotations Reference

Annotation Source Purpose
@Entity JPA Marks class as a DB table
@Table(name="...") JPA Sets the exact table name
@Id JPA Marks the primary key field
@GeneratedValue JPA Auto-increments the ID
@Column(nullable=false) JPA Adds NOT NULL constraint
@OneToMany JPA Defines 1-to-many relationship
@ManyToOne JPA Defines many-to-1 relationship
@JoinColumn JPA Specifies the foreign key column
@PrePersist JPA Method runs before INSERT
@Getter @Setter Lombok Auto-generates getters/setters
@Builder Lombok Enables builder design pattern
@NoArgsConstructor Lombok Generates no-arg constructor
@AllArgsConstructor Lombok Generates all-args constructor
@NotBlank Validation Field cannot be blank
@NotNull Validation Field cannot be null
@DecimalMin Validation Minimum decimal value
@Min Validation Minimum integer value
@JsonIgnore Jackson Excludes field from JSON output
@JsonManagedReference Jackson Handles bidirectional JSON serialization (parent side)
@JsonBackReference Jackson Handles bidirectional JSON serialization (child side)

10. Repository Layer

Repositories are interfaces (not classes) that extend JpaRepository. Hibernate writes the actual implementation — you get CRUD for free.

// ProductRepository.java
public interface ProductRepository extends JpaRepository<Product, Long> {
    // JpaRepository<EntityType, IDType>
    // Built-in methods available automatically:
    // .save(entity)        → INSERT or UPDATE
    // .findById(id)        → SELECT by primary key
    // .findAll()           → SELECT all rows
    // .deleteById(id)      → DELETE by primary key
}

// OrderRepository.java
public interface OrderRepository extends JpaRepository<Order, Long> { }

// OrderItemRepository.java
public interface OrderItemRepository extends JpaRepository<OrderItem, Long> { }

You define the interface. JPA + Hibernate writes the SQL. You never touch SQL for standard CRUD.


11. Service Layer

Services contain all business logic. They talk to repositories for persistence.

@Service
@RequiredArgsConstructor
public class ProductService {

    private final ProductRepository productRepository;

    // CREATE
    public Product createProduct(Product product) {
        return productRepository.save(product);
    }

    // UPDATE
    public Product updateProduct(Long id, Product product) {
        Product existing = productRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Product not found with ID: " + id));

        existing.setName(product.getName());
        existing.setDescription(product.getDescription());
        existing.setCategory(product.getCategory());
        existing.setPrice(product.getPrice());
        existing.setStockQuantity(product.getStockQuantity());

        return productRepository.save(existing);
    }

    // READ ALL
    public List<Product> getProducts() {
        return productRepository.findAll();
    }

    // READ ONE
    public Product getProductById(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Product not found with ID: " + id));
    }

    // DELETE
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
}

12. Controller Layer

Controllers expose REST endpoints. They only receive requests, validate, and delegate to the service.

@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;

    @PostMapping
    public Product createProduct(@Valid @RequestBody Product product) {
        return productService.createProduct(product);
    }

    @PutMapping("/{id}")
    public Product updateProduct(@PathVariable Long id,
                                  @Valid @RequestBody Product product) {
        return productService.updateProduct(id, product);
    }

    @GetMapping
    public List<Product> getProducts() {
        return productService.getProducts();
    }

    @GetMapping("/{id}")
    public Product getProductById(@PathVariable Long id) {
        return productService.getProductById(id);
    }

    @DeleteMapping("/{id}")
    public void deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
    }
}

Key Controller Annotations

Annotation Purpose
@RestController Marks class as a REST API controller
@RequestMapping("/path") Base path for all endpoints in class
@PostMapping Maps HTTP POST
@GetMapping Maps HTTP GET
@PutMapping("/{id}") Maps HTTP PUT with path variable
@DeleteMapping("/{id}") Maps HTTP DELETE with path variable
@PathVariable Extracts {id} from URL path
@RequestBody Reads JSON body from request
@Valid Triggers field-level validation annotations

13. DTO Layer

DTOs (Data Transfer Objects) are simplified classes used to accept input from clients. They don't have DB relationships — just the fields you need.

Why DTOs?

When creating an order, you don't want the client to send full relational objects. You want a clean, flat input structure.

OrderItemRequest DTO

@Getter @Setter
@NoArgsConstructor @AllArgsConstructor
@Builder
public class OrderItemRequest {

    @NotNull(message = "Product ID is required")
    private Long productId;

    @NotNull(message = "Quantity is required")
    @Min(value = 1, message = "Quantity must be at least one")
    private Integer quantity;
}

OrderRequest DTO

@Getter @Setter
@NoArgsConstructor @AllArgsConstructor
@Builder
public class OrderRequest {

    @NotBlank(message = "Customer name is required")
    private String customerName;

    @NotBlank(message = "Customer email is required")
    @Email(message = "Enter a valid email")
    private String customerEmail;

    @Valid
    @NotEmpty(message = "Order must contain at least one item")
    private List<OrderItemRequest> items;
}

14. Order Flow — Full Implementation

OrderService (Full Business Logic)

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;

    @Transactional
    public Order createOrder(OrderRequest orderRequest) {

        Order order = new Order();
        order.setCustomerName(orderRequest.getCustomerName());
        order.setCustomerEmail(orderRequest.getCustomerEmail());
        order.setStatus("confirmed");

        List<OrderItem> orderItems = new ArrayList<>();
        BigDecimal totalPrice = BigDecimal.ZERO;

        for (OrderItemRequest itemRequest : orderRequest.getItems()) {

            // 1. Check product exists
            Product product = productRepository.findById(itemRequest.getProductId())
                .orElseThrow(() -> new RuntimeException(
                    "Product not found with ID: " + itemRequest.getProductId()));

            // 2. Check stock availability
            if (product.getStockQuantity() < itemRequest.getQuantity()) {
                throw new RuntimeException("Not enough stock for: " + product.getName());
            }

            // 3. Calculate price
            BigDecimal priceOfItem = product.getPrice()
                .multiply(BigDecimal.valueOf(itemRequest.getQuantity()));
            totalPrice = totalPrice.add(priceOfItem);

            // 4. Deduct stock
            product.setStockQuantity(product.getStockQuantity() - itemRequest.getQuantity());
            productRepository.save(product);

            // 5. Build order item (captures price snapshot)
            OrderItem orderItem = OrderItem.builder()
                .order(order)
                .product(product)
                .quantity(itemRequest.getQuantity())
                .priceAtPurchase(product.getPrice())
                .build();

            orderItems.add(orderItem);
        }

        order.setOrderItems(orderItems);
        order.setTotalPrice(totalPrice);

        return orderRepository.save(order);
    }
}

Why @Transactional?

An order creation touches 3 tables (orders, order_items, products). If anything fails midway, @Transactional rolls back all changes, keeping the database consistent. Without it, you could deduct stock but fail to save the order.

OrderController

@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @PostMapping
    public Order createOrder(@Valid @RequestBody OrderRequest orderRequest) {
        return orderService.createOrder(orderRequest);
    }

    // Assignments: implement these two below ↓
    @GetMapping
    public List<Order> getOrders() {
        // TODO: implement in service
        return null;
    }

    @GetMapping("/{id}")
    public Order getOrderById(@PathVariable Long id) {
        // TODO: implement in service
        return null;
    }
}

15. Swagger / OpenAPI Documentation

What is it?

Auto-generated interactive API documentation. Instead of manually maintaining API docs, Spring Boot generates them from your code.

How to access

Run the app, then visit:

http://localhost:8080/swagger-ui/index.html

What you get

  • All controllers and endpoints listed automatically
  • "Try it out" button to test endpoints directly from the browser
  • Request body schemas shown automatically — no guessing about JSON format
  • Useful for sharing with frontend developers

The dependency (already added in pom.xml)

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.x.x</version>
</dependency>

No extra configuration required — it works out of the box.


16. Testing with an API Client

Use Requestly, Postman, or any HTTP client to test your endpoints.

Example: Create Product

POST http://localhost:8080/api/products
Content-Type: application/json

{
  "name": "T-Shirt",
  "category": "Apparel",
  "price": 199.00,
  "stockQuantity": 100
}

Example: Place an Order

POST http://localhost:8080/api/orders
Content-Type: application/json

{
  "customerName": "Dedar",
  "customerEmail": "dedar@example.com",
  "items": [
    {
      "productId": 1,
      "quantity": 5
    }
  ]
}

What happens internally on order creation:

  1. Validates input fields
  2. Finds the product in DB — throws error if missing
  3. Checks stock availability — throws error if insufficient
  4. Calculates total price (price × quantity)
  5. Deducts quantity from product stock
  6. Creates OrderItem record with price snapshot
  7. Saves the full Order with all items in a single transaction
  8. Returns the saved order object

17. Key Concepts Glossary

Term Explanation
ORM Object-Relational Mapping — maps Java classes to DB tables automatically
Hibernate The ORM library used by Spring Boot (implements JPA)
JPA Jakarta Persistence API — the standard interface Hibernate implements
DDL Data Definition Language — SQL for CREATE TABLE, ALTER TABLE
DML Data Manipulation Language — SQL for INSERT, UPDATE, DELETE
Dependency Injection Spring automatically creates and provides objects where needed (via @RequiredArgsConstructor + private final)
Bean A Spring-managed object. @Service, @Repository, @RestController all create beans
Transaction A group of DB operations that all succeed or all roll back together
DTO Data Transfer Object — simplified class for accepting/returning API data
Junction Table A table that connects two other tables in a many-to-many relationship
Builder Pattern A design pattern for constructing objects field by field (via @Builder)
Lombok Library that auto-generates getters, setters, constructors at compile time
Swagger/OpenAPI Standard for documenting REST APIs; SpringDoc generates it automatically
JDBC Java Database Connectivity — the protocol Java uses to talk to databases
Path Variable A value embedded in the URL: /products/{id}
Request Body JSON payload sent in the HTTP request body (POST/PUT)

18. Assignments / Take-Home Tasks

From the tutorial

  1. Implement getOrders() — Return all orders from the DB via orderRepository.findAll()

  2. Implement getOrderById(Long id) — Return a single order, throw RuntimeException if not found

  3. Add @PrePersist to other entities — Add createdAt auto-population to Product and OrderItem as well

  4. Add extra validations to Order entity — Fields like status, total price should have proper validation annotations

Stretch Goals

  1. Add error handling — Create a global @ControllerAdvice class to return clean error messages instead of stack traces

  2. Add updatedAt field — Use @PreUpdate annotation (works like @PrePersist but fires on UPDATE)

  3. Add a search endpointGET /api/products?category=Apparel using a custom JPA query method

  4. Add pagination — Replace findAll() with findAll(Pageable pageable) for large product lists


Quick Reference: Request Flow

POST /api/orders
      │
      ▼
OrderController.createOrder(@Valid @RequestBody OrderRequest)
      │  delegates to
      ▼
OrderService.createOrder(orderRequest)   ← @Transactional
      │  checks stock, calculates price, builds entities
      ▼
ProductRepository.findById()             ← reads from DB
ProductRepository.save()                 ← updates stock in DB
OrderRepository.save()                   ← writes order + items to DB
      │
      ▼
Returns Order object → JSON response to client

Built with Spring Boot 4.x · Hibernate · PostgreSQL (Supabase) · Lombok · SpringDoc OpenAPI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment