Cart Management with JWT
Erstellt am: 17. Januar 2025
Erstellt am: 17. Januar 2025
I have created a cart backend application using SpringBoot. Each category can have many products, each product belongs to a category and each product have some images. I have also implemented JWT authentication for getting user details and role. Need help to implement CartItem and CartLogic for a user. Also admin should be able to get specific cart by Id
public interface CartRepository extends JpaRepository<Cart,Long> {
}
@Service
@RequiredArgsConstructor
public class CartService implements ICartService {
textprivate final CartRepository cartRepository; private final CartItemRepository cartItemRepository; private final UserRepository userRepository; @Autowired private UserDetailsServiceImpl userDetailsService; @Override public Cart getCart(Long id) { Cart cart = cartRepository.findById(id).orElseThrow(()-> new ResourceNotFoundException("Cart not found")); BigDecimal totalAmount = cart.getTotalAmount(); cart.setTotalAmount(totalAmount); return cartRepository.save(cart); } @Transactional @Override public void clearCart(Long id) { Cart cart = getCart(id); cartItemRepository.deleteAllByCartId(id); cart.getItems().clear(); cartRepository.deleteById(id); } @Override public BigDecimal getTotalPrice(Long id) { Cart cart = getCart(id); return cart.getItems().stream().map(CartItem::getTotalPrice).reduce(BigDecimal.ZERO,BigDecimal::add); } @Override public Cart getCartForUser(String username) { User user = userRepository.findByUsername(username) .orElseThrow(() -> new ResourceNotFoundException("User not found")); return cartRepository.findByUser(user) .orElseGet(() -> initializeNewCartForUser(user)); } @Override public Long initializeNewCart() { Cart newCart = new Cart(); return cartRepository.save(newCart).getId(); }
}
private final ICartService cartService;
text@GetMapping("/{cartId}/my-cart") public ResponseEntity<ApiResponse> getCart(@PathVariable Long cartId) { try { Cart cart = cartService.getCart(cartId); return ResponseEntity.ok(new ApiResponse("Success", cart)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(NOT_FOUND).body(new ApiResponse(e.getMessage(), null)); } } @DeleteMapping("/{cartId}/clear") public ResponseEntity<ApiResponse> clearCart( @PathVariable Long cartId) { try { cartService.clearCart(cartId); return ResponseEntity.ok(new ApiResponse("Clear Cart Success!", null)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(NOT_FOUND).body(new ApiResponse(e.getMessage(), null)); } } @GetMapping("/{cartId}/cart/total-price") public ResponseEntity<ApiResponse> getTotalAmount( @PathVariable Long cartId) { try { BigDecimal totalPrice = cartService.getTotalPrice(cartId); return ResponseEntity.ok(new ApiResponse("Total Price", totalPrice)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(NOT_FOUND).body(new ApiResponse(e.getMessage(), null)); } }
public interface CartItemRepository extends JpaRepository<CartItem,Long> {
void deleteAllByCartId(Long id);
}
private final CartItemRepository cartItemRepository;
private final CartRepository cartRepository;
private final ICartService cartService;
private final IProductService productService;
text@Override public void addItemToCart(Long cartId, Long productId, int quantity) { Cart cart = cartService.getCart(cartId); Product product = productService.getProductById(productId); CartItem cartItem = cart.getItems() .stream() .filter(item -> item.getProduct().getId().equals(productId)) .findFirst().orElse(new CartItem()); if (cartItem.getId() == null) { cartItem.setCart(cart); cartItem.setProduct(product); cartItem.setQuantity(quantity); cartItem.setUnitPrice(product.getPrice()); } else { cartItem.setQuantity(cartItem.getQuantity() + quantity); } cartItem.setTotalPrice(); cart.addItem(cartItem); cartItemRepository.save(cartItem); cartRepository.save(cart); } @Override public void removeItemFromCart(Long cartId, Long productId) { Cart cart = cartService.getCart(cartId); CartItem cartItem = cart.getItems() .stream().filter(item -> item.getProduct().getId().equals(productId)).findFirst().orElseThrow(()-> new ResourceNotFoundException("Product Not Found")); cart.removeItem(cartItem); cartRepository.save(cart); } @Override public void updateItemQuantity(Long cartId, Long productId, int quantity) { Cart cart = cartService.getCart(cartId); cart.getItems() .stream() .filter(item -> item.getProduct().getId().equals(productId)) .findFirst() .ifPresent(item -> { item.setQuantity(quantity); item.setUnitPrice(item.getProduct().getPrice()); item.setTotalPrice(); }); BigDecimal totalAmount = cart.getItems() .stream().map(CartItem ::getTotalPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); cart.setTotalAmount(totalAmount); cartRepository.save(cart); } @Override public CartItem getCartItem(Long cartId, Long productId) { Cart cart = cartService.getCart(cartId); return cart.getItems() .stream() .filter(item -> item.getProduct().getId().equals(productId)) .findFirst().orElseThrow(() -> new ResourceNotFoundException("Item not found")); }
private final ICartItemService cartItemService;
private final ICartService cartService;
text@PostMapping("/item/add") public ResponseEntity<ApiResponse> addItemToCart(@RequestParam(required = false) Long cartId, @RequestParam Long productId, @RequestParam Integer quantity) { try { if (cartId == null) { cartId= cartService.initializeNewCart(); } cartItemService.addItemToCart(cartId, productId, quantity); return ResponseEntity.ok(new ApiResponse("Add Item Success", null)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(NOT_FOUND).body(new ApiResponse(e.getMessage(), null)); } } @DeleteMapping("/cart/{cartId}/item/{itemId}/remove") public ResponseEntity<ApiResponse> removeItemFromCart(@PathVariable Long cartId, @PathVariable Long itemId) { try { cartItemService.removeItemFromCart(cartId, itemId); return ResponseEntity.ok(new ApiResponse("Remove Item Success", null)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(NOT_FOUND).body(new ApiResponse(e.getMessage(), null)); } } @PutMapping("/cart/{cartId}/item/{itemId}/update") public ResponseEntity<ApiResponse> updateItemQuantity(@PathVariable Long cartId, @PathVariable Long itemId, @RequestParam Integer quantity) { try { cartItemService.updateItemQuantity(cartId, itemId, quantity); return ResponseEntity.ok(new ApiResponse("Update Item Success", null)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(NOT_FOUND).body(new ApiResponse(e.getMessage(), null)); } }
Below is a step-by-step guide and illustrative code snippets to help you implement cart logic tied to a specific user (via JWT) and also allow an admin to fetch any cart by its ID. The main points we'll cover are:
java@Entity @Table(name = "carts") public class Cart { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // ManyToOne to associate each Cart with one User @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; @OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true) private List<CartItem> items = new ArrayList<>(); private BigDecimal totalAmount = BigDecimal.ZERO; // utility methods public void addItem(CartItem item) { items.add(item); item.setCart(this); } public void removeItem(CartItem item) { items.remove(item); item.setCart(null); } // getters and setters }
java@Entity @Table(name = "cart_items") public class CartItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // ManyToOne to associate CartItem with a single Cart @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "cart_id") private Cart cart; // ManyToOne to associate CartItem with a single Product @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "product_id") private Product product; private int quantity; private BigDecimal unitPrice; private BigDecimal totalPrice; public void setTotalPrice() { this.totalPrice = this.unitPrice.multiply(new BigDecimal(this.quantity)); } // getters and setters }
java@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private String role; // e.g. ROLE_USER or ROLE_ADMIN // ... }
javapublic interface CartRepository extends JpaRepository<Cart,Long> { Optional<Cart> findByUser(User user); // If you prefer searching by userId directly: // Optional<Cart> findByUserId(Long userId); }
java@Service @RequiredArgsConstructor @Transactional public class CartService implements ICartService { private final CartRepository cartRepository; private final UserRepository userRepository; private final CartItemRepository cartItemRepository; /** * 1) Find cart by cartId * 2) Optionally re-calculate and store total amount */ @Override public Cart getCart(Long id) { Cart cart = cartRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Cart not found")); // If you want to recalculate or set the total each time: BigDecimal totalAmount = cart.getItems().stream() .map(CartItem::getTotalPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); cart.setTotalAmount(totalAmount); // Return or save if you want to persist the new total return cartRepository.save(cart); } @Override public void clearCart(Long id) { Cart cart = getCart(id); // reuses getCart logic cartItemRepository.deleteAllByCartId(id); cart.getItems().clear(); cart.setTotalAmount(BigDecimal.ZERO); // Depending on your business logic: // You might want to keep the empty cart in DB // or actually delete it. E.g., to keep it: cartRepository.save(cart); // to remove it entirely: // cartRepository.deleteById(id); } @Override public BigDecimal getTotalPrice(Long id) { Cart cart = getCart(id); return cart.getItems().stream() .map(CartItem::getTotalPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); } /** * Retrieve or create a Cart for a specific user */ @Override public Cart getCartForUser(String username) { User user = userRepository.findByUsername(username) .orElseThrow(() -> new ResourceNotFoundException("User not found")); return cartRepository.findByUser(user) .orElseGet(() -> initializeNewCartForUser(user)); } /** * If the user doesn't have a cart, create a new Cart */ private Cart initializeNewCartForUser(User user) { Cart newCart = new Cart(); newCart.setUser(user); newCart.setTotalAmount(BigDecimal.ZERO); return cartRepository.save(newCart); } /** * Utility if you want to create an empty cart * not associated with any user * (You might not actually need this). */ @Override public Long initializeNewCart() { Cart newCart = new Cart(); newCart.setTotalAmount(BigDecimal.ZERO); return cartRepository.save(newCart).getId(); } }
Key changes above:
initializeNewCartForUser(User user)
that sets the User
reference on the Cart.getCartForUser(username)
uses findByUser(user)
to return an existing cart or create a new one.javapublic interface CartItemRepository extends JpaRepository<CartItem,Long> { void deleteAllByCartId(Long cartId); }
java@Service @RequiredArgsConstructor @Transactional public class CartItemService implements ICartItemService { private final CartItemRepository cartItemRepository; private final CartRepository cartRepository; private final ICartService cartService; private final IProductService productService; @Override public void addItemToCart(Long cartId, Long productId, int quantity) { Cart cart = cartService.getCart(cartId); Product product = productService.getProductById(productId); // Attempt to find an existing CartItem for the product CartItem cartItem = cart.getItems().stream() .filter(item -> item.getProduct().getId().equals(productId)) .findFirst() .orElse(new CartItem()); // If new (no ID set), populate details if (cartItem.getId() == null) { cartItem.setCart(cart); cartItem.setProduct(product); cartItem.setQuantity(quantity); cartItem.setUnitPrice(product.getPrice()); } else { // otherwise, just adjust the quantity cartItem.setQuantity(cartItem.getQuantity() + quantity); } // Recompute the total price for this CartItem cartItem.setTotalPrice(); // Add to cart if it's new if (!cart.getItems().contains(cartItem)) { cart.addItem(cartItem); } // Persist cartItemRepository.save(cartItem); cartRepository.save(cart); } @Override public void removeItemFromCart(Long cartId, Long productId) { Cart cart = cartService.getCart(cartId); CartItem cartItem = cart.getItems().stream() .filter(item -> item.getProduct().getId().equals(productId)) .findFirst() .orElseThrow(() -> new ResourceNotFoundException("Product Not Found in Cart")); cart.removeItem(cartItem); cartItemRepository.delete(cartItem); cartRepository.save(cart); } @Override public void updateItemQuantity(Long cartId, Long productId, int quantity) { Cart cart = cartService.getCart(cartId); cart.getItems().stream() .filter(item -> item.getProduct().getId().equals(productId)) .findFirst() .ifPresent(item -> { item.setQuantity(quantity); item.setUnitPrice(item.getProduct().getPrice()); item.setTotalPrice(); }); // Recompute total for entire cart BigDecimal totalAmount = cart.getItems().stream() .map(CartItem::getTotalPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); cart.setTotalAmount(totalAmount); cartRepository.save(cart); } @Override public CartItem getCartItem(Long cartId, Long productId) { Cart cart = cartService.getCart(cartId); return cart.getItems().stream() .filter(item -> item.getProduct().getId().equals(productId)) .findFirst() .orElseThrow(() -> new ResourceNotFoundException("Item not found")); } }
You can design your controllers in (at least) two ways:
username
from the JWT token in the SecurityContext).ROLE_ADMIN
, and can specify a cartId
to retrieve any user’s cart).Below are some example endpoints. Adjust them to fit your naming conventions and desired URL structure.
Make sure your SecurityConfig (or WebSecurityConfig) has something like:
java@EnableWebSecurity @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/api/cart/**").hasRole("USER") .anyRequest().authenticated() .and() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // ... .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } // ... }
java@RestController @RequestMapping("/api/cart") @RequiredArgsConstructor public class CartController { private final ICartService cartService; private final ICartItemService cartItemService; /** * Get the current user's cart. * The username is extracted from the principal/JWT. */ @GetMapping("/my-cart") public ResponseEntity<ApiResponse> getMyCart(Authentication authentication) { try { String username = authentication.getName(); // or if you have a custom JwtUserDetails, cast and get the username Cart cart = cartService.getCartForUser(username); return ResponseEntity.ok(new ApiResponse("User Cart", cart)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ApiResponse(e.getMessage(), null)); } } /** * Clear the current user's cart */ @DeleteMapping("/my-cart/clear") public ResponseEntity<ApiResponse> clearMyCart(Authentication authentication) { try { String username = authentication.getName(); Cart cart = cartService.getCartForUser(username); cartService.clearCart(cart.getId()); return ResponseEntity.ok(new ApiResponse("Clear Cart Success", null)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ApiResponse(e.getMessage(), null)); } } /** * Add item to the current user's cart (by productId). * If cart doesn't exist, it will be created automatically in getCartForUser. */ @PostMapping("/my-cart/item") public ResponseEntity<ApiResponse> addItemToMyCart( Authentication authentication, @RequestParam Long productId, @RequestParam Integer quantity) { try { String username = authentication.getName(); Cart cart = cartService.getCartForUser(username); cartItemService.addItemToCart(cart.getId(), productId, quantity); return ResponseEntity.ok(new ApiResponse("Item added to cart", null)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ApiResponse(e.getMessage(), null)); } } /** * Remove item from current user's cart */ @DeleteMapping("/my-cart/item") public ResponseEntity<ApiResponse> removeItemFromMyCart( Authentication authentication, @RequestParam Long productId) { try { String username = authentication.getName(); Cart cart = cartService.getCartForUser(username); cartItemService.removeItemFromCart(cart.getId(), productId); return ResponseEntity.ok(new ApiResponse("Item removed from cart", null)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ApiResponse(e.getMessage(), null)); } } /** * Update quantity of an item in the current user's cart */ @PutMapping("/my-cart/item") public ResponseEntity<ApiResponse> updateItemQuantityInMyCart( Authentication authentication, @RequestParam Long productId, @RequestParam int quantity) { try { String username = authentication.getName(); Cart cart = cartService.getCartForUser(username); cartItemService.updateItemQuantity(cart.getId(), productId, quantity); return ResponseEntity.ok(new ApiResponse("Item quantity updated", null)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ApiResponse(e.getMessage(), null)); } } /** * Get total price of the current user's cart */ @GetMapping("/my-cart/total") public ResponseEntity<ApiResponse> getMyCartTotal(Authentication authentication) { try { String username = authentication.getName(); Cart cart = cartService.getCartForUser(username); BigDecimal total = cartService.getTotalPrice(cart.getId()); return ResponseEntity.ok(new ApiResponse("Total Price", total)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ApiResponse(e.getMessage(), null)); } } }
java@RestController @RequestMapping("/api/admin/cart") @PreAuthorize("hasRole('ADMIN')") @RequiredArgsConstructor public class AdminCartController { private final ICartService cartService; /** * Admin can fetch any cart by ID */ @GetMapping("/{cartId}") public ResponseEntity<ApiResponse> getCartById(@PathVariable Long cartId) { try { Cart cart = cartService.getCart(cartId); return ResponseEntity.ok(new ApiResponse("Admin fetched cart", cart)); } catch (ResourceNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new ApiResponse(e.getMessage(), null)); } } // Add any other admin-only cart endpoints, e.g. forcibly clearing user cart, etc. }
In the above approach:
cartId
around. We always infer it from the JWT via cartService.getCartForUser(username)
.cartId
.Because you’ve already implemented JWT authentication, you just need to extract the username (and roles) from the Authentication
or SecurityContextHolder
inside your controllers.
Example using method parameter:
java@GetMapping("/my-cart") public ResponseEntity<ApiResponse> getMyCart(Authentication authentication) { String username = authentication.getName(); // ... }
Alternatively, you can do:
javaString username = SecurityContextHolder.getContext().getAuthentication().getName();
/api/cart/my-cart
with the JWT in the Authorization: Bearer <token>
header.
Authentication
.cartService.getCartForUser(username)
→ either fetches existing or creates a new Cart
./api/admin/cart/{cartId}
to see any user’s cart.
ROLE_ADMIN
.cartService.getCart(cartId)
and returns it.Cart
, you also fetch CartItems
if you need them for the response. You may want to use @EntityGraph
or explicit fetch joins in your repository if you want to avoid lazy-loading exceptions outside a transaction.Cart.totalAmount
in the DB or compute it on the fly from the items each time. If stored, keep it in sync carefully.quantity
is positive, product is in stock, etc., as part of your business rules.@Transactional
if you need to handle partial-failure rollbacks.Cart
entity to the User
, then using the username from JWT to locate or create the user’s cart.With these pieces in place, you should have a clean, secure way to let users manage their own cart, and let admins view/manipulate any cart in the system.