- Comprendre le rôle de Spring Boot.
- Créer et exécuter une première API REST.
- Explorer la structure d’un projet Spring Boot.
Sans framework :
- Beaucoup de configuration manuelle
- Code répétitif
- Sécurité, logs, injections, etc. à gérer soi-même
Avec Spring Boot :
✅ Gain de temps
✅ Convention > configuration
✅ Outils intégrés (serveur web, logs, tests)
Spring = Framework Java pour apps d’entreprise
Spring Boot = version simplifiée et automatisée de Spring
→ Crée une appli web en 2 minutes avec des dépendances prêtes à l’emploi
src/
└── main/
└── java/
└── com/example/demo/
├── DemoApplication.java
├── controller/ 👈 contrôleurs REST (API)
│ └── UserController.java
├── model/ 👈 modèles ou entités (domain)
│ └── User.java
├── repository/ 👈 accès à la base de données
│ └── UserRepository.java
└── service/ 👈 logique métier (règles, traitements)
└── UserService.java
- JDK 17 (java -version)
- VsCode
Plugin VSCode
- Extension Pack for Java
- Spring Boot Extension Pack
- Lombok Annotations Support for VS Code
-
Aller sur https://start.spring.io
-
Choisir :
- Project : Maven
- Language : Java
- Spring Boot : 3.x
- Java 17 : Version la plus populaire
- Dependencies : Spring Web, Lombok, JPA, H2
-
Télécharger le projet et l’ouvrir dans VsCode.
Configuration de application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
- Vérifier que l'on a bien Java d'installé sur sa machine.
- Dans VS Code → File → Open Folder → Le dossier dézippé du projet généré
- VS Code détecte automatiquement le projet Java (Maven)
- Laisse VS Code importer les dépendances
- Lance ton application avec './mvnw spring-boot:run'
- Go sur http://localhost:8080
Sur le dossier com.example.demo → New File
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello() {
return "Hello Spring Boot 🚀";
}
}➡️ Lancer l’app → ouvrir http://localhost:8080/hello
Créer un endpoint /hello/{name} qui retourne :
"Hello, [name]! Bienvenue dans Spring Boot."
- Utiliser
@PathVariable String name - Retourner une
String
Corrigé :
@GetMapping("/hello/{name}")
public String sayHello(@PathVariable String name) {
return "Hello, " + name + " ! Bienvenue dans Spring Boot.";
}- Spring Boot = framework opinionated → il décide pour toi.
- Fichiers clés :
Application.java(main),application.properties,pom.xml.
- Structurer une application en couches.
- Créer un CRUD complet avec données en mémoire.
- Comprendre
@Service,@Repository,@Autowired.
Controller → reçoit les requêtes
Service → logique métier
Repository → accès aux données
Model → objets manipulés (entités)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Task {
private Long id;
private String title;
private boolean done;
}@Service
public class TaskService {
private List<Task> tasks = new ArrayList<>();
public TaskService() {
tasks.add(new Task(1L, "Apprendre Spring Boot", false));
}
public List<Task> findAll() { return tasks; }
public Task findById(Long id) {
return tasks.stream()
.filter(t -> t.getId().equals(id))
.findFirst()
.orElseThrow(() -> new RuntimeException("Task not found"));
}
public Task add(Task task) {
task.setId((long) (tasks.size() + 1));
tasks.add(task);
return task;
}
public void delete(Long id) {
tasks.removeIf(t -> t.getId().equals(id));
}
}@RestController
@RequestMapping("/tasks")
public class TaskController {
private final TaskService taskService;
public TaskController(TaskService taskService) {
this.taskService = taskService;
}
@GetMapping
public List<Task> getAll() { return taskService.findAll(); }
@GetMapping("/{id}")
public Task getOne(@PathVariable Long id) { return taskService.findById(id); }
@PostMapping
public Task add(@RequestBody Task task) { return taskService.add(task); }
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) { taskService.delete(id); }
}Objectif : permettre la mise à jour d’une tâche (changer le titre ou l’état done).
Corrigé :
@PutMapping("/{id}")
public Task update(@PathVariable Long id, @RequestBody Task newTask) {
Task t = taskService.findById(id);
t.setTitle(newTask.getTitle());
t.setDone(newTask.isDone());
return t;
}- Injection de dépendances via constructeur = meilleure pratique.
- Le service contient la logique métier, pas le contrôleur.
- Configurer une base de données (H2 ou MySQL).
- Créer des entités JPA et repositories.
- Comprendre la magie de
JpaRepository.
ORM = Object Relational Mapping
→ traduit des objets Java en tables SQL
Spring Data JPA = simplifie l’accès à la base
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Task {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private boolean done;
}Dépendance à ajouter :
<!-- Add Spring Data JPA so JpaRepository is available -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>import org.springframework.data.jpa.repository.JpaRepository;
public interface TaskRepository extends JpaRepository<Task, Long> {
List<Task> findByDone(boolean done);
}Commande :
.\mvnw.cmd spring-boot:run
.\mvnw.cmd clean package
@Service
public class TaskService {
private final TaskRepository repo;
public TaskService(TaskRepository repo) { this.repo = repo; }
public List<Task> findAll() { return repo.findAll(); }
public Task findById(Long id) { return repo.findById(id).orElseThrow(); }
public Task add(Task t) { return repo.save(t); }
public void delete(Long id) { repo.deleteById(id); }
}spring.datasource.url=jdbc:h2:mem:testdb
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=updateCréer un endpoint /tasks/done pour retourner uniquement les tâches terminées.
Corrigé :
@GetMapping("/done")
public List<Task> getDoneTasks() {
return taskService.findByDone(true);
}- Spring Data JPA gère automatiquement les CRUD.
- Pas besoin d’écrire du SQL.
H2est très pratique pour tester sans serveur externe.
Ajouter cette option au controleur
''' @CrossOrigin(origins = "*") // autorise toutes les origines '''
Le html de base pour tester
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Test TaskController</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 30px;
background: #f5f5f5;
}
h1 { color: #333; }
section {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
input, button {
padding: 8px;
margin: 5px;
border-radius: 5px;
border: 1px solid #ccc;
}
button {
cursor: pointer;
background-color: #1976d2;
color: white;
border: none;
}
button:hover {
background-color: #0d47a1;
}
table {
border-collapse: collapse;
width: 100%;
margin-top: 10px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background: #1976d2;
color: white;
}
</style>
</head>
<body>
<h1>🧪 Test du TaskController</h1>
<section>
<h2>🔍 Récupérer les tâches</h2>
<button onclick="getAllTasks()">Afficher toutes</button>
<button onclick="getDoneTasks()">Afficher les 'done'</button>
<table id="taskTable">
<thead>
<tr><th>ID</th><th>Title</th><th>Done</th><th>Actions</th></tr>
</thead>
<tbody></tbody>
</table>
</section>
<section>
<h2>➕ Ajouter une tâche</h2>
<input id="addTitle" placeholder="Titre de la tâche">
<label><input type="checkbox" id="addDone"> Done ?</label>
<button onclick="addTask()">Ajouter</button>
</section>
<section>
<h2>✏️ Mettre à jour une tâche</h2>
<input id="updateId" type="number" placeholder="ID">
<input id="updateTitle" placeholder="Nouveau titre">
<label><input type="checkbox" id="updateDone"> Done ?</label>
<button onclick="updateTask()">Mettre à jour</button>
</section>
<section>
<h2>🗑️ Supprimer une tâche</h2>
<input id="deleteId" type="number" placeholder="ID">
<button onclick="deleteTask()">Supprimer</button>
</section>
<script>
const BASE_URL = "http://localhost:8080/tasks";
async function getAllTasks() {
const res = await fetch(BASE_URL);
const data = await res.json();
renderTable(data);
}
async function getDoneTasks() {
const res = await fetch(`${BASE_URL}/done`);
const data = await res.json();
renderTable(data);
}
async function addTask() {
const title = document.getElementById("addTitle").value;
const done = document.getElementById("addDone").checked;
const res = await fetch(BASE_URL, {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ title, done })
});
if (res.ok) {
alert("Tâche ajoutée !");
getAllTasks();
}
}
async function updateTask() {
const id = document.getElementById("updateId").value;
const title = document.getElementById("updateTitle").value;
const done = document.getElementById("updateDone").checked;
const res = await fetch(`${BASE_URL}/${id}`, {
method: "PUT",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ title, done })
});
if (res.ok) {
alert("Tâche mise à jour !");
getAllTasks();
}
}
async function deleteTask() {
const id = document.getElementById("deleteId").value;
const res = await fetch(`${BASE_URL}/${id}`, { method: "DELETE" });
if (res.ok) {
alert("Tâche supprimée !");
getAllTasks();
}
}
function renderTable(tasks) {
const tbody = document.querySelector("#taskTable tbody");
tbody.innerHTML = "";
tasks.forEach(t => {
const row = document.createElement("tr");
row.innerHTML = `
<td>${t.id}</td>
<td>${t.title}</td>
<td>${t.done ? "✅" : "❌"}</td>
<td>
<button onclick="prefillUpdate(${t.id}, '${t.title}', ${t.done})">🖋️</button>
<button onclick="deleteTaskById(${t.id})">🗑️</button>
</td>`;
tbody.appendChild(row);
});
}
function prefillUpdate(id, title, done) {
document.getElementById("updateId").value = id;
document.getElementById("updateTitle").value = title;
document.getElementById("updateDone").checked = done;
}
function deleteTaskById(id) {
document.getElementById("deleteId").value = id;
deleteTask();
}
// Charger la liste au démarrage
getAllTasks();
</script>
</body>
</html>- Tester les endpoints.
- Documenter l’API avec Swagger.
- Générer le
.jarfinal.
@SpringBootTest
@AutoConfigureMockMvc
class TaskControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void shouldReturnAllTasks() throws Exception {
mockMvc.perform(get("/tasks"))
.andExpect(status().isOk());
}
}Pour lancer les tests
./mvnw testpackage com.example.demo; // <-- adapte ce package à ton projet
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; // pour GET, POST, PUT, DELETE
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; // pour status, content, jsonPath
@SpringBootTest // Démarre tout le contexte Spring Boot pour tester l’application complète
@AutoConfigureMockMvc // Active MockMvc pour simuler des requêtes HTTP sans serveur réel
class TaskControllerTest {
@Autowired
private MockMvc mockMvc; // Objet fourni par Spring pour simuler des appels HTTP
// --------------------------------------------------------
// 🧪 1️⃣ TEST : Récupération de toutes les tâches
// --------------------------------------------------------
@Test
void shouldReturnAllTasks() throws Exception {
mockMvc.perform(get("/tasks")) // Simule une requête HTTP GET sur /tasks
.andExpect(status().isOk()) // Vérifie que la réponse a le code 200 OK
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) // Vérifie que la réponse est du JSON
.andExpect(jsonPath("$").isArray()); // Vérifie que le JSON est bien un tableau (liste de tâches)
}
// --------------------------------------------------------
// 🧪 2️⃣ TEST : Ajout d’une nouvelle tâche
// --------------------------------------------------------
@Test
void shouldAddNewTask() throws Exception {
// JSON représentant une nouvelle tâche
String newTaskJson = "{\"title\": \"Faire les courses\", \"done\": false}";
mockMvc.perform(post("/tasks") // Envoie une requête HTTP POST sur /tasks
.contentType(MediaType.APPLICATION_JSON) // Spécifie qu’on envoie du JSON
.content(newTaskJson)) // Corps de la requête = le JSON ci-dessus
.andExpect(status().isOk()) // Vérifie que la réponse a bien un code 200 (succès)
.andExpect(jsonPath("$.title").value("Faire les courses")) // Vérifie que la tâche retournée contient bien le bon titre
.andExpect(jsonPath("$.done").value(false)); // Vérifie que le champ done = false
}
// --------------------------------------------------------
// 🧪 3️⃣ TEST : Récupération d’une tâche par son ID
// --------------------------------------------------------
@Test
void shouldReturnOneTaskById() throws Exception {
// D’abord, on crée une tâche pour avoir un ID existant
String newTaskJson = "{\"title\": \"Lire un livre\", \"done\": false}";
// On enregistre la tâche et récupère le résultat dans une variable
String response = mockMvc.perform(post("/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content(newTaskJson))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
// Exemple simple : on sait que la première tâche aura souvent l’ID 1 si la base est vide.
// Mais dans un vrai projet, tu pourrais extraire l’ID du JSON via un parseur.
mockMvc.perform(get("/tasks/1")) // On récupère la tâche avec l’ID 1
.andExpect(status().isOk()) // On attend un 200 OK
.andExpect(jsonPath("$.title").exists()); // Vérifie qu’il y a bien un titre dans la réponse
}
// --------------------------------------------------------
// 🧪 4️⃣ TEST : Mise à jour d’une tâche existante
// --------------------------------------------------------
@Test
void shouldUpdateTask() throws Exception {
// D’abord on crée une tâche à modifier
String newTaskJson = "{\"title\": \"Faire le ménage\", \"done\": false}";
mockMvc.perform(post("/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content(newTaskJson))
.andExpect(status().isOk());
// Puis on prépare le JSON de mise à jour
String updatedTaskJson = "{\"title\": \"Faire le grand ménage\", \"done\": true}";
// Appel PUT pour mettre à jour la tâche avec l’ID 1
mockMvc.perform(put("/tasks/1")
.contentType(MediaType.APPLICATION_JSON)
.content(updatedTaskJson))
.andExpect(status().isOk()) // Vérifie que la requête est OK
.andExpect(jsonPath("$.title").value("Faire le grand ménage")) // Vérifie le nouveau titre
.andExpect(jsonPath("$.done").value(true)); // Vérifie que "done" est passé à true
}
// --------------------------------------------------------
// 🧪 5️⃣ TEST : Suppression d’une tâche
// --------------------------------------------------------
@Test
void shouldDeleteTask() throws Exception {
// D’abord on crée une tâche à supprimer
String newTaskJson = "{\"title\": \"Aller courir\", \"done\": false}";
mockMvc.perform(post("/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content(newTaskJson))
.andExpect(status().isOk());
// Suppression de la tâche d’ID 1
mockMvc.perform(delete("/tasks/1")) // Envoie une requête DELETE
.andExpect(status().isOk()); // Vérifie que la suppression renvoie 200 OK
}
// --------------------------------------------------------
// 🧪 6️⃣ TEST : Récupération des tâches "done" uniquement
// --------------------------------------------------------
@Test
void shouldReturnDoneTasks() throws Exception {
// On ajoute une tâche marquée comme faite
String doneTaskJson = "{\"title\": \"Apprendre Spring\", \"done\": true}";
mockMvc.perform(post("/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content(doneTaskJson))
.andExpect(status().isOk());
// On appelle GET /tasks/done pour récupérer uniquement les "done"
mockMvc.perform(get("/tasks/done"))
.andExpect(status().isOk()) // Vérifie que la requête réussit
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) // Réponse JSON attendue
.andExpect(jsonPath("$[0].done").value(true)); // Vérifie que la 1ère tâche est bien done = true
}
}Ajouter la dépendance dans pom.xml :
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>Lancer l’app → http://localhost:8080/swagger-ui.html
mvn clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar
Ajouter une contrainte :
@NotBlank
private String title;Tester qu’une requête POST vide renvoie une erreur 400.
- Les tests assurent la stabilité du code.
- Swagger facilite la communication entre développeurs.
- Spring Boot embarque Tomcat : pas besoin de serveur externe.
✅ Créer un projet Spring Boot
✅ Structurer un code propre (Controller / Service / Repository)
✅ Gérer une base de données avec JPA
✅ Tester et documenter leur API
- Spring Security (authentification)
- Déploiement sur Render / Railway
- Variables d’environnement avec
application.yml - Relations complexes JPA (
ManyToMany,OneToMany, etc.)