Spring Boot 입문 튜토리얼 | 30분 만에 REST API 만들기 [초보자용]

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 (웹)

  1. https://start.spring.io/ 접속
  2. 다음과 같이 설정:
    • Project: Maven
    • Language: Java
    • Spring Boot: 3.2.x (최신 안정 버전)
    • Packaging: Jar
    • Java: 17
  3. Dependencies 추가:
    • Spring Web
    • Spring Data JPA
    • H2 Database
  4. GENERATE 클릭 → ZIP 다운로드
  5. 압축 해제 후 IDE로 열기

방법 2: IntelliJ IDEA

  1. New Project → Spring Initializr
  2. 위와 동일하게 설정
  3. 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

브라우저에서 테스트

  1. H2 콘솔 접속: http://localhost:8080/h2-console

    • JDBC URL: jdbc:h2:mem:testdb
    • Username: sa
    • Password: (빈칸)
  2. 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

빠른 레퍼런스

주요 애노테이션

애노테이션용도예시
@RestControllerREST API 컨트롤러클래스에 적용
@GetMappingGET 요청 처리메서드에 적용
@PostMappingPOST 요청 처리메서드에 적용
@RequestBodyJSON → 객체 변환파라미터에 적용
@PathVariableURL 경로 변수/{id}
@Service비즈니스 로직클래스에 적용
@RepositoryDB 접근인터페이스에 적용
@EntityJPA 엔티티클래스에 적용

자주 쓰는 프로퍼티

# 서버 설정
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

마무리

핵심 정리

  1. Spring Initializr로 30초 안에 프로젝트 생성
  2. Controller → Service → Repository 3계층 구조
  3. JPA로 SQL 없이 DB 조작
  4. @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와 함께 보면 좋은 백엔드 개발 가이드입니다:


한 줄 요약: 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 만들기 [초보자용]」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 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 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.