Skip to content

Instantly share code, notes, and snippets.

@devded
Created June 7, 2026 08:56
Show Gist options
  • Select an option

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

Select an option

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

Spring Boot for Beginners — Building a "Creator Store" Backend

A complete, hands-on study guide built from a beginner-friendly Spring Boot tutorial. By the end you'll understand why, how, and what Spring Boot is — and you'll have built a real REST API backend that powers an influencer's merchandise store, the same way companies like Walmart, Netflix, Amazon, and Google use Spring Boot in production.


Table of Contents

  1. What You'll Build
  2. System Thinking: Understand the Problem First
  3. Designing the Database (ER Diagram)
  4. Designing the API Endpoints
  5. What Is Spring & Spring Boot?
  6. Project Setup
  7. Project Structure Explained
  8. Configuration & Connecting a Database
  9. Securing Secrets with .env
  10. The Layered (MVC) Architecture
  11. Entities (The Models)
  12. Entity Relationships
  13. Repositories
  14. Controllers
  15. Services (Business Logic)
  16. Testing the Product CRUD
  17. DTOs (Data Transfer Objects)
  18. Building the Order Feature
  19. Swagger / API Documentation
  20. Key Concepts Cheat Sheet
  21. Annotations Reference
  22. Take-Home Assignments

1. What You'll Build

You're going to play the role of a backend engineer ("the hero") helping Mr. User — an influencer who wants to set up a merchandise store to sell things like t-shirts, mugs, and stickers.

The store needs to answer everyday business questions such as:

  • What is the price of an item?
  • When was an order placed?
  • How many products are left in stock?
  • What items are in a particular order (for refunds/disputes)?

From these requirements, two core concepts emerge that the whole app revolves around:

  • Product — the things being sold
  • Order — the revenue-generating transactions

Key mindset: Always start with system thinking. Understand the problem before touching code. If Spring Boot disappeared tomorrow and you had to use another framework, your thought process should stay the same. Framework choice is an implementation detail; the design isn't.


2. System Thinking: Understand the Problem First

Break the requirements into abilities for each type of user.

As the creator/owner, you can:

  • Create a product
  • Read a product (and read all products)
  • Update a product
  • Delete a product

As a customer, you can:

  • Read all products
  • Read an individual product
  • Place an order
  • View order history

These abilities map directly onto CRUD operations (Create, Read, Update, Delete) and become your API endpoints later.


3. Designing the Database (ER Diagram)

Convert requirements into tables. The tutorial uses Eraser for diagrams, but any ER tool works.

products table

Column Type Notes
id primary key auto-generated
name string
description string optional
price decimal decimal so you can store paise/cents
stock_quantity integer
created_at timestamp metadata
updated_at timestamp metadata

(A category field is also tracked — e.g. apparel vs tech.)

orders table

Column Type Notes
id primary key
customer_name string
customer_email string
status string could be an enum; kept as string for simplicity
total_amount decimal
created_at timestamp

order_items table (the junction table)

Products and orders have no direct relationship on their own — you don't know which products belong to which orders. A junction table connects them.

Column Type Notes
id primary key
order_id FK → orders which order this belongs to
product_id FK → products which product this is for
price_at_purchase decimal snapshot of price when bought (see below)
quantity integer how many of this product

Why price_at_purchase? Product prices change over time (sales, demand). If you want reliable analytics — e.g. "how much revenue did I make in March?" — you must store the price at the time of purchase, not the current price.

Relationships

  • One order → many order itemsone-to-many
  • One product → many order items (a product can appear in many orders while stock lasts) → one-to-many

The junction table resolves what would otherwise be a many-to-many relationship between products and orders.


4. Designing the API Endpoints

Use plural nouns matching the table names, and standard HTTP methods.

Product endpoints (/api/products):

Action Method Path
Create product POST /api/products
Update product PUT /api/products/{id}
Read all products GET /api/products
Read one product GET /api/products/{id}
Delete product DELETE /api/products/{id}

Order endpoints (/api/orders):

Action Method Path
Place order POST /api/orders
Get order by ID GET /api/orders/{id}
Get all orders GET /api/orders

Now you have: requirements → DB design → endpoints. Time to bring in Spring.


5. What Is Spring & Spring Boot?

The food analogy

  • Cooking from scratch = writing everything yourself (find ingredients, cook, eat).
  • Frozen/ready-made food = someone already did the heavy work; you just heat and eat.

Spring Boot is the frozen food. Common problems (database connections, authentication, REST communication) have already been solved by developers worldwide who packaged solutions into reusable libraries. You don't reinvent the wheel — you pick the right package, configure it, and focus on building your application.

Spring Framework

An open-source framework (on GitHub) that provides core support for dependency injection, transaction management, web apps, data access, messaging, and more.

You're not the first person to need DB connections or auth — others solved it, so you install, configure, and use their work. Spring does the heavy lifting ("it's a bodybuilder lifting the 100 kg dumbbells for you — you just go to the gym"). Its logo is a green leaf.

Spring Framework vs Spring Boot

  • Spring Framework = the core; flexible but requires more setup.
  • Spring Boot = takes an opinionated view of building Spring apps so you "get up and running as quickly as possible." It's the frozen-food convenience layer on top of Spring.

Enterprises (Netflix and others) use it for real reasons: it's productive and battle-tested across use cases — microservices, cloud, web apps, serverless, even generative AI.


6. Project Setup

The tutorial uses IntelliJ IDEA (free Community edition available) and the Spring Initializr-style new-project wizard.

Settings chosen:

  • Name: creator-store
  • Language: Java
  • Build tool: Maven
  • Group ID: your website in reverse (e.g. in.anishwala / com.google)
  • JDK: default (install one if you don't have it)
  • Config format: YAML (.yml) — preferred for readability over .properties
  • Spring Boot version: latest stable

Starter dependencies added:

Dependency Purpose
PostgreSQL Driver Connect the Java app to a Postgres database
Lombok Auto-generate getters/setters/constructors (less boilerplate)
Spring Web Create and work with REST APIs
Spring Data JPA The ORM layer — converts Java classes ↔ DB tables
OpenAPI Spec (springdoc) Generates Swagger API documentation (not OpenAI!)

Need GraphQL instead of REST? Add "Spring for GraphQL". Dependencies are like the entries in a JavaScript package.json.

After creating, do an initial git commit so you can track progress from the start.


7. Project Structure Explained

File / Folder What it is
.idea/ IntelliJ IDE-specific config — don't touch
.mvn/ Maven wrapper config (where to find/download dependencies)
mvnw / mvnw.cmd Maven Wrapper — Maven bundled in the project, so no separate install needed
.gitignore Files/folders excluded from git
pom.xml The Maven project file (XML, not JSON) — like package.json. Lists dependencies, group/artifact ID, Spring Boot version
src/main/java Your actual source code
src/main/resources/application.yml Central app configuration (DB credentials, flags, etc.)
src/test Unit tests
target/ Generated build output — compiled .class files
Main Application class Entry point with the main method and a Run button

When you first run it with the Postgres dependency but no DB configured, the app fails on purpose with a "missing datasource URL" error. That's a good sign — it proves the dependency is active and waiting for config.


8. Configuration & Connecting a Database

Add database config to application.yml. You need three things: URL, username, password, plus ORM (Hibernate) settings.

Where to get a database

Options: Supabase, Neon, or a local Docker Postgres. The tutorial uses Supabase.

Supabase setup:

  1. Create a new project (name it creator-store).
  2. Generate and save the database password (you'll need it).
  3. Pick the closest region (lower latency).
  4. Click Connect → choose Session Pooler (not Direct Connection — direct connections aren't pooled and Supabase may reject them).
  5. Choose connection type JDBC (Java-friendly).
  6. Copy the connection string.

The URL starts with jdbc:postgresql://...never remove the jdbc: prefix; it's the protocol Java needs.

Hibernate / JPA config

  • JPA = Jakarta Persistence API (a standard Java gives for persistence).
  • Hibernate = the ORM that implements JPA (Java's equivalent of Drizzle/Prisma/Mongoose).

Settings to enable:

  • ddl-auto: update — automatically apply schema changes to the DB. (DB queries split into DML, DCL, DDL; this is the Data Definition Language part.)
  • show-sql: true — print the SQL Hibernate runs (great for debugging/learning).
  • format_sql: true — make that SQL human-readable.

ORM benefit: You write Java; Hibernate generates all the SQL — CREATE TABLE, INSERT, SELECT, foreign-key constraints — automatically. You won't write a single SQL line yourself.


9. Securing Secrets with .env

Never hardcode credentials in application.yml — once committed to git they're exposed to the world.

Steps:

  1. Add the dotenv-java dependency (search Maven Repository — the Java equivalent of npmjs.com — pick the latest version, copy the install snippet into pom.xml, and sync Maven).
  2. Create a .env file in the project root with key–value pairs:
    DATABASE_URL=jdbc:postgresql://...
    DATABASE_USERNAME=...
    DATABASE_PASSWORD=...
    
  3. When prompted, tell git not to track .env (it holds secrets).
  4. Load .env into system properties in your main class so Spring can read it:
    Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
    dotenv.entries().forEach(entry ->
        System.setProperty(entry.getKey(), entry.getValue()));
    This loops over each .env entry and sets it as a system property of the running Java process.
  5. Reference the variables in application.yml with ${...} syntax:
    spring:
      datasource:
        url: ${DATABASE_URL}
        username: ${DATABASE_USERNAME}
        password: ${DATABASE_PASSWORD}

Now application.yml is safe to commit (no secrets), and in production you set the env vars on the server. Run the app — you should see a successful DB connection log and the app running on port 8080.

Commit message convention used: chore: add .env (dotenv-java) for the application.


10. The Layered (MVC) Architecture

Spring Boot apps follow a clean separation of concerns. A request flows through layers:

Client (browser / mobile app / Postman)
        │
        ▼
   Controller   ← defines REST endpoints, handles HTTP. Single Responsibility: just route.
        │
        ▼
    Service     ← the "protein": all business logic lives here.
        │
        ▼
  Repository    ← talks to the database (CRUD). Powered by Hibernate.
        │
        ▼
    Entity      ← Java classes mapped to DB tables.
        │
        ▼
   Database (PostgreSQL)

Why separate layers? To follow SOLID principles. The controller should only map endpoints and hand off work — never hold business logic. Otherwise a controller with 5+ methods becomes bloated and unmaintainable.

Packages you'll create: entities, repositories, controllers, services, and later dto.


11. Entities (The Models)

Create a package entities with three classes: Product, Order, OrderItem.

Example: Product

Fields: id (long), name, description, category, price (BigDecimal), stockQuantity (integer).

Class-level annotations:

  • @Entity — marks this as a JPA entity (from Jakarta Persistence API).
  • @Table(name = "products") — sets the table name (plural).
  • @Getter / @Setter — Lombok generates accessors.
  • @AllArgsConstructor / @NoArgsConstructor — Lombok constructors.
  • @Builder — Lombok enables the Builder design pattern (create objects fluently without new).

Field-level annotations:

  • @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) — primary key, DB generates the sequence.
  • @Column(nullable = false) — column constraints.
  • @Column(name = "stock_quantity")map camelCase Java fields to snake_case DB columns.
  • @DecimalMin(value = "0", inclusive = false, message = "Price must be greater than zero") — validation for price.
  • @Min(value = 0, message = "Stock cannot be less than zero") — for integers (use @Min, not @DecimalMin).
  • @NotBlank(message = "Product name is required") — string can't be empty.
  • @NotNull(message = "Price is required") — value must be present.

Annotations attach metadata that extends a class's functionality without you writing the plumbing. They work at both class level (what the table is) and field level (constraints and validations).

When you run the app, Hibernate auto-creates the tables and foreign-key constraints. Verify in the Supabase Table Editor and Schema Visualizer — it matches your original ER diagram, with zero hand-written SQL.


12. Entity Relationships

This is where Spring Boot makes something that's painful in other frameworks remarkably simple.

OrderOrderItem (one-to-many)

In Order:

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

In OrderItem:

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

ProductOrderItem (one-to-many)

In Product:

@OneToMany(mappedBy = "product")
@JsonIgnore
private List<OrderItem> orderItems;

In OrderItem:

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

Why the JSON annotations?

  • @JsonIgnore on Product's orderItems — when you fetch a product you want its own fields, not every order it ever appeared in. This prevents accidentally dragging along huge nested data.
  • @JsonManagedReference / @JsonBackReference — prevent infinite recursion when serializing the two-way Order↔OrderItem relationship to JSON.
  • cascade = CascadeType.ALL — saving an order also saves its order items.

@PrePersist — a method that runs before a record is first saved, used to set createdAt:

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

13. Repositories

Create a repositories package. Repositories are interfaces (not classes!) that talk to the database. Each entity gets one.

public interface ProductRepository extends JpaRepository<Product, Long> {
}
  • Extend JpaRepository<EntityType, IdType> — here <Product, Long> because Product's id is a Long.
  • You write no method bodies. Hibernate provides the implementations of JPA's standard methods: save(), findAll(), findById(), deleteById(), and more — all "cooked in" out of the box.
  • Because you depend on the JPA standard (not a specific ORM), swapping ORMs later means changing config in application.yml, not your code.

Create three: ProductRepository, OrderRepository, OrderItemRepository.


14. Controllers

Create a controllers package. Controllers define the REST endpoints clients (frontend, mobile, Postman) talk to.

ProductController skeleton

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

    private final ProductService productService;  // dependency injection

    @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);
    }
}

Annotations explained:

  • @RestController — marks the class for REST API operations.
  • @RequestMapping("/api/products") — base path; every method lives under it.
  • @PostMapping, @PutMapping, @GetMapping, @DeleteMapping — map HTTP methods.
  • @RequestBody — read data from the request body (for objects you can't fit in a URL).
  • @PathVariable — read a value from the URL path (e.g. the {id}). The path variable name must match the method parameter.
  • @Valid — triggers the validations you defined on the entity/DTO.
  • @RequiredArgsConstructor (Lombok) — generates a constructor for all final fields, enabling dependency injection of the service.

Naming matters: use clear, readable method names (getProductById) so anyone reading the codebase instantly understands the intent.

The controller follows the Single Responsibility Principle — it only maps endpoints and hands work off to the service.


15. Services (Business Logic)

Create a services package. Annotate with @Service so Spring registers it as a bean available for injection. Inject the repository via @RequiredArgsConstructor + a private final field.

@Service
@RequiredArgsConstructor
public class ProductService {

    private final ProductRepository productRepository;

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

    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);
    }

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

    public Product getProductById(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Product not available"));
        // Alternatively, return Optional<Product> and let the caller handle absence.
    }

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

Key points:

  • Always check existence before updating — never blindly update an ID that may not exist.
  • findById returns an Optional — either handle it with .orElseThrow(...) or return the Optional to the caller.
  • Getters/setters come from Lombok.
  • The service is the only layer that talks to the repository when persistence is needed.

Dependency Injection (DI): instead of creating objects yourself with new, you declare what you need (a final field) and Spring "injects" it for you. This is a core Spring concept.

That completes the full MVC flow for Product: Model → Repository → Controller → Service → DB.


16. Testing the Product CRUD

Use an API client — the tutorial uses Requestly (Postman or any client works). Run the app first (confirm DB connection + port 8080), then create a collection creator-store.

Create a product (POST http://localhost:8080/api/products):

{
  "name": "T-Shirt",
  "stockQuantity": 100,
  "price": 199,
  "category": "apparel"
}

Description is optional, so omitting it still works. Response returns the object with an auto-generated id of 1.

Read all (GET /api/products): returns a JSON array of product objects.

Read one (GET /api/products/2): pass the ID as a path variable; returns one product.

Update (PUT /api/products/2): change the mapping to PUT, send the new fields in the body (e.g. bump price to 150, quantity to 9000). Confirm with a fresh GET.

Persistence check: stop and restart the app — the data is still there because it lives in the database, not memory.

Delete (DELETE /api/products/2): no body needed; removes the product. A subsequent GET shows it's gone.

That's the full CRUD lifecycle on Product.


17. DTOs (Data Transfer Objects)

When accepting an order, you don't want clients sending the entity's complex relationships. A DTO exposes only the fields you actually need.

Create a dto package.

OrderItemRequest

@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

@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;
}

Why DTOs? They keep request payloads clean and free of entity relationships, and let you apply validations specific to incoming requests.

Don't forget the @JsonManagedReference / @JsonBackReference + cascade = CascadeType.ALL wiring on the Order/OrderItem entities so saving an order cascades to its items.


18. Building the Order Feature

The OrderService is where the whole app comes together. Inject both repositories:

@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. Verify the 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 this product");
            }

            // 3. Calculate line price (current price × quantity) and add to total
            BigDecimal priceOfItem = product.getPrice()
                .multiply(BigDecimal.valueOf(itemRequest.getQuantity()));
            totalPrice = totalPrice.add(priceOfItem);

            // 4. Reduce stock and persist the product
            product.setStockQuantity(product.getStockQuantity() - itemRequest.getQuantity());
            productRepository.save(product);

            // 5. Build the order item (junction record) using the Builder pattern
            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?

Placing an order does multiple SQL operations: insert into orders, insert into order_items, and update products (stock). Grouping them in a single transaction (from Jakarta Persistence API) means: if anything fails, everything rolls back; if all succeed, it commits. No half-finished orders.

Watch out: the tutorial notes a common mistake — initially the service returned the in-memory order object instead of calling orderRepository.save(order). Always persist through the repository. Debugging like this is normal and expected.

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);
    }
}

Test it (POST http://localhost:8080/api/orders):

{
  "customerName": "Anirudh",
  "customerEmail": "anirudh@gmail.com",
  "items": [
    { "productId": 1, "quantity": 5 }
  ]
}

The response returns the order with customer info, status, a dynamically calculated total price, the linked order items, the related products, and a createdAt timestamp.


19. Swagger / API Documentation

The springdoc-openapi dependency (added at project creation) auto-generates interactive API docs.

  1. Confirm the dependency is in pom.xml (search "Spring Doc" / OpenAPI specification).
  2. Run the app and visit: http://localhost:8080/swagger-ui/index.html

You'll see a (dark-mode) UI listing all controllers and their CRUD operations. You can:

  • Expand an endpoint, click Try it out, enter an ID/body, and Execute — e.g. GET product ID 1 returns a 200 with the t-shirt details.
  • For POST/PUT, Swagger auto-generates an example request body based on your DTOs and path variables — no guessing the format (unlike raw Postman/Requestly).

As a backend developer, you hand this Swagger documentation to the frontend team so they can test your APIs without you explaining every payload.


20. Key Concepts Cheat Sheet

Concept Meaning
Spring Framework Open-source core framework: dependency injection, transactions, web, data access, messaging.
Spring Boot Opinionated convenience layer on Spring — fast setup, sensible defaults.
ORM Object-Relational Mapping — converts Java objects ↔ DB tables.
JPA Jakarta Persistence API — the standard Java provides for persistence.
Hibernate The ORM that implements JPA and writes the SQL for you.
Entity A Java class mapped to a database table.
Repository Interface (extends JpaRepository) that gives CRUD methods for free.
Controller Defines REST endpoints; single responsibility = routing.
Service Holds business logic; talks to repositories.
DTO Data Transfer Object — clean request/response shapes without entity relationships.
Dependency Injection Spring supplies your objects (beans) instead of you using new.
Bean An object managed by the Spring container.
Junction table Resolves many-to-many by linking two tables (here order_items).
Transaction A group of DB operations that all succeed or all roll back.
Builder pattern Fluent object creation without the new keyword (via Lombok @Builder).
Lombok Library that auto-generates getters/setters/constructors/builders.
Maven Build tool & dependency manager (pom.xml); analog of npm/package.json.

21. Annotations Reference

Annotation Where Purpose
@Entity class Marks a JPA entity.
@Table(name="...") class Sets the DB table name.
@Id field Marks the primary key.
@GeneratedValue(strategy = GenerationType.IDENTITY) field DB auto-generates the ID.
@Column(nullable=false, name="...") field Column constraints / snake_case name.
@NotBlank / @NotNull / @NotEmpty field Presence validations.
@Min / @DecimalMin field Numeric minimums (@Min for ints, @DecimalMin for decimals).
@Email field Validates email format.
@PrePersist method Runs before first save (e.g. set createdAt).
@OneToMany(mappedBy=..., cascade=...) field One-to-many relationship.
@ManyToOne field Many-to-one relationship.
@JoinColumn(name=..., nullable=false) field Foreign-key column.
@JsonIgnore field Exclude field from JSON output.
@JsonManagedReference / @JsonBackReference field Prevent infinite recursion in bidirectional JSON.
@Getter / @Setter class Lombok accessors.
@NoArgsConstructor / @AllArgsConstructor class Lombok constructors.
@Builder class Lombok builder pattern.
@RequiredArgsConstructor class Lombok constructor for final fields (DI).
@RestController class REST API controller.
@RequestMapping("/path") class Base path for endpoints.
@PostMapping / @GetMapping / @PutMapping / @DeleteMapping method Map HTTP verbs.
@RequestBody param Bind the request body to an object.
@PathVariable param Bind a URL path segment to a parameter.
@Valid param Trigger bean validation.
@Service class Marks a service bean.
@Transactional method Run inside a DB transaction.

22. Take-Home Assignments

The tutorial leaves these for you to implement (take a screenshot when done and share it):

  1. Get all ordersGET /api/orders. Add getOrders() returning List<Order> in the service (currently a TODO returning null) and wire it to the controller.
  2. Get order by IDGET /api/orders/{id}. Returns a single Order (useful for order tracking). Add the supporting service method and wire it up.
  3. Add a @PrePersist createdAt to other entities (the pattern was only shown on Order).
  4. Add extra validations to the Order entity (none were added beyond the basics).
  5. Homework lookups: Google these terms to solidify understanding — JPA, DDL/DML/DCL, Builder design pattern, getters/setters, .class files, dependency injection.

Final Notes

  • Commit often with conventional messages (chore:, feat:, etc.) so your git history tells the story of how the project was built.
  • The exact same design thinking (requirements → DB → endpoints → layers) applies no matter which framework you use — Spring Boot just makes the implementation faster and cleaner.
  • Real production systems at Netflix, Amazon, Walmart, and Google are built on these same fundamentals.

Happy building! 🌱

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