Building a Shopping List API with Spring Data JPA and H2
A family trip to the local Woollies on a Saturday morning is part of the weekend rhythm for many Aussie households, and forgetting the soy sauce or the Tim Tams can derail dinner plans. A small backend service that tracks a shopping list removes that friction, letting everyone at home tick off items from their phone before someone heads out to the servo. With Spring Data JPA and the H2 in-memory database, such a service can be built in a single afternoon, ready to plug into a mobile app or a simple web front-end.
This walkthrough covers the moving parts of a working Shopping List API: the Maven setup, the JPA entity, the repository, the REST controller, the H2 console configuration, and a few test calls. It assumes a working knowledge of Java and Spring Boot, so if any of the foundation concepts feel shaky, a guide to Java covers the building blocks in plain language before going further.
Setting up the Spring Boot project
The fastest way to scaffold the project is through Spring Initializr, choosing Maven, Java 17, and adding the dependencies for Spring Web, Spring Data JPA, and the H2 Database. Once generated, the pom.xml file holds everything needed to compile and run, including the embedded Tomcat server that ships with Spring Boot. For teams split between Melbourne and Perth who collaborate across time zones, this single-jar approach means a colleague can clone the repo, run mvn spring-boot:run, and have a working API within minutes.
The application.properties file is where the H2 datasource is configured for development. Setting spring.h2.console.enabled=true exposes a browser-based console at /h2-console, which is handy when poking at the data during a debugging session. Keeping the database in memory keeps local development fast and avoids leaving stray data files on the laptop, which matters when working from a café in Brisbane or on the train home through the suburbs.
Defining the ShoppingItem entity
The core of the API is a ShoppingItem JPA entity that maps to a table called shopping_items. Each item carries an id, a name, a quantity, a unit of measure, a category such as "dairy" or "snacks", and a boolean flag indicating whether it has been picked up. Annotating the class with @Entity and the primary key with @Id and @GeneratedValue is enough for Hibernate to generate the schema on startup.
A second field, a LocalDateTime called addedAt, captures when the item went onto the list. This timestamp is useful for sorting older entries to the top or for pruning items that have been sitting on the list for weeks, which is a common occurrence in shared household lists across Adelaide and the Gold Coast where multiple flatmates add and forget things.
Building the repository layer
Spring Data JPA removes the boilerplate around CRUD operations by generating repository implementations at runtime. Extending JpaRepository<ShoppingItem, Long> exposes methods like findAll, save, deleteById, and findById without writing a single line of implementation code. Custom queries, such as listing only the unchecked items or filtering by category, can be added through method naming conventions.
Defining List<ShoppingItem> findByPurchasedFalseOrderByAddedAtAsc(); in the repository interface is enough for Spring to translate that into the underlying SQL at startup. It is the kind of expressive layer that keeps the codebase small, which is helpful when the project is being passed between contractors during a busy sprint.
Exposing REST endpoints with a controller
A @RestController class translates HTTP calls into repository operations and returns JSON responses. Returning a ResponseEntity with appropriate HTTP status codes keeps the API predictable for client developers.
Endpoints exposed by the controller:
GET /api/itemsreturns every item on the listPOST /api/itemscreates a new item from a JSON bodyPUT /api/items/{id}updates the name, quantity, or category of an existing entryDELETE /api/items/{id}removes the itemPATCH /api/items/{id}/toggleflips the purchased flag
A useful extra pattern is the toggle endpoint that mirrors how a shopper would tap an item in the aisle. This shape shows up in plenty of side projects across Australian dev meetups, where the goal is a tidy contract rather than a sprawling one.
Configuring H2 for local and test use
H2 shines in development and testing because it boots in milliseconds and resets cleanly between test runs. The application can run in two modes: an in-memory mode for tests, and a file-based mode for local development so the data survives restarts. Both modes use the same JDBC URL pattern, with mem:testdb for memory and file:./data/shoppingdb for persistence.
| Feature | H2 | HSQLDB | Derby | SQLite |
|---|---|---|---|---|
| In-memory mode | Yes | Yes | Yes | Limited |
| Browser console | Yes (built-in) | No | No | No |
| Spring Boot starter | h2 |
hsqldb |
derby |
jdbc-sqlite |
| SQL dialect closeness | PostgreSQL | HSQLDB native | SQL standard | SQLite native |
| Concurrency model | Multi-threaded | Multi-threaded | Multi-threaded | Single-writer |
For a small API like this one, H2's console and PostgreSQL-compatible mode make it the most convenient pick during the build phase.
Testing the endpoints and wrapping up
Running the application and hitting the endpoints with curl or Postman confirms the wiring is correct. A typical flow is to POST a few items representing a weekly Coles run, GET the list, toggle one item as purchased, and DELETE another that turned out to be unnecessary. Watching the H2 console show the rows appear and disappear in real time is a satisfying way to confirm the stack is behaving as expected.
Habits worth keeping when extending the project:
- Keep entity field validation on the model with Jakarta annotations like
@NotBlank. - Use DTOs in the controller layer to avoid leaking internal fields.
- Write integration tests with
@SpringBootTestso the H2 schema is rebuilt each run. - Profile query performance with
spring.jpa.show-sql=truebefore adding complexity.
From here, the same backend can power a simple Android app built in Brisbane, a weekend hack for a Melbourne hackathon, or a kiosk running in the staff kitchen of a Sydney consultancy. The combination of Spring Data JPA and H2 keeps the focus on the domain logic rather than the plumbing.