EasyShop (EZ Shop) demonstrates enterprise-level backend development with secure JWT authentication, role-based access control, and full e-commerce functionality. The API features a complete product catalog with advanced search and filtering capabilities, persistent shopping cart management that maintains state across sessions, user profile management for shipping details, and separate admin/customer roles. It follows industry best practices including the DAO pattern for data access, prepared statements for SQL injection prevention, and stateless REST architecture. EZ Shop provides JSON responses, integrates seamlessly with any frontend framework, and showcases proficiency in Spring Security, database design, and RESTful API development.
- User Authentication & Authorization: JWT-based authentication with role-based access control
- Category Management: Browse and manage product categories
- Product Search & Filtering: Advanced search with multiple filter options
- Shopping Cart: Persistent shopping cart with full CRUD operations
- User Profiles: Manage user information and shipping details
- Security: Spring Security integration with JWT tokens
- Java 17
- Spring Boot
- Spring Security with JWT
- MySQL Database
- JDBC for data access
- BCrypt for password encryption
CategoriesController: Class to manage API requests for the categories- Product Search Fix: Corrected the price range filtering logic
- Product Update Fix: Fixed duplicate product creation on update
Unit Tests have been implemented in the following classes
MySqlCategoryDaoTestMySqlProductDaoTestMySqlProfileDaoTestMySqlShoppingCartDaoTest
- Admin: username:
admin, password:password - User: username:
user, password:password
| Method | Endpoint | Description | Access |
|---|---|---|---|
| POST | /register |
Register new user | Public |
| POST | /login |
Login user | Public |
| Method | Endpoint | Description | Access |
|---|---|---|---|
| GET | /categories |
Get all categories | Public |
| GET | /categories/{id} |
Get category by ID | Public |
| GET | /categories/{id}/products |
Get products in category | Public |
| POST | /categories |
Create category | Admin |
| PUT | /categories/{id} |
Update category | Admin |
| DELETE | /categories/{id} |
Delete category | Admin |
| Method | Endpoint | Description | Access |
|---|---|---|---|
| GET | /products |
Search products with filters | Public |
| GET | /products/{id} |
Get product by ID | Public |
| POST | /products |
Create product | Admin |
| PUT | /products/{id} |
Update product | Admin |
| DELETE | /products/{id} |
Delete product | Admin |
| Method | Endpoint | Description | Access |
|---|---|---|---|
| GET | /cart |
Get user's cart | Authenticated |
| POST | /cart/products/{id} |
Add product to cart | Authenticated |
| PUT | /cart/products/{id} |
Update item quantity | Authenticated |
| DELETE | /cart |
Clear cart | Authenticated |
| Method | Endpoint | Description | Access |
|---|---|---|---|
| GET | /profile |
Get user profile | Authenticated |
| PUT | /profile |
Update user profile | Authenticated |
![]() Home Screen |
![]() Logging In |
![]() Shopping Cart |
![]() User Profile |
The mapRow method in the MySqlProfileDao class demonstrates an elegant implementation of method mapping to save repetitive use of the same code. It is used when resultset mapping is needed in the other methods in the class:
private Profile mapRow(ResultSet row) throws SQLException {
Profile profile = new Profile();
profile.setUserId(row.getInt("user_id"));
profile.setFirstName(row.getString("first_name"));
profile.setLastName(row.getString("last_name"));
profile.setPhone(row.getString("phone"));
profile.setEmail(row.getString("email"));
profile.setAddress(row.getString("address"));
profile.setCity(row.getString("city"));
profile.setState(row.getString("state"));
profile.setZip(row.getString("zip"));
return profile;
}The search method in the products-services.js javascript file shows a clever approach to mapping images to specific products. It uses the product's name to map it to the corresponding image in the if statement. It also uses the axios as the API call with the const url above it:
search()
{
const url = `${config.baseUrl}/products${this.filter.queryString()}`;
axios.get(url)
.then(response => {
let data = {};
data.products = response.data;
data.products.forEach(product => {
if (!this.hasPhoto(product.imageUrl)) {
if (product.name && product.name.includes("Smart Home Hub")) { product.imageUrl = "smart-home.png"; }
if (product.name && product.name.includes("Portable Charger")) { product.imageUrl = "portable-charger.png"; }
if (product.name && product.name.includes("Men's Swim Trunks")) { product.imageUrl = "mens-swim-trunks.png"; }
if (product.name && product.name.includes("Men's Casual Shirt")) { product.imageUrl = "mens-casual-shirt.png"; }
if (product.name && product.name.includes("Women's Jumpsuit")) { product.imageUrl = "womens-jumpsuit.png"; }
if (product.name && product.name.includes("Women's Swimwear")) { product.imageUrl = "womens-swimwear.png"; }
if (product.name && product.name.includes("Air Fryer")) { product.imageUrl = "air-fryer.png"; }
if (product.name && product.name.includes("Cookies")) { product.imageUrl = "cookies.png"; }
if (product.name && product.name.includes("Grandma Cookies")) { product.imageUrl = "grandma-cookies.png"; }
}
});
templateBuilder.build('product', data, 'content', this.enableButtons);
})
.catch(error => {
const data = {
error: "Searching products failed."
};
templateBuilder.append("error", data, "errors")
});
}Front-end customization related to the dark blue color theme was supported by Anthropicβs Claude Opus 4 (2025). Product image generation was assisted by OpenAIβs ChatGPT-4o (2025).
Anthropic. (2025). Claude Opus 4 [Large language model]. Anthropic. https://www.anthropic.com OpenAI. (2025). ChatGPT-4o [Multimodal language model]. OpenAI. https://openai.com/chatgpt



