✅ Part of the Spring Boot Roadmap — a curated masterclass covering everything from Java basics to advanced Spring Boot practices and projects.
It is one of several interconnected repositories making up the broader Spring Boot Roadmap, which provides a complete learning journey.
This is a guide about learning Spring Data MongoDB to develop a simple CRUD Expense Manager REST API.
0. Getting Started
1. MongoDB
1.1 MongoDB Installation
1.2 Mongosh vs. Studio3T
1.3 MongoDB Commands
2. REST API
2.1 Dependencies
2.2 Model
2.3 Repository
2.4 Creating REST API (Service & Controller)
- Git
- Java 11
- Maven
-
Open your terminal or command prompt.
-
Clone the repository using Git:
git clone https://github.com/arsy786/spring-boot-mongodb-rest-api.git
-
Navigate to the cloned repository's root directory:
cd spring-boot-mongodb-rest-api -
Run the following Maven command to build and start the service:
# For Maven mvn spring-boot:run # For Maven Wrapper ./mvnw spring-boot:run
The application should now be running on localhost:8080.
Can install from MongoDB website or the Mac terminal.
Website: Install MongoDB Community Edition
Terminal: How to install Mongodb 5 | latest MAC installation (YouTube/HiteshChoudhary)
Common problem when installing via Terminal: zsh: command not found: mongo
Can use a shell (Terminal) or GUI to access MongoDB.
MongoDB Shell (Mongosh) is the quickest way to connect, configure, query, and work with your MongoDB database. It acts as a command-line client of the MongoDB server.
Mongosh: MongoDB Crash Course (YouTube/WebDevSimplified)
Studio3T is an interactive tool for querying, optimizing, and analyzing your MongoDB data. Studio3T also has its own built-in mongo shell, IntelliShell, which offers live error highlighting and smart auto-completion.
Studio3T: Getting Started with Studio 3T | The GUI for MongoDB (YouTube/Studio3T)
MongoDB has some commonly used commands which have been neatly gathered for quick reference in: MongoDB-Dark.pdf
Spring Data MongoDB Tutorial (ProgrammingTechie)
You can download the starter project with all the needed dependencies at Spring Initializr website with these dependencies:
- Spring Web
- Spring Data MongoDB
- Lombok
- Testcontainers
You can define the MongoDB properties by either using the MongoURI or by defining the host, username, password and database details:
application.properties:
# Approach 1
spring.data.mongodb.uri=mongodb://localhost:27017/expense-tracker
# Approach 2
spring.data.mongodb.host=localhost
spring.data.mongodb.username=<your-username>
spring.data.mongodb.password=<your-password>
spring.data.mongodb.database=expense-trackerNOTE: If authentication enabled, username and password must be provided.
expense.java:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Document("expense")
public class Expense {
@Id
private String id;
@Field("name")
@Indexed(unique = true)
private String expenseName;
@Field("category")
private ExpenseCategory expenseCategory;
@Field("amount")
private BigDecimal expenseAmount;
}ExpenseCategory.java:
public enum ExpenseCategory {
ENTERTAINMENT, GROCERIES, RESTAURANT, UTILITIES, MISC
}- Normal POJO class with annotations.
- Lombok annotations reduce boilerplate code.
- To define a Model Class as a MongoDB Document, we are going to use the @Document(“expense”) where expense is the name of the Document.
- @Id represents a unique identifier for our Document.
- We can represent different fields inside the Document using the @Field annotation.
- By default, Spring Data creates the field inside the document using the fieldName of the model class (Eg: expenseName), but we can override this by providing the required value to the annotation eg: @Field(“name”).
- To be able to easily retrieve the documents, we can also create an index using the @Indexed annotaion.
- We can also specify the unique=true property to make sure that this field is unique.
Spring Data MongoDB provides an interface called MongoRepository which provides an API to perform read and write operations to MongoDB.
ExpenseRepository.java:
@Repository
public interface ExpenseRepository extends MongoRepository<Expense, String> {
@Query("{'name': ?0}")
Optional<Expense> findByName(String name);
}We can also perform custom queries using the @Query annotation and by passing in the required query we need to run to this annotation. Spring Data will inject the value of the name field into the query, in the place of the ?0 placeholder.
Need to add the business logic in the Service layer.
ExpenseService.java:
@Service
@RequiredArgsConstructor
public class ExpenseService {
private final ExpenseRepository expenseRepository;
public void addExpense(Expense expense) {
expenseRepository.insert(expense);
}
public void updateExpense(Expense expense) {
Expense savedExpense = expenseRepository.findById(expense.getId()).orElseThrow(
() -> new RuntimeException(String.format("Cannot Find Expense by ID %s", expense.getId())));
savedExpense.setExpenseName(expense.getExpenseName());
savedExpense.setExpenseCategory(expense.getExpenseCategory());
savedExpense.setExpenseAmount(expense.getExpenseAmount());
expenseRepository.save(expense);
}
public Expense getExpense(String name) {
return expenseRepository.findByName(name).orElseThrow(
() -> new RuntimeException(String.format("Cannot Find Expense by Name - %s", name)));
}
public List<Expense> getAllExpenses() {
return expenseRepository.findAll();
}
public void deleteExpense(String id) {
expenseRepository.deleteById(id);
}
}Need to add the API Endpoints in the Controller layer.
ExpenseController.java:
@RestController
@RequestMapping("/expense")
@RequiredArgsConstructor
public class ExpenseController {
private final ExpenseService expenseService;
@PostMapping("/")
public ResponseEntity addExpense(@RequestBody Expense expense) {
expenseService.addExpense(expense);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@PutMapping
public ResponseEntity updateExpense(@RequestBody Expense expense) {
expenseService.updateExpense(expense);
return ResponseEntity.status(HttpStatus.OK).build();
}
@GetMapping
public ResponseEntity<List<Expense>> getAllExpenses() {
return ResponseEntity.ok(expenseService.getAllExpenses());
}
@GetMapping("/{name}")
public ResponseEntity getExpenseByName(@PathVariable String name) {
return ResponseEntity.ok(expenseService.getExpense(name));
}
@DeleteMapping("/{id}")
public ResponseEntity deleteExpense(@PathVariable String id) {
expenseService.deleteExpense(id);
return ResponseEntity.noContent().build();
}
}