RESTful API Todo 만들기
RESTful API URI 경로
사용자의 모든 Todo 목록 조회
- HTTP 메소드: GET
- URI: /api/users/{userId}/todos
- 설명: 특정 사용자의 모든 Todo 항목을 페이지네이션하여 조회합니다.
특정 Todo 조회
- HTTP 메소드: GET
- URI: /api/users/{userId}/todos/{todoId}
- 설명: 특정 사용자의 특정 Todo 항목의 세부 정보를 조회합니다.
새로운 Todo 생성
- HTTP 메소드: POST
- URI: /api/users/{userId}/todos
- 설명: 특정 사용자에게 새로운 Todo 항목을 생성합니다.
기존 Todo 수정
- HTTP 메소드: PUT
- URI: /api/users/{userId}/todos/{todoId}
- 설명: 특정 사용자의 특정 Todo 항목을 수정합니다.
Todo 삭제
- HTTP 메소드: DELETE
- URI: /api/users/{userId}/todos/{todoId}
- 설명: 특정 사용자의 특정 Todo 항목을 삭제합니다.

URI 설계 원칙
자원(Resource) 중심: URI는 명사로 구성되며, 액션을 나타내는 동사를 사용하지 않습니다.
예: /todos 대신 /api/users/{userId}/todos
계층적 구조: Todo는 User에 속한 자원이므로, URI에 User의 식별자를 포함시켜 계층적 관계를 표현합니다.
예: /api/users/{userId}/todos/{todoId}
일관성: 모든 CRUD(Create, Read, Update, Delete) 작업에 대해 일관된 패턴을 유지하여 API의 예측 가능성을 높입니다.
RESTful 원칙 준수: HTTP 메소드(GET, POST, PUT, DELETE)를 적절히 사용하여 자원의 상태를 관리합니다.
추가 고려사항
버전 관리: API의 변경에 대비하여 URI에 버전 정보를 포함시키는 것을 고려할 수 있습니다.
예: /api/v1/users/{userId}/todos
검색 및 필터링: 필요에 따라 쿼리 파라미터를 사용하여 Todo를 검색하거나 필터링할 수 있습니다.
예: /api/users/{userId}/todos?done=true
페이지네이션: 대량의 데이터를 효율적으로 처리하기 위해 페이지 번호와 페이지 크기를 쿼리 파라미터로 받을 수 있습니다.
예: /api/users/{userId}/todos?page=1&size=10
1. ApiUserController
package net.macaronics.springboot.webapp.api.controller;
import java.net.URI;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import net.macaronics.springboot.webapp.dto.ResponseDTO;
import net.macaronics.springboot.webapp.dto.todo.TodoCreateDTO;
import net.macaronics.springboot.webapp.dto.todo.TodoResponseDTO;
import net.macaronics.springboot.webapp.dto.todo.TodoUpdateDTO;
import net.macaronics.springboot.webapp.service.TodoService;
import net.macaronics.springboot.webapp.utils.PageMaker;
@RestController
@RequestMapping("/api/users/{userId}/todos")
@RequiredArgsConstructor
public class ApiTodoController {
private final TodoService todoService;
/**
* HATEOAS 링크 추가 메서드
*/
private TodoResponseDTO addTodoLinks(TodoResponseDTO todoResponse) {
todoResponse.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(ApiTodoController.class)
.getTodoById(todoResponse.getUserId(), todoResponse.getId()))
.withSelfRel());
return todoResponse;
}
/**
* 1. GET /api/users/{userId}/todos
* 전체 Todo 목록 조회
*/
@Operation(summary = "전체 Todo 목록 조회", description = "특정 사용자의 모든 Todo를 페이지네이션하여 조회합니다.")
@GetMapping
public ResponseEntity<?> getAllTodos(@PathVariable Long userId,@Valid PageMaker pageMaker) {
int pageInt = pageMaker.getPage() == null ? 0 : pageMaker.getPage();
PageRequest pageable = PageRequest.of(pageInt, 10); // 예: 페이지당 10개
Page<TodoResponseDTO> todoPage = todoService.getTodosByUserId(userId, pageable);
List<TodoResponseDTO> todosWithLinks = todoPage.getContent().stream().map(this::addTodoLinks).collect(Collectors.toList());
CollectionModel<TodoResponseDTO> collectionModel = CollectionModel.of(todosWithLinks);
collectionModel.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(ApiTodoController.class).getAllTodos(userId, pageMaker)).withSelfRel());
return ResponseEntity.ok().cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
.body(ResponseDTO.builder()
.code(1)
.message("success")
.data(collectionModel)
.build());
}
/**
* 2. GET /api/users/{userId}/todos/{todoId}
* 개별 Todo 조회
*/
@Operation(summary = "개별 Todo 조회", description = "특정 사용자의 특정 Todo를 조회합니다.")
@GetMapping("/{todoId}")
public ResponseEntity<?> getTodoById(
@PathVariable Long userId,
@PathVariable Long todoId) {
TodoResponseDTO todo = todoService.getTodoById(userId, todoId);
addTodoLinks(todo);
return ResponseEntity.ok(todo);
}
/**
* 3. POST /api/users/{userId}/todos
* Todo 생성
*/
@Operation(summary = "Todo 생성", description = "특정 사용자에게 새로운 Todo를 생성합니다.")
@PostMapping
public ResponseEntity<?> createTodo(
@PathVariable Long userId,
@Valid @RequestBody TodoCreateDTO todoCreateDTO,
BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(null, bindingResult);
}
TodoResponseDTO createdTodo = todoService.createTodo(userId, todoCreateDTO);
addTodoLinks(createdTodo);
URI location = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(ApiTodoController.class).getTodoById(userId, createdTodo.getId())).toUri();
return ResponseEntity.created(location)
.body(ResponseDTO.builder()
.code(1)
.message("Todo created successfully")
.data(createdTodo)
.build());
}
/**
* 4. PUT /api/users/{userId}/todos/{todoId}
* Todo 수정
*/
@Operation(summary = "Todo 수정", description = "특정 사용자의 특정 Todo를 수정합니다.")
@PutMapping("/{todoId}")
public ResponseEntity<?> updateTodo(
@PathVariable Long userId,
@PathVariable Long todoId,
@Valid @RequestBody TodoUpdateDTO todoUpdateDTO,
BindingResult bindingResult) throws Exception {
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(null, bindingResult);
}
TodoResponseDTO updatedTodo = todoService.updateTodo(userId, todoId, todoUpdateDTO);
addTodoLinks(updatedTodo);
return ResponseEntity.ok(ResponseDTO.builder()
.code(1)
.message("Todo updated successfully")
.data(updatedTodo)
.build());
}
/**
* 5. DELETE /api/users/{userId}/todos/{todoId}
* Todo 삭제
*/
@Operation(summary = "Todo 삭제", description = "특정 사용자의 특정 Todo를 삭제합니다.")
@DeleteMapping("/{todoId}")
public ResponseEntity<?> deleteTodo(
@PathVariable Long userId,
@PathVariable Long todoId) {
todoService.deleteTodo(userId, todoId);
CollectionModel<?> collectionModel = CollectionModel.empty();
collectionModel.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(ApiTodoController.class).getAllTodos(userId, new PageMaker())).withRel("all-todos"));
ResponseDTO<?> response = ResponseDTO.builder()
.code(1)
.message("Todo deleted successfully")
.data(collectionModel)
.build();
return ResponseEntity.ok(response);
}
}
기본 URI: @RequestMapping("/api/users/{userId}/todos") 어노테이션은 모든 Todo 관련 엔드포인트의 기본 URI를 설정하여,
각 Todo가 항상 특정 사용자의 컨텍스트에서 접근되도록 보장합니다.
HATEOAS 링크: 각 TodoResponseDTO에는 HATEOAS 링크가 포함되어 있어 API의 발견 가능성과 탐색성을 높입니다. addTodoLinks 메서드는 각 Todo 리소스에 self 링크를 추가합니다.
페이징: getAllTodos 메서드는 PageMaker 유틸리티를 통해 페이징을 지원하여 클라이언트가 Todo 목록을 적절한 크기로 나눠서 받을 수 있게 합니다.
유효성 검사: 입력 유효성 검사는 @Valid 어노테이션과 BindingResult로 처리됩니다. 유효성 검사가 실패하면 MethodArgumentNotValidException이 발생하며, 전역적으로 처리됩니다.
응답 구조: 모든 응답은 일관성을 위해 ResponseDTO로 감싸져 있으며, 여기에는 코드, 메시지, 데이터 페이로드가 포함됩니다.
2. TodoCreateDTO
package net.macaronics.springboot.webapp.dto.todo;
import java.time.LocalDate;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class TodoCreateDTO {
@NotBlank(message = "내용은 필수 입니다.")
@Size(min = 5, max = 35, message = "글자는 10자에서 35자 사이여야 합니다.")
private String description;
@NotNull(message = "목표 날짜는 필수입니다.")
private LocalDate targetDate;
}
3. TodoUpdateDTO
package net.macaronics.springboot.webapp.dto.todo;
import java.time.LocalDate;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class TodoUpdateDTO {
@NotBlank(message = "내용은 필수 입니다.")
@Size(min = 5, max = 35, message = "글자는 10자에서 35자 사이여야 합니다.")
private String description;
@NotNull(message = "목표 날짜는 필수입니다.")
private LocalDate targetDate;
private boolean done;
}
4. TodoResponseDTO
package net.macaronics.springboot.webapp.dto.todo;
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.hateoas.RepresentationModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TodoResponseDTO extends RepresentationModel<TodoResponseDTO> {
private Long id;
private Long userId;
private String username;
private String description;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate targetDate;
private boolean done;
private Long num; // 목록에서 번호를 매길 필드
// 매개변수 있는 생성자 추가 (Projections.constructor에서 요구하는 타입)
public TodoResponseDTO(Long id, Long userId, String username, String description, LocalDate targetDate, Boolean done) {
this.id = id;
this.userId = userId;
this.username = username;
this.description = description;
this.targetDate = targetDate;
this.done = done;
}
}
5. TodoService
package net.macaronics.springboot.webapp.service;
import java.time.LocalDate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import net.macaronics.springboot.webapp.dto.todo.TodoCreateDTO;
import net.macaronics.springboot.webapp.dto.todo.TodoResponseDTO;
import net.macaronics.springboot.webapp.dto.todo.TodoUpdateDTO;
import net.macaronics.springboot.webapp.entity.Todo;
import net.macaronics.springboot.webapp.entity.User;
import net.macaronics.springboot.webapp.exception.ResourceNotFoundException;
import net.macaronics.springboot.webapp.mapper.TodoMapper;
import net.macaronics.springboot.webapp.repository.TodoRepository;
import net.macaronics.springboot.webapp.repository.UserRepository;
import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
@Service
@Transactional // 이 서비스의 모든 메서드는 기본적으로 트랜잭션 내에서 실행됨
@RequiredArgsConstructor // 필수 필드의 생성자를 자동으로 생성해주는 Lombok 어노테이션
public class TodoService {
private final UserRepository userRepository; // User 정보를 관리하는 Repository
private final TodoRepository todoRepository; // Todo 정보를 관리하는 Repository
private final TodoMapper todoMapper;
/**
* todo 목록 가져오기
* @param userId
* @param pageable
* @return
*/
@Transactional(readOnly = true)
public Page<TodoResponseDTO> getTodosByUserId(Long userId, PageRequest pageable) {
return todoRepository.findByUserId(userId, pageable).map(todoMapper::convertTodoResponseDTO);
}
/**
* todo 상세정보 가져오기
* @param userId
* @param todoId
* @return
*/
@Transactional(readOnly = true)
public TodoResponseDTO getTodoById(Long userId, Long todoId) {
Todo todo=todoRepository.findByIdAndUserId(todoId, userId).orElseThrow(
()->new ResourceNotFoundException("Todo not found"));
return todoMapper.convertTodoResponseDTO(todo);
}
/**
* todo 저장하기
* @param userId
* @param createDTO
* @return
*/
public TodoResponseDTO createTodo(Long userId, TodoCreateDTO createDTO) {
User user=userRepository.findById(userId).orElseThrow(()->new ResourceNotFoundException(userId +" 유저를 찾츨 수 없습니다."));
Todo todo=todoMapper.ofTodo(createDTO);
todo.setUser(user);
todo.setDone(false);
Todo savedTodo = todoRepository.save(todo);
return todoMapper.convertTodoResponseDTO(savedTodo);
}
/**
* todo Update
* @param userId
* @param todoId
* @param updateDTO
* @return
*/
public TodoResponseDTO updateTodo(Long userId, Long todoId, TodoUpdateDTO updateDTO) {
Todo todo = todoRepository.findByIdAndUserId(todoId, userId)
.orElseThrow(() -> new ResourceNotFoundException("Todo not found"));
//더티 체킹
todo.setDescription(updateDTO.getDescription());
todo.setTargetDate(updateDTO.getTargetDate());
todo.setDone(updateDTO.isDone());
return todoMapper.convertTodoResponseDTO(todo);
}
/**
* todo 삭제
* @param userId
* @param todoId
*/
public void deleteTodo(Long userId, Long todoId) {
Todo todo = todoRepository.findByIdAndUserId(todoId, userId)
.orElseThrow(() -> new ResourceNotFoundException("Todo not found"));
todoRepository.delete(todo);
}
}
6.TodoMapper
package net.macaronics.springboot.webapp.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import net.macaronics.springboot.webapp.dto.todo.TodoCreateDTO;
import net.macaronics.springboot.webapp.dto.todo.TodoFormDTO;
import net.macaronics.springboot.webapp.dto.todo.TodoResponseDTO;
import net.macaronics.springboot.webapp.entity.Todo;
/**
* MapStruct를 사용하면, 인터페이스 기반으로 매핑 메서드를 정의하고, 컴파일 시점에 매핑 구현체가 자동으로 생성됩니다.
* componentModel = "spring" 설정을 통해 Spring의 빈으로 등록됩니다.
*/
@Mapper(componentModel = "spring")
public interface TodoMapper {
@Mapping(source = "user.username", target = "username")
TodoResponseDTO convertTodoResponseDTO(Todo todo);
// 필요에 따라 다른 매핑 메서드도 추가
// Todo toEntity(TodoRequestDTO dto);
@Mapping(source = "username", target = "user.username")
Todo ofTodo(TodoFormDTO todoFormDTO);
@Mapping(source = "description", target = "description")
Todo ofTodo(TodoCreateDTO todoCreateDTO);
}
7.TodoRepository
package net.macaronics.springboot.webapp.repository;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import net.macaronics.springboot.webapp.entity.Todo;
import net.macaronics.springboot.webapp.entity.User;
public interface TodoRepository extends JpaRepository<Todo, Long>, TodoRepositoryCustom {
Todo findByIdAndUser(Long id, User user);
void deleteByUserId(Long id);
Page<Todo> findByUserId(Long userId, Pageable pageable);
Optional<Todo> findByIdAndUserId(Long id, Long userId);
}
8.GlobalExceptionHandler
package net.macaronics.springboot.webapp.exception;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import lombok.extern.slf4j.Slf4j;
import net.macaronics.springboot.webapp.dto.ResponseDTO;
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 모든 예외 처리
* @param ex
* @param request
* @return
*/
@ExceptionHandler(Exception.class)
public final ResponseEntity<?> handleAllExceptions(Exception ex, WebRequest request){
log.error("Unhandled exception occurred", ex);
ErrorDetails errorDetails = new ErrorDetails(LocalDateTime.now(), ex.getMessage(), request.getDescription(false));
ResponseDTO<?> response = ResponseDTO.builder()
.code(-1)
.message("Internal Server Error")
.data(errorDetails)
.errorCode("INTERNAL_SERVER_ERROR")
.build();
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* 공통 404 예외 처리: NotFoundException
* @param ex
* @param request
* @return
*/
@ExceptionHandler(ResourceNotFoundException.class)
public final ResponseEntity<?> handleNotFoundException(ResourceNotFoundException ex, WebRequest request){
log.warn("Resource not found exception: {}", ex.getMessage());
ErrorDetails errorDetails = new ErrorDetails(LocalDateTime.now(), ex.getMessage(), request.getDescription(false));
ResponseDTO<?> response = ResponseDTO.builder()
.code(-1)
.message("Resource Not Found")
.data(errorDetails)
.errorCode("NOT_FOUND")
.build();
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
/**
* bindingResult.hasErrors() 에러시 반환 처리한다
* 유효성 체크 에러 처리
* @param ex
* @param request
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationExceptions(MethodArgumentNotValidException ex, WebRequest request){
List<String> errors = ex.getBindingResult()
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
ErrorDetails errorDetails = new ErrorDetails(LocalDateTime.now(), "Validation Failed", request.getDescription(false));
log.warn("Validation failed: {}", errorDetails.getMessage());
ResponseDTO<?> response = ResponseDTO.builder()
.code(-1)
.message(errors.get(0))
.data(errorDetails)
.errorCode("VALIDATION_ERROR")
.build();
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
// 기타 특정 예외 핸들러 추가 가능
}
9. ResourceNotFoundException
package net.macaronics.springboot.webapp.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "찾을 수 없습니다.")
public class ResourceNotFoundException extends RuntimeException{
private static final long serialVersionUID = -2997057852151179119L;
public ResourceNotFoundException(String message) {
super(message);
}
}
10.ErrorDetails
package net.macaronics.springboot.webapp.exception;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ErrorDetails {
private LocalDateTime timestamp;
private String message;
private String details;
}
11. 더미 데이터 JSON 샘플
1.1. Todo 생성 (POST 요청)
새로운 Todo를 생성할 때 사용할 JSON 예시입니다.
{
"description": "Spring Boot 프로젝트 설정하기",
"targetDate": "2024-05-01"
}
1.2. Todo 수정 (PUT 요청)
기존 Todo를 수정할 때 사용할 JSON 예시입니다.
{
"description": "Spring Boot 프로젝트 설정 및 초기화",
"targetDate": "2024-05-05",
"done": true
}
1.3. Todo 응답 예시 (GET 요청)
특정 Todo를 조회할 때 서버에서 반환하는 JSON 예시입니다.
{
"id": 1,
"userId": 1,
"description": "Spring Boot 프로젝트 설정하기",
"targetDate": "2024-05-01",
"done": false,
"_links": {
"self": {
"href": "http://localhost:8080/api/users/1/todos/1"
}
}
}
1.4. Todo 목록 응답 예시 (GET 요청)
Todo 목록을 조회할 때 서버에서 반환하는 JSON 예시입니다.
{
"code": 1,
"message": "success",
"data": {
"_embedded": {
"todoResponseDTOList": [
{
"id": 1,
"userId": 1,
"description": "Spring Boot 프로젝트 설정하기",
"targetDate": "2024-05-01",
"done": false,
"_links": {
"self": {
"href": "http://localhost:8080/api/users/1/todos/1"
}
}
},
{
"id": 2,
"userId": 1,
"description": "데이터베이스 설계",
"targetDate": "2024-05-10",
"done": false,
"_links": {
"self": {
"href": "http://localhost:8080/api/users/1/todos/2"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/users/1/todos?page=0&size=10"
}
}
}
}
1.5. Todo 삭제 응답 예시 (DELETE 요청)
Todo를 삭제한 후 서버에서 반환하는 JSON 예시입니다.
{
"code": 1,
"message": "Todo deleted successfully",
"data": {
"_links": {
"all-todos": {
"href": "http://localhost:8080/api/users/1/todos?page=0&size=10"
}
}
}
}
1. Todo 생성 (POST)
HTTP 메소드: POST
URI: http://localhost:8080/api/users/{userId}/todos
예를 들어, userId가 1인 경우:
http://localhost:8080/api/users/1/todos
헤더 설정:
- Content-Type: application/json
바디 설정:
- Body 탭을 클릭하고, JSON 형식을 선택한 후 아래와 같이 더미 데이터를 입력합니다
{
"description": "Spring Boot 프로젝트 설정하기",
"targetDate": "2024-05-01"
}
요청 전송: 설정이 완료되면 "Send" 버튼을 클릭하여 요청을 전송합니다.
2.3.2. Todo 목록 조회 (GET)
HTTP 메소드: GET
URI: http://localhost:8080/api/users/{userId}/todos
예를 들어, userId가 1인 경우:
http://localhost:8080/api/users/1/todos?page=0&size=10
헤더 설정: 특별한 헤더 설정이 필요하지 않습니다.
요청 전송: "Send" 버튼을 클릭하여 요청을 전송합니다.
2.3.3. 특정 Todo 조회 (GET)
HTTP 메소드: GET
URI: http://localhost:8080/api/users/{userId}/todos/{todoId}
예를 들어, userId가 1이고 todoId가 1인 경우:
http://localhost:8080/api/users/1/todos/1
2.3.4. Todo 수정 (PUT)
HTTP 메소드: PUT
URI: http://localhost:8080/api/users/{userId}/todos/{todoId}
예를 들어, userId가 1이고 todoId가 1인 경우:
http://localhost:8080/api/users/1/todos/1
헤더 설정:
- Content-Type: application/json
바디 설정:
- Body 탭을 클릭하고, JSON 형식을 선택한 후 아래와 같이 더미 데이터를 입력합니다
{
"description": "Spring Boot 프로젝트 설정 및 초기화",
"targetDate": "2024-05-05",
"done": true
}
요청 전송: "Send" 버튼을 클릭하여 요청을 전송합니다.
2.3.5. Todo 삭제 (DELETE)
HTTP 메소드: DELETE
URI: http://localhost:8080/api/users/{userId}/todos/{todoId}
예를 들어, userId가 1이고 todoId가 1인 경우:
http://localhost:8080/api/users/1/todos/1


















댓글 ( 0)
댓글 남기기