Spring Boot 입문 튜토리얼 | 30분 만에 REST API 만들기 [초보자용]
이 글의 핵심
Java Spring Boot 완전 초보자를 위한 입문 튜토리얼. 설치부터 첫 REST API 배포까지 30분이면 충분합니다. 코드 복사만으로 따라할 수 있는 단계별 가이드.
들어가며: 30분 만에 첫 API 만들기
“Spring Boot가 뭔지는 알겠는데, 어디서부터 시작해야 할지 모르겠어요.”
이 튜토리얼은 완전 초보자를 위한 가이드입니다. 설치 → 프로젝트 생성 → REST API → 실행 → 배포까지 30분이면 충분합니다. 복잡한 이론 없이 코드를 복사해서 실행하며 배웁니다.
이 튜토리얼에서 만들 것
간단한 TODO 관리 REST API:
GET /api/todos- 할 일 목록 조회POST /api/todos- 할 일 추가PUT /api/todos/{id}- 할 일 수정DELETE /api/todos/{id}- 할 일 삭제
사전 요구사항
- Java 17 이상 설치 (OpenJDK 추천)
- IDE: IntelliJ IDEA Community (무료) 또는 VS Code
1단계: Java 설치 확인 (3분)
# 터미널에서 확인
java -version
# 출력 예시:
# openjdk version "17.0.2" 2022-01-18
# OpenJDK Runtime Environment (build 17.0.2+8-86)
Java가 없다면:
- Windows: https://adoptium.net/ → JDK 17 다운로드
- Mac:
brew install openjdk@17 - Linux:
sudo apt install openjdk-17-jdk
2단계: 프로젝트 생성 (5분)
방법 1: Spring Initializr (웹)
- https://start.spring.io/ 접속
- 다음과 같이 설정:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.x (최신 안정 버전)
- Packaging: Jar
- Java: 17
- Dependencies 추가:
- Spring Web
- Spring Data JPA
- H2 Database
- GENERATE 클릭 → ZIP 다운로드
- 압축 해제 후 IDE로 열기
방법 2: IntelliJ IDEA
- New Project → Spring Initializr
- 위와 동일하게 설정
- Create
3단계: 첫 REST API 만들기 (10분)
프로젝트 구조
src/main/java/com/example/demo/
├── DemoApplication.java # 메인
├── controller/
│ └── TodoController.java # REST API
├── model/
│ └── Todo.java # 데이터 모델
├── repository/
│ └── TodoRepository.java # DB 접근
└── service/
└── TodoService.java # 비즈니스 로직
1) 데이터 모델 (Todo.java)
src/main/java/com/example/demo/model/Todo.java 파일 생성:
package com.example.demo.model;
import jakarta.persistence.*;
@Entity
@Table(name = "todos")
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private boolean completed;
// 기본 생성자 (JPA 필수)
public Todo() {}
public Todo(String title) {
this.title = title;
this.completed = false;
}
// Getter & Setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public boolean isCompleted() { return completed; }
public void setCompleted(boolean completed) { this.completed = completed; }
}
2) Repository (TodoRepository.java)
src/main/java/com/example/demo/repository/TodoRepository.java:
package com.example.demo.repository;
import com.example.demo.model.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TodoRepository extends JpaRepository<Todo, Long> {
// 기본 CRUD는 JpaRepository가 자동 제공
// findAll(), save(), deleteById() 등
}
놀라운 점: 인터페이스만 만들었는데 Spring Data JPA가 자동으로 구현체를 생성합니다!
3) Service (TodoService.java)
src/main/java/com/example/demo/service/TodoService.java:
package com.example.demo.service;
import com.example.demo.model.Todo;
import com.example.demo.repository.TodoRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class TodoService {
private final TodoRepository repository;
// 생성자 주입 (권장 방식)
public TodoService(TodoRepository repository) {
this.repository = repository;
}
public List<Todo> getAllTodos() {
return repository.findAll();
}
public Optional<Todo> getTodoById(Long id) {
return repository.findById(id);
}
public Todo createTodo(Todo todo) {
return repository.save(todo);
}
public Todo updateTodo(Long id, Todo todoDetails) {
Todo todo = repository.findById(id)
.orElseThrow(() -> new RuntimeException("Todo not found"));
todo.setTitle(todoDetails.getTitle());
todo.setCompleted(todoDetails.isCompleted());
return repository.save(todo);
}
public void deleteTodo(Long id) {
repository.deleteById(id);
}
}
4) Controller (TodoController.java)
src/main/java/com/example/demo/controller/TodoController.java:
package com.example.demo.controller;
import com.example.demo.model.Todo;
import com.example.demo.service.TodoService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final TodoService service;
public TodoController(TodoService service) {
this.service = service;
}
// GET /api/todos - 전체 조회
@GetMapping
public List<Todo> getAllTodos() {
return service.getAllTodos();
}
// GET /api/todos/{id} - 단건 조회
@GetMapping("/{id}")
public ResponseEntity<Todo> getTodoById(@PathVariable Long id) {
return service.getTodoById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// POST /api/todos - 생성
@PostMapping
public Todo createTodo(@RequestBody Todo todo) {
return service.createTodo(todo);
}
// PUT /api/todos/{id} - 수정
@PutMapping("/{id}")
public ResponseEntity<Todo> updateTodo(
@PathVariable Long id,
@RequestBody Todo todoDetails
) {
try {
Todo updated = service.updateTodo(id, todoDetails);
return ResponseEntity.ok(updated);
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
}
}
// DELETE /api/todos/{id} - 삭제
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTodo(@PathVariable Long id) {
service.deleteTodo(id);
return ResponseEntity.ok().build();
}
}
5) application.properties 설정
src/main/resources/application.properties:
# 서버 포트
server.port=8080
# H2 데이터베이스 (개발용)
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# JPA 설정
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
# H2 콘솔 활성화
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
4단계: 실행 및 테스트 (7분)
서버 실행
# Maven
./mvnw spring-boot:run
# 또는 IntelliJ에서 DemoApplication.java 실행
출력:
Tomcat started on port 8080
Started DemoApplication in 2.345 seconds
API 테스트 (curl)
# 1. TODO 추가
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title": "Spring Boot 배우기", "completed": false}'
# 응답:
# {"id":1,"title":"Spring Boot 배우기","completed":false}
# 2. 전체 조회
curl http://localhost:8080/api/todos
# 응답:
# [{"id":1,"title":"Spring Boot 배우기","completed":false}]
# 3. 수정
curl -X PUT http://localhost:8080/api/todos/1 \
-H "Content-Type: application/json" \
-d '{"title": "Spring Boot 배우기", "completed": true}'
# 4. 삭제
curl -X DELETE http://localhost:8080/api/todos/1
브라우저에서 테스트
-
H2 콘솔 접속: http://localhost:8080/h2-console
- JDBC URL:
jdbc:h2:mem:testdb - Username:
sa - Password: (빈칸)
- JDBC URL:
-
SQL 쿼리:
SELECT * FROM todos;
5단계: 핵심 개념 이해 (5분)
Spring Boot의 마법
1) @SpringBootApplication
@SpringBootApplication
// = @Configuration + @EnableAutoConfiguration + @ComponentScan
- 자동 설정: Tomcat, JPA 자동 구성
- 컴포넌트 스캔:
@Controller,@Service자동 인식
2) 의존성 주입 (DI)
public TodoController(TodoService service) {
this.service = service;
}
- Spring이
TodoService객체를 자동으로 생성해서 주입 new TodoService()직접 호출 불필요
3) JPA (Java Persistence API)
@Entity
public class Todo { ... }
- Java 객체 ↔ 데이터베이스 자동 매핑
- SQL 작성 불필요
4) RESTful 패턴
@GetMapping // 조회 (SELECT)
@PostMapping // 생성 (INSERT)
@PutMapping // 수정 (UPDATE)
@DeleteMapping // 삭제 (DELETE)
실무 팁
자주 하는 실수
1) 생성자 주입 안 함
// ❌ 나쁜 예: 필드 주입
@Autowired
private TodoService service;
// ✅ 좋은 예: 생성자 주입
private final TodoService service;
public TodoController(TodoService service) {
this.service = service;
}
2) @RequestBody 빠뜨림
// ❌ 에러 발생
@PostMapping
public Todo create(Todo todo) { ... }
// ✅ JSON을 객체로 변환
@PostMapping
public Todo create(@RequestBody Todo todo) { ... }
3) CORS 에러
// 프론트엔드 연동 시 필요
@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class TodoController { ... }
디버깅 팁
# 로그 레벨 설정 (application.properties)
logging.level.org.springframework=DEBUG
logging.level.com.example=DEBUG
# SQL 로그 상세히 보기
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
다음 단계: 실무로 가는 길
이 튜토리얼을 마쳤다면 다음을 학습하세요:
필수 학습 (순서대로)
1) Exception 처리
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleNotFound(RuntimeException ex) {
return ResponseEntity.status(404).body(ex.getMessage());
}
}
2) Validation
public class Todo {
@NotBlank(message = "제목은 필수입니다")
private String title;
}
@PostMapping
public Todo create(@Valid @RequestBody Todo todo) { ... }
3) Spring Security (인증)
// 의존성 추가 후
@EnableWebSecurity
public class SecurityConfig { ... }
4) 테스트
@SpringBootTest
class TodoControllerTest {
@Test
void testGetAllTodos() { ... }
}
5) 프로덕션 DB (PostgreSQL)
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=postgres
spring.datasource.password=password
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
추천 학습 경로
이 튜토리얼 (30분)
↓
[Spring Boot 완벽 가이드] (3시간) ← 실무 수준
↓
실전 프로젝트 (1-2주)
↓
포트폴리오 제작 (1개월)
흔한 에러 및 해결
에러 1: Port 8080 already in use
# 포트 변경 (application.properties)
server.port=8081
에러 2: Could not find or load main class
# Maven 클린 빌드
./mvnw clean install
에러 3: Failed to configure a DataSource
# H2 의존성 확인 (pom.xml)
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
실전 프로젝트 아이디어
이제 배운 내용으로 실전 프로젝트를 만들어보세요:
초급:
- 간단한 게시판 API
- 날씨 정보 저장 서비스
- 간단한 블로그 백엔드
중급:
- 사용자 인증이 있는 TODO 앱
- 파일 업로드 서비스
- 실시간 채팅 서버 (WebSocket)
고급:
- 마이크로서비스 아키텍처
- OAuth2 로그인
- Redis 캐싱 + PostgreSQL
빠른 레퍼런스
주요 애노테이션
| 애노테이션 | 용도 | 예시 |
|---|---|---|
@RestController | REST API 컨트롤러 | 클래스에 적용 |
@GetMapping | GET 요청 처리 | 메서드에 적용 |
@PostMapping | POST 요청 처리 | 메서드에 적용 |
@RequestBody | JSON → 객체 변환 | 파라미터에 적용 |
@PathVariable | URL 경로 변수 | /{id} |
@Service | 비즈니스 로직 | 클래스에 적용 |
@Repository | DB 접근 | 인터페이스에 적용 |
@Entity | JPA 엔티티 | 클래스에 적용 |
자주 쓰는 프로퍼티
# 서버 설정
server.port=8080
server.servlet.context-path=/api
# 데이터베이스
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
# JPA
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create-drop
# 로깅
logging.level.root=INFO
logging.level.com.example=DEBUG
마무리
핵심 정리
- Spring Initializr로 30초 안에 프로젝트 생성
- Controller → Service → Repository 3계층 구조
- JPA로 SQL 없이 DB 조작
- @RestController로 JSON API 자동 생성
30분 동안 배운 것
- Spring Boot 프로젝트 생성
- REST API 4가지 (GET, POST, PUT, DELETE)
- JPA로 데이터베이스 연동
- H2 인메모리 DB 사용
- 로컬에서 실행 및 테스트
다음에 배울 것
- Exception 처리 및 Validation
- Spring Security 인증/인가
- PostgreSQL/MySQL 연동
- 테스트 작성 (JUnit, MockMvc)
- Docker로 배포
축하합니다! 첫 Spring Boot API를 만들었습니다. 이제 Spring Boot 완벽 가이드로 넘어가 실무 수준을 익히세요.
관련 글 (내부 링크)
Spring Boot와 함께 보면 좋은 백엔드 개발 가이드입니다:
- Spring Boot 완벽 가이드 | REST API·JPA·Security·Actuator - 이 튜토리얼 다음 단계
- Java 시리즈 #10 Spring Boot | REST API 서버 만들기 - 더 상세한 설명
- Java 시리즈 #1 Java 입문 | 기초 문법 - Java 기초
- NestJS 완벽 가이드 | TypeScript 백엔드 - TypeScript 대안
- FastAPI 완벽 가이드 | Python 백엔드 - Python 대안
- Supabase 완벽 가이드 | PostgreSQL·인증·스토리지 - 백엔드 대안
한 줄 요약: 30분이면 Spring Boot로 첫 REST API를 만들 수 있습니다. Controller·Service·Repository 구조와 JPA 기본만 알면 실무 프로젝트도 시작할 수 있습니다.
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「Spring Boot 입문 튜토리얼 | 30분 만에 REST API 만들기 [초보자용]」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「Spring Boot 입문 튜토리얼 | 30분 만에 REST API 만들기 [초보자용]」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.