co-cherry
Spring Boot 시작하기 & REST API 기초 본문
1. Spring Boot란?
Spring 기반 웹 애플리케이션을 쉽고 빠르게 만들 수 있도록 도와주는 Java의 프레임워크
- 복잡한 설정을 자동으로 처리하는 Auto Configuration 기능 제공
- 내장 서버(Tomcat) 제공 → 별도의 서버 설치 필요 없음
- REST API 개발에 많이 사용됨
1-01 스프링 부트란?
**스프링 부트(Spring Boot)** 는 웹 프로그램(웹 애플리케이션)을 쉽고 빠르게 만들 수 있도록 도와주는 자바의 웹 프레임워크이다. 스프링 부트는 스프링(Spring)…
wikidocs.net
자세한 내용은 위 링크 참조 (개념 이해에 좋다)
2. Spring Boot 프로젝트 생성하기
IntelliJ IDEA를 사용해 Spring Boot 프로젝트를 생성했다.

개발 환경
- IDE: IntelliJ IDEA
- JDK: OpenJDK 21 (Java 17 target)
- Build Tool: Gradle
- Server: Embedded Tomcat
프로젝트 생성 환경
- Project: Gradle - Groovy
- Language: Java
- Spring Boot: 3.5.11
(더 최신 버전이 있지만 안정성이 검증된 버전을 이용하는 것이 호환성 문제 등에 좋다) - Java Version: 17
Database
- DB: MySQL
- ORM: Spring Data JPA
사용 Dependency

- Spring Web
REST API를 구현하기 위해 사용하는 라이브러리
HTTP 요청을 처리하고 JSON 형식의 응답을 반환하도록 지원함 - Spring Data JPA
데이터베이스와의 연동을 위해 사용하는 ORM 기술
JPA를 기반으로 데이터 접근 계층을 쉽게 구현할 수 있도록 도움 - MySQL Driver
Spring Boot 애플리케이션이 MySQL 데이터베이스와 연결할 수 있도록 해주는 JDBC 드라이버 - Lombok
Getter, Setter, 생성자 등의 반복되는 코드를 자동으로 생성해 코드의 가독성과 생산성을 높여주는 라이브러리
기본적으로 위와 같은 의존성을 추가하고 추후 기능을 계속해서 확장해갈 예정이다!
3. 프로젝트 패키지 구조
프로젝트 패키지 구조는 기본적으로 두 가지로 나눌 수 있다.
- 계층형 구조
- 도메인형 구조
계층형 구조 (Layered Architecture)
기능의 역할(계층)을 기준으로 패키지를 나누는 방식
com.example.project
┣ controller
┣ service
┣ repository
┣ entity
┗ dto
특징
- 구조가 단순하고 이해하기 쉽다
- 작은 프로젝트에서 많이 사용한다
- 같은 역할의 코드가 한 패키지에 모인다
단점
- 기능이 많아질수록 코드가 여러 패키지에 분산된다
- 특정 기능을 찾기 위해 여러 패키지를 이동해야 한다
도메인형 구조 (Domain-based Architecture)
기능(도메인) 기준으로 패키지를 나누는 방식
com.example.project
┣ user
┃ ┣ controller
┃ ┣ service
┃ ┣ repository
┃ ┣ entity
┃ ┗ dto
┣ order
┃ ┣ controller
┃ ┣ service
┃ ┣ repository
┃ ┗ entity
┗ global
특징
- 하나의 기능 관련 코드가 한 패키지에 모인다
- 기능 단위로 관리하기 쉬워진다
- 대규모 프로젝트에서 유지보수가 용이하다
단점
- 처음 구조를 이해하기 어려울 수 있다
- 프로젝트 초기에 구조 설계가 필요하다
더 자세한 비교 분석은 아래의 링크를 참고해보자.
https://ksh-coding.tistory.com/96
[아키텍쳐] 패키지 구조 : 계층형 VS 도메인형 어떤 것을 선택할까?
🎯 0. 들어가기 전 MVC 패턴 & 자바 기반의 콘솔 애플리케이션에서는 관성적으로 model(domain) & controller & view 패키지를 만들고 시작하는 경우가 대부분이었다. 웹 애플리케이션을 구현하면서, 설계
ksh-coding.tistory.com
[Spring Boot] 패키지 구조: 계층형 vs 도메인형
규모가 있는 프로젝트를 진행하다 보면 직면하게 되는 문제가 있다. 바로 내가 맡은 기능의 코드를 찾기가 까다롭다는 것이다. 수많은 Service 중 UserService를 찾고, Domain 패키지에서 User를 찾는 것
velog.io
나의 경우, 유지보수성과 확장성을 고려하여 도메인형 구조를 주로 사용한다.

따라서, 이와 같은 형태로 구조를 만들었다.
여기서, 각 패키지들은 무엇이며 왜 필요하게 되었을까?
4. REST API란?
REST API는 웹에서 클라이언트와 서버가 데이터를 주고받기 위해 사용하는 API 설계 방식이다.
즉, 클라이언트가 서버에 요청을 보내면 서버는 해당 요청을 처리한 뒤 JSON 같은 형식으로 데이터를 응답하는 방식으로 동작한다.
REST API에서는 주로 다음과 같은 Http Method를 사용한다.
| Method | 역할 |
| GET | 데이터 조회 |
| POST | 데이터 생성 |
| PUT | 데이터 수정 |
| DELETE | 데이터 삭제 |
예를 들어, 사용자 정보를 조회하는 API는 GET /users 와 같이 표현할 수 있다.
5. REST API의 동작 흐름
Spring Boot에서 일반적으로 REST API는 다음과 같은 흐름으로 동작한다.
Client
↓
Controller
↓
Service
↓
Repository
↓
Database
1. 클라이언트가 API 요청을 보낸다.
2. Controller가 요청을 받아 처리할 Service를 호출한다.
3. Service에서 비즈니스 로직을 수행한다.
4. Repository를 통해 데이터베이스에 접근한다.
5. 처리 결과를 다시 Controller로 전달한다.
6. Controller가 클라이언트에게 응답을 반환한다.
이러한 흐름을 기반으로 Spring Boot 프로젝트에서는 각 역할에 맞게 패키지를 분리하여 관리한다.
6. 패키지 설명

1. user
사용자와 관련된 기능을 모아둔 도메인 패키지
회원 조회, 회원 생성, 회원 정보 관리 등 사용자와 관련된 API 로직이 전부 이에 해당된다.
2. controller
클라이언트의 HTTP 요청을 가장 먼저 받는 계층 (즉, REST API의 시작 지점)
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public ResponseEntity<UserResponse> createUser(@RequestBody UserRequest request) {
// 요청 받고
User user = userService.createUser(request);
// 응답으로 돌려보내기
return ResponseEntity.ok(new UserResponse(user));
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(new UserResponse(user));
}
}
특징 (요청 받기 → 서비스 호출 → 응답 보내기)
- HTTP 요청을 받는다 (URL, Method, RequestBody, PathVariable 등)
- 요청을 Service에 넘긴다
- Service가 준 결과를 응답으로 돌려준다
- 비즈니스 로직을 직접 처리하지 않는다
- 데이터베이스에 직접 접근하지 않는다
3. dto
DTO는 Data Transfer Object의 약자로, 계층 간 데이터를 주고 받기 위한 매개체
public class UserRequest {
private String name;
private String email;
private String password;
}
public class UserResponse {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
public UserResponse(User user) {
this.id = user.getId();
this.name = user.getName();
this.email = user.getEmail();
this.createdAt = user.getCreatedAt();
}
}
특징
- Entity를 외부에 노출하지 않음 (Entity는 DB 구조를 그대로 반영)
- API 명세와 코드를 맞출 수 있음
- 클라이언트와 서버가 독립적임
4. service
비즈니스 로직을 처리하는 REST API의 핵심 로직
Controller가 단순히 요청을 받는 역할이라면, Service는 실제로 무엇을 할지 결정하고 수행하는 부분
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final EmailService emailService;
private final CacheService cacheService;
public User createUser(UserRequest request) {
// 1️⃣ 유효성 검사
validateUserRequest(request);
// 2️⃣ 중복 확인
if (userRepository.existsByEmail(request.getEmail())) {
throw new DuplicateException("이미 존재하는 이메일입니다");
}
// 3️⃣ 비밀번호 암호화
String encryptedPassword = passwordEncoder.encode(request.getPassword());
// 4️⃣ 사용자 생성 및 저장
User user = User.builder()
.name(request.getName())
.email(request.getEmail())
.password(encryptedPassword)
.build();
userRepository.save(user);
// 5️⃣ 캐시에 추가
cacheService.addUser(user);
// 6️⃣ 환영 이메일 발송
emailService.sendWelcomeEmail(user);
return user;
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다"));
}
private void validateUserRequest(UserRequest request) {
if (request.getName() == null || request.getName().isEmpty()) {
throw new InvalidInputException("이름은 필수입니다");
}
if (request.getEmail() == null || !isValidEmail(request.getEmail())) {
throw new InvalidInputException("유효한 이메일을 입력하세요");
}
if (request.getPassword() == null || request.getPassword().length() < 8) {
throw new InvalidInputException("비밀번호는 8자 이상이어야 합니다");
}
}
private boolean isValidEmail(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
}
특징
- 비즈니스 로직 수행 (검증, 저장, 캐시, 이메일 발송 등)
- 테스트에 용이
- 재사용 가능
- 유지보수 쉬움
5. repository
데이터베이스에 접근하는 계층으로 데이터를 어디에 저장할 건지, 어디에서 꺼낼 건지 정의하는 곳
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 기본 CRUD는 자동으로 제공됨 (save, findById, findAll, delete 등)
// 커스텀 쿼리
User findByEmail(String email);
boolean existsByEmail(String email);
List<User> findAllByNameContaining(String name);
List<User> findByCreatedAtAfter(LocalDateTime date);
}
특징
- DB 접근 로직을 분리하기 위해 필요
- 데이터 조회, 저장, 수정, 삭제 책임을 한 곳에 모을 수 있음
- Spring Data JPA를 사용하면 기본적인 CRUD 기능을 쉽게 구현 가능
userRepository.save(user); // INSERT
userRepository.findById(1L); // SELECT
userRepository.findAll(); // SELECT *
userRepository.delete(user); // DELETE
userRepository.deleteById(1L); // DELETE WHERE id = 1
6. entity
JPA를 통해 데이터베이스 테이블과 매핑되는 객체
JPA를 사용할 때 @Entity를 붙여 DB 테이블과 자바 객체를 연결한다.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(nullable = false)
private LocalDateTime updatedAt;
@Builder
public User(String name, String email, String password) {
this.name = name;
this.email = email;
this.password = password;
}
}
특징
- 데이터베이스 테이블을 객체로 표현
- Repository의 작동 기준
- 관계형 데이터를 객체 지향으로 다룸
각 계층이 담당하는 책임
| 계층 | 담당 | 예시 |
| Controller | 요청/응답 연결 | URL 매핑, PathVariable 처리, HTTP 상태 코드 |
| Service | 비즈니스 로직 | 검증, 데이터 처리, 여러 Repository 조율 |
| Repository | 데이터 접근 | DB 쿼리, CRUD 작업 |
| DTO | 데이터 형식 | 클라이언트와 통신 형식 |
| Entity | DB 매핑 | 테이블 구조, JPA 관계 |
추가적으로 아래 링크들을 참고하면 이해에 더 도움이 될 것이다.
https://nosupport.tistory.com/entry/Controller%EC%99%80-Seriv
컨트롤러(Controller)와 서비스(Service)의 이해
1. Controller개념애플리케이션의 입구 역할을 함.사용자의 요청(Request)을 받아서, 이를 처리할 적절한 로직(Service)으로 전달.HTTP 요청과 응답을 관리.용도URL 라우팅과 요청 처리.요청 파라미터 검증
nosupport.tistory.com
https://velog.io/@rrrr/Controller-Service
Controller, Service
Spring MVC(Model-View-Controller) 아키텍처에서 Controller는 사용자의 요청을 처리하는 역할클라이언트의 요청을 받아들이고 적절한 Service 호출요청 데이터를 검증하고 처리처리된 데이터를 응답으로 반
velog.io
https://hianna.tistory.com/1170
[Spring Boot 입문 - 6] Controller, Service, Repository: 3계층 구조 완벽 이해하기
지난 시간, 우리는 HelloController라는 웨이터 한 명을 고용해서 손님(브라우저)의 인사를 받아주게 했습니다.그런데 만약 손님이 "로그인 해주세요" 라거나 "내 통장 잔고 조회해 주세요" 같은 복잡
hianna.tistory.com
7. 간단한 API 만들어보기
서버 동작을 확인하기 위해 Ping API를 구현해보았다.
@RestController
public class PingController {
@GetMapping("/api/ping")
public ApiResponse<String> ping() throws Exception {
return ApiResponse.onSuccess(GeneralSuccessCode.OK, "pong");
}
}
코드 설명
+)@RestController는 @Controller + @ResponseBody 라는 의미로, 반환 값이 자동으로 JSON이 된다.
위 API는 간단히 말해, GET 요청을 /api/ping 의 경로에서 받겠다는 의미이다.
이 엔드포인트에 클라이언트가 GET 요청을 보내면 이 메서드가 실행될 것이다.
또한, 이 API는 Controller를 통해 요청을 보내고, 따로 Service를 통한 비즈니스 로직 없이, Controller를 통해 응답을 반환한다.
나는 응답 구조를 예쁘게 하기 위해서 ApiResponse 를 사용했으나, 현재 시점에서는 기능만 보면 된다!
API를 실행해보자. (postman을 이용)

다음과 같이 요청이 성공적으로 처리되었다는 메세지와 함께 pong이 전달되었다!
'Springboot' 카테고리의 다른 글
| 게시글 목록 API 구현하기 (페이징 · 검색, N+1) (0) | 2026.05.07 |
|---|---|
| 회원가입 및 로그인 구현 (0) | 2026.04.28 |
| 게시판 CRUD (0) | 2026.03.30 |
| ERD + DB + JPA 시작 (0) | 2026.03.24 |
| 예외 처리와 Validation (1) | 2026.03.13 |