Contents
Swagger를 사용해보자API 문서의 목적
- 프론트엔드에게 내 서버가 어떻게 생겼는지 알려주기 위해서
- = 내 서버 사용법
API 문서에는 어떤 것들이 들어가야 할까?
게시글 작성의 경우를 예로 들어보자. 게시글 작성은 내가 입력한 값이 결국 db에 insert 되어야 한다. 그러나 프론트엔드에서는 db에 직접 접근 불가능하다. 즉 외부에서 봤을 때 Controller는 쓸 수 있는 메서드를 열어준다는 점에서 인터페이스와 유사하다.
- postmapping으로 /board 와 같이 api 요청을 위한 uri를 알아야 한다.
- localhost:8080과 같은 네트워크 주소도 필요하다.
- db에 들어가야할 컬럼값에 대한 정보도 필요하다.
- 마지막으로 데이터를 어떻게, 어떠한 방식으로 전달해야할지와 같은 content-type을 알아야 한다. (e.g. application/json)
Swagger를 사용해보자
Swagger란?
단점 - 코드 가독성을 해친다
Swagger 사용하기
1.

2.

plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'shop.mtcoding'
version = '1.0'
java {
sourceCompatibility = '21'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation group: 'com.auth0', name: 'java-jwt', version: '4.3.0'
implementation group: 'org.qlrm', name: 'qlrm', version: '4.0.1'
implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.4'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
// plain 파일 생성하지 않기
jar {
enabled = false
}3.
package shop.mtcoding.blog.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
public class UserRequest {
@Data
public static class UpdateDTO {
@Schema(description = "비밀번호 (4~20자)", example = "1234")
@Size(min = 4, max = 20)
private String password;
@Schema(description = "이메일 주소", example = "user@example.com")
@Pattern(regexp = "^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\\.[a-zA-Z]{2,3}$", message = "이메일 형식으로 적어주세요")
private String email;
}
@Data
public static class JoinDTO {
@Schema(description = "유저네임 (2~20자, 특수문자/한글 불가)", example = "metacoding")
@Pattern(regexp = "^[a-zA-Z0-9]{2,20}$", message = "유저네임은 2-20자이며, 특수문자,한글이 포함될 수 없습니다")
private String username;
@Schema(description = "비밀번호 (4~20자)", example = "1234")
@Size(min = 4, max = 20)
private String password;
@Schema(description = "이메일 주소", example = "user@example.com")
@Pattern(regexp = "^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\\.[a-zA-Z]{2,3}$", message = "이메일 형식으로 적어주세요")
private String email;
public User toEntity() {
return User.builder()
.username(username)
.password(password)
.email(email)
.build();
}
}
@Data
public static class LoginDTO {
@Schema(description = "유저네임 (2~20자)", example = "metacoding")
@Pattern(regexp = "^[a-zA-Z0-9]{2,20}$", message = "유저네임은 2-20자이며, 특수문자,한글이 포함될 수 없습니다")
private String username;
@Schema(description = "비밀번호 (4~20자)", example = "1234")
@Size(min = 4, max = 20)
private String password;
@Schema(description = "자동 로그인 여부 (체크시 'on')", example = "on", nullable = true)
private String rememberMe; // check되면 on, 안되면 null
}
}4.
package shop.mtcoding.blog.user;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.blog._core.util.Resp;
import java.util.Map;
@Slf4j
@Tag(name = "User API", description = "회원가입, 로그인, 회원정보 수정 등 사용자 관련 API")
@RequiredArgsConstructor
@RestController // json만 리턴!!
public class UserController {
private final UserService userService;
private final HttpSession session;
@Operation(summary = "회원정보 수정", description = "로그인한 사용자의 비밀번호와 이메일을 수정합니다.")
@PutMapping("/s/api/user")
public ResponseEntity<?> update(@Valid @RequestBody UserRequest.UpdateDTO reqDTO, Errors errors) {
User sessionUser = (User) session.getAttribute("sessionUser");
UserResponse.DTO respDTO = userService.회원정보수정(reqDTO, sessionUser.getId());
return Resp.ok(respDTO);
}
@Operation(summary = "유저네임 중복체크", description = "해당 유저네임이 이미 사용 중인지 확인합니다.")
@GetMapping("/api/check-username-available/{username}")
public ResponseEntity<?> checkUsernameAvailable(
@Parameter(description = "확인할 유저네임", example = "metacoding") @PathVariable("username") String username) {
Map<String, Object> respDTO = userService.유저네임중복체크(username);
return Resp.ok(respDTO);
}
@Operation(summary = "회원가입", description = "유저네임, 비밀번호, 이메일을 받아 회원가입을 진행합니다.")
@PostMapping("/join")
public ResponseEntity<?> join(
@Valid @RequestBody UserRequest.JoinDTO reqDTO,
Errors errors,
HttpServletResponse response,
HttpServletRequest request) {
log.debug(reqDTO.toString());
log.trace("트레이스ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
log.debug("디버그---------");
log.info("인포ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
log.warn("워닝ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
log.error("에러ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
String hello = request.getHeader("X-Key");
System.out.println("X-good : " + hello);
response.setHeader("Authorization", "jooho");
UserResponse.DTO respDTO = userService.회원가입(reqDTO);
return Resp.ok(respDTO);
}
@Operation(summary = "로그인", description = "유저네임과 비밀번호를 이용하여 로그인합니다.")
@PostMapping("/login")
public ResponseEntity<?> login(
@Valid @RequestBody UserRequest.LoginDTO loginDTO,
Errors errors,
HttpServletResponse response) {
UserResponse.TokenDTO respDTO = userService.로그인(loginDTO);
return Resp.ok(respDTO);
}
// AccessToken만으로는 Logout 을 할 수 없다.
}5.
package shop.mtcoding.blog.user;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.blog._core.util.Resp;
import java.util.Map;
@Slf4j
@Tag(name = "User API", description = "회원가입, 로그인, 회원정보 수정 등 사용자 관련 API")
@RequiredArgsConstructor
@RestController
public class UserController {
private final UserService userService;
private final HttpSession session;
@Operation(summary = "회원정보 수정", description = "로그인한 사용자의 비밀번호와 이메일을 수정합니다.")
@ApiResponse(responseCode = "200", description = "회원정보 수정 성공",
content = @Content(schema = @Schema(implementation = UserResponse.DTO.class)))
@PutMapping("/s/api/user")
public ResponseEntity<?> update(@Valid @RequestBody UserRequest.UpdateDTO reqDTO, Errors errors) {
User sessionUser = (User) session.getAttribute("sessionUser");
UserResponse.DTO respDTO = userService.회원정보수정(reqDTO, sessionUser.getId());
return Resp.ok(respDTO);
}
@Operation(summary = "유저네임 중복체크", description = "해당 유저네임이 이미 사용 중인지 확인합니다.")
@ApiResponse(responseCode = "200", description = "중복 여부 반환",
content = @Content(schema = @Schema(implementation = Map.class)))
@GetMapping("/api/check-username-available/{username}")
public ResponseEntity<?> checkUsernameAvailable(
@Parameter(description = "확인할 유저네임", example = "metacoding") @PathVariable("username") String username) {
Map<String, Object> respDTO = userService.유저네임중복체크(username);
return Resp.ok(respDTO);
}
@Operation(summary = "회원가입", description = "유저네임, 비밀번호, 이메일을 받아 회원가입을 진행합니다.")
@ApiResponse(responseCode = "200", description = "회원가입 성공",
content = @Content(schema = @Schema(implementation = UserResponse.DTO.class)))
@PostMapping("/join")
public ResponseEntity<?> join(
@Valid @RequestBody UserRequest.JoinDTO reqDTO,
Errors errors,
HttpServletResponse response,
HttpServletRequest request) {
log.debug(reqDTO.toString());
log.trace("트레이스ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
log.debug("디버그---------");
log.info("인포ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
log.warn("워닝ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
log.error("에러ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ");
String hello = request.getHeader("X-Key");
System.out.println("X-good : " + hello);
response.setHeader("Authorization", "jooho");
UserResponse.DTO respDTO = userService.회원가입(reqDTO);
return Resp.ok(respDTO);
}
@Operation(summary = "로그인", description = "유저네임과 비밀번호를 이용하여 로그인합니다.")
@ApiResponse(responseCode = "200", description = "로그인 성공",
content = @Content(schema = @Schema(implementation = UserResponse.TokenDTO.class)))
@PostMapping("/login")
public ResponseEntity<?> login(
@Valid @RequestBody UserRequest.LoginDTO loginDTO,
Errors errors,
HttpServletResponse response) {
UserResponse.TokenDTO respDTO = userService.로그인(loginDTO);
return Resp.ok(respDTO);
}
}6.
package shop.mtcoding.blog.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
public class UserResponse {
@Data
public static class TokenDTO {
@Schema(description = "엑세스 토큰", example = "eyJhbGciOiJIUzI1NiIsInR5cCI...")
private String accessToken;
@Schema(description = "리프레시 토큰", example = "dGhpc0lzUmVmcmVzaFRva2Vu")
private String refreshToken;
@Builder
public TokenDTO(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
@Data
public static class DTO {
@Schema(description = "유저 ID", example = "1")
private Integer id;
@Schema(description = "유저 이름", example = "cos")
private String username;
@Schema(description = "이메일 주소", example = "cos@nate.com")
private String email;
@Schema(description = "생성일시", example = "2024-05-16T10:00:00")
private String createdAt;
public DTO(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.email = user.getEmail();
this.createdAt = user.getCreatedAt().toString();
}
}
}
Share article