Spring Boot with MongoDB
Hello, everyone! 👋 In this article, we’ll dive into building a REST API from the ground up using Spring Boot and MongoDB, and I promise you’ll uncover some valuable insights by the end !
Setting Up Your Project
First, let’s create a project with the right dependencies. Go to Spring Initializer and select:
- Spring Web
- Spring Data MongoDB
- Lombok (optional but recommended)
Once you generate the project lets open in any IDE , here my fav one is IntelliJ so lets open and create some packages inside our source .
I prefer to separate logic to make things clear , now don't forget to start mongoDB server in your machine i hope you already installed .
lets create an mongo database and collection , i am using MongoDB Compass here to easily work with mongo db you can use mongo shell as well.
once you created database lets add some properties into application.properties to connect our mongo db to spring boot app.
we have many ways to configure i prefer uri in above example .
Let’s build a simple Product Management APIs to understand the basics.
Product is my document where each object of Product will be store as document in mongo products collection.
Product.java
@Document(collection = "products")
@Data // Lombok annotation for getters, setters, etc.
public class Product {
@Id
private String id; // MongoDB uses String IDs
@Indexed(unique = true)
private String sku;
private String name;
private Double price;
private String description;
@Field("created_at") // Custom field name in MongoDB
private LocalDateTime createdAt;
private List<String> categories;
// Nested document example
private ProductDetails details;
}
ProductDetails.java
@Data
public class ProductDetails {
private String manufacturer;
private String origin;
private Double weight;
}
@Document
: Marks class as MongoDB document (like @Entity in JPA)@Id
: Unique identifier field@Indexed
: Creates database index@Field
: Customizes field name in MongoDB
lets create repository to add some database operations its like our DAO layer to manage db transactions .
@Repository
public interface ProductRepository extends MongoRepository<Product, String> {
// Built-in methods we get for free to use
// - save()
// - findById()
// - findAll()
// - delete()
// ... and many more!
// Custom queries
List<Product> findByNameContaining(String name);
List<Product> findByPriceBetween(Double minPrice, Double maxPrice);
// Custom query with MongoDB syntax
@Query("{'price': {$lt: ?0}, 'categories': ?1}")
List<Product> findCheapProductsByCategory(Double maxPrice, String category);
}
Let’s create a service layer to add business logic .
@Service
@Slf4j // Lombok logging
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Product createProduct(Product product) {
product.setCreatedAt(LocalDateTime.now());
log.info("Creating new product: {}", product.getName());
return productRepository.save(product);
}
public List<Product> getAllProducts() {
return productRepository.findAll();
}
public Product getProductById(String id) {
return productRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Product not found"));
}
public Product updateProduct(String id, Product product) {
Product existing = getProductById(id);
// Update fields
existing.setName(product.getName());
existing.setPrice(product.getPrice());
existing.setDescription(product.getDescription());
existing.setCategories(product.getCategories());
existing.setDetails(product.getDetails());
return productRepository.save(existing);
}
public void deleteProduct(String id) {
if (!productRepository.existsById(id)) {
throw new NotFoundException("Product not found");
}
productRepository.deleteById(id);
}
// Custom business logic
public List<Product> findProductsByPriceRange(Double min, Double max) {
return productRepository.findByPriceBetween(min, max);
}
}
Lets make a controller layer to write REST endpoints
@RestController
@RequestMapping("/api/products")
@Slf4j
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product created = productService.createProduct(product);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(created);
}
@GetMapping
public List<Product> getAllProducts(
@RequestParam(required = false) Double minPrice,
@RequestParam(required = false) Double maxPrice
) {
if (minPrice != null && maxPrice != null) {
return productService.findProductsByPriceRange(minPrice, maxPrice);
}
return productService.getAllProducts();
}
@GetMapping("/{id}")
public Product getProduct(@PathVariable String id) {
return productService.getProductById(id);
}
@PutMapping("/{id}")
public Product updateProduct(
@PathVariable String id,
@RequestBody Product product
) {
return productService.updateProduct(id, product);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteProduct(@PathVariable String id) {
productService.deleteProduct(id);
}
}
lets run the application & check your endpoints in POSTMAN.
Now that you’ve built your product APIs , let’s look at a few extra things to make them even better:
- Add Pagination to Keep Things Manageable
Imagine loading hundreds or thousands of products in one go — it’d be slow for your users. pagination is our friend here. With just a few lines, you can let users request specific “pages” of results, controlling how many products they get at a time and in what order to make UI effienciet .
@GetMapping("/products")
public Page<Product> getProductsPaged(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "name") String sortBy
) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return productRepository.findAll(pageable);
}
2. Use Complex Queries for Fine-Tuned Results
Ever need to get just the right products based on multiple criteria? MongoTemplate
is our go-to tool. ex , if you want to pull products from a specific category and only those priced above a certain amount, here’s how:
public List<Product> findComplexQuery(String category, Double minPrice) {
Query query = new Query();
query.addCriteria(
Criteria.where("categories").in(category)
.and("price").gte(minPrice)
);
return mongoTemplate.find(query, Product.class);
}
Here Criteria API is Homework for you , its interesting Concept to write complex queries . Just explore n try once
Great for scenarios like filtering products for a sale or specific recommendations.
3. Boost Performance with Indexing
Indexes are like shortcuts for our database, making searches faster. When you’re querying large datasets, indexing fields like price
and categories
can make a big difference.
keep in mind :
- Index frequently used fields: If
price
andcategories
are often in your queries, index them. MongoDB will retrieve data much faster this way. - Combine fields with compound indexes: When queries use multiple fields together, create a compound index to handle them efficiently.
- Balance reads and writes: While indexes speed up reads, they slow down writes a bit, so use them wisely.
// Over the document classe ..
@Indexed(fields = {"price", "categories"})
//ex. compound index are combination of two fields
@Document(collection = "products")
@CompoundIndex(def = "{'name': 1, 'price': -1}")
public class Product {
@Indexed(direction = IndexDirection.ASCENDING)
private String sku;
@TextIndexed
private String description;
}
1. The
1
forname
indicates ascending order, while-1
forprice
specifies descending order.2. With this compound index, queries that involve both
name
andprice
(e.g., sorting byname
first and then byprice
) will be optimized, especially for large datasets.
Thanks for following along — let’s continue exploring and enhancing our skills together !
Happy coding! 🚀