관리 메뉴

♠개발자의 작은 서재♠

RESTful API의 기본 개념과 Spring에서 REST API 만드는 방법 ( 1 ) 본문

Spring MVC/웹 애플리케이션 개발

RESTful API의 기본 개념과 Spring에서 REST API 만드는 방법 ( 1 )

♠디지털 모험일지♠ 2024. 10. 21. 19:22

RESTful API의 기본 개념

REST (Representational State Transfer)는 웹 서비스 설계를 위한 아키텍처 스타일입니다. RESTful API는 HTTP 프로토콜을 통해 클라이언트와 서버 간의 통신을 수행하며, 다음과 같은 원칙을 따릅니다:

  • Stateless: 서버는 요청 간에 클라이언트 상태를 저장하지 않습니다. 모든 요청은 독립적이어야 하며, 필요한 모든 정보를 포함해야 합니다.
  • Resource: URI를 통해 자원을 명확히 식별합니다. 예를 들어 /users는 사용자 목록을, /users/1은 특정 사용자를 나타냅니다.
  • HTTP Methods: 자원에 대한 작업은 HTTP 메서드를 사용하여 나타냅니다.
    • GET: 리소스를 조회
    • POST: 리소스를 생성
    • PUT: 리소스를 수정
    • DELETE: 리소스를 삭제
  • Representation: 서버는 요청에 따라 JSON, XML 등의 형식으로 데이터를 반환합니다.

Spring에서 REST API 만들기

Spring Framework는 RESTful API를 쉽게 만들 수 있도록 여러 어노테이션과 기능을 제공합니다. 이 중 주요한 어노테이션은 @RestController@RequestMapping입니다.

@RestController

@RestController는 클래스가 REST API의 엔드포인트임을 나타내는 어노테이션입니다. 이 클래스 내에 정의된 메서드들은 REST 요청을 처리하고, 그 결과를 JSON 또는 XML 등의 형태로 반환합니다.

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;

@RestController
public class UserController {

    @GetMapping("/users")
    public List<User> getAllUsers() {
        return Arrays.asList(
            new User(1, "John"),
            new User(2, "Jane")
        );
    }
}

위의 예시에서는 /users 엔드포인트로 GET 요청이 들어오면, 모든 사용자 목록을 반환하는 API가 구현됩니다.

@RequestMapping

@RequestMapping 어노테이션은 특정 URI에 대한 요청을 매핑하기 위해 사용됩니다. 주로 클래스나 메서드에 적용되며, HTTP 메서드를 명시적으로 설정할 수 있습니다.

예시 코드: @RequestMapping 사용하기

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class UserController {

    @RequestMapping(method = RequestMethod.GET)
    public List<User> getAllUsers() {
        return Arrays.asList(
            new User(1, "John"),
            new User(2, "Jane")
        );
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public User getUserById(@PathVariable int id) {
        return new User(id, "User " + id);
    }
}

이 코드는 /users로 GET 요청이 들어오면 사용자 목록을 반환하고, /users/{id}로 GET 요청이 들어오면 해당 id에 맞는 사용자를 반환합니다.

자주 묻는 질문 (FAQ)

1. @Controller@RestController의 차이점은 무엇인가요?

@Controller는 주로 뷰(View)를 반환하는 데 사용되며, 템플릿을 통해 웹 페이지를 구성합니다. 반면에, @RestController는 데이터를 반환하는 RESTful 서비스를 만들 때 사용됩니다. @RestController@Controller@ResponseBody를 결합한 것으로, 메서드가 반환하는 데이터는 자동으로 JSON 또는 XML 형식으로 직렬화됩니다.

2. @RequestMapping@GetMapping, @PostMapping의 차이점은 무엇인가요?

@RequestMapping은 모든 HTTP 메서드(GET, POST, PUT, DELETE 등)를 처리할 수 있지만, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping은 각각 특정 HTTP 메서드를 처리하기 위해 사용됩니다. 예를 들어, @GetMapping은 GET 요청을 처리하기 위해 사용됩니다.

3. RESTful API에서 상태를 유지해야 할 때는 어떻게 하나요?

REST는 기본적으로 무상태(stateless)를 지향하지만, 필요에 따라 상태를 유지해야 할 경우 쿠키나 세션을 사용할 수 있습니다. 다만, REST의 원칙에 맞게 이를 최소화하는 것이 좋습니다. 상태 유지가 필요하다면 토큰 기반 인증(JWT) 등을 고려해볼 수 있습니다.

4. REST API에서 에러 처리는 어떻게 하나요?

RESTful API에서는 HTTP 상태 코드를 사용하여 에러를 처리하는 것이 일반적입니다. 예를 들어:

  • 200 OK: 요청이 성공적으로 처리됨
  • 201 Created: 새로운 리소스가 성공적으로 생성됨
  • 400 Bad Request: 잘못된 요청 (유효성 검사 실패 등)
  • 404 Not Found: 요청한 리소스를 찾을 수 없음
  • 500 Internal Server Error: 서버에서 처리 중 에러 발생

Spring에서는 예외 처리 시 @ExceptionHandler 어노테이션을 활용하거나 ResponseEntity를 사용하여 상태 코드와 응답을 명시적으로 지정할 수 있습니다.

5. REST API에 인증을 추가하려면 어떻게 해야 하나요?

REST API는 무상태이므로, 일반적으로 세션을 사용하지 않고 토큰 기반 인증을 많이 사용합니다. 대표적으로 JWT (JSON Web Token)가 있습니다. 사용자가 로그인하면 JWT 토큰을 발급하고, 이후 요청 시 이 토큰을 Authorization 헤더에 포함하여 서버로 전달합니다.

Spring Security를 사용하면 이러한 인증 및 권한 관리를 쉽게 구현할 수 있습니다.

예시: JWT 인증 적용

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AuthController {

    private final AuthenticationManager authenticationManager;

    public AuthController(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @PostMapping("/login")
    public String authenticateUser(@RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginRequest.getUsername(),
                        loginRequest.getPassword()
                )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);

        // JWT 생성 후 반환 (토큰 생성 로직 추가 필요)
        return "JWT Token";
    }
}

이 코드는 사용자가 /login 엔드포인트로 로그인 요청을 보낼 때, 인증이 성공하면 JWT 토큰을 생성하고 반환하는 방식으로 동작합니다.

6. Spring에서 REST API에 대해 CORS 설정은 어떻게 하나요?

CORS(Cross-Origin Resource Sharing)란, 웹 애플리케이션이 다른 도메인의 리소스에 접근할 수 있도록 허용하는 메커니즘입니다. Spring에서 CORS 설정은 간단하게 가능합니다.

글로벌 CORS 설정

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 모든 경로에 대해 CORS 허용
                .allowedOrigins("http://example.com") // 허용할 도메인
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 허용할 HTTP 메서드
                .allowCredentials(true);
    }
}

위의 코드는 모든 경로에 대해 특정 도메인에서 오는 요청에 대해 CORS를 허용하는 설정을 적용합니다. allowedOrigins 메서드에 허용할 도메인을 지정하고, allowedMethods를 통해 허용할 HTTP 메서드를 정의할 수 있습니다.

개별 컨트롤러에 CORS 설정

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(origins = "http://example.com")
public class UserController {

    @GetMapping("/users")
    public List<User> getAllUsers() {
        return Arrays.asList(
            new User(1, "John"),
            new User(2, "Jane")
        );
    }
}

위의 코드는 @CrossOrigin 어노테이션을 사용하여 특정 엔드포인트(/users)에 대해 CORS를 허용하는 방법을 보여줍니다. origins 속성을 통해 허용할 도메인을 지정할 수 있습니다. 이 방식은 글로벌 설정과 달리 개별 컨트롤러 또는 메서드에 대해 적용할 수 있습니다.

7. RESTful API와 상태 코드, 오류 메시지를 함께 반환하려면 어떻게 해야 하나요?

RESTful API에서 상태 코드와 오류 메시지를 함께 반환하는 방식은 ResponseEntity 객체를 사용하여 구현할 수 있습니다. 이 객체를 사용하면 응답 본문뿐만 아니라 HTTP 상태 코드와 헤더를 함께 설정할 수 있습니다.

예시: 오류 메시지와 상태 코드 반환하기

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUserById(@PathVariable int id) {
        User user = findUserById(id);

        if (user == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND); // 404 상태 코드 반환
        }

        return new ResponseEntity<>(user, HttpStatus.OK); // 200 상태 코드와 함께 사용자 데이터 반환
    }

    private User findUserById(int id) {
        // 데이터베이스에서 사용자 조회 로직 (간단한 예시)
        if (id == 1) {
            return new User(1, "John");
        } else {
            return null;
        }
    }
}

위의 예시에서는 사용자가 없는 경우 404 상태 코드(HttpStatus.NOT_FOUND)를 반환하고, 사용자가 존재하면 200 상태 코드(HttpStatus.OK)와 함께 사용자 데이터를 반환합니다. ResponseEntity를 사용하면 이렇게 응답의 상태 코드와 데이터를 함께 반환할 수 있어, 클라이언트에게 더 명확한 응답을 제공합니다.

8. RESTful API에서 요청 데이터의 유효성 검증은 어떻게 하나요?

Spring에서는 @Valid 어노테이션과 BindingResult 객체를 사용하여 요청 데이터의 유효성을 검증할 수 있습니다. 모델 객체에 제약 조건을 추가하고, 유효성 검증에 실패했을 경우 적절한 오류 메시지를 반환할 수 있습니다.

예시: 유효성 검증 적용하기

import javax.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @PostMapping("/users")
    public ResponseEntity<String> createUser(@Valid @RequestBody User user, BindingResult result) {
        if (result.hasErrors()) {
            return new ResponseEntity<>("Invalid data", HttpStatus.BAD_REQUEST); // 400 상태 코드와 오류 메시지 반환
        }

        // 사용자 생성 로직 (예: 데이터베이스에 저장)
        return new ResponseEntity<>("User created", HttpStatus.CREATED); // 201 상태 코드와 함께 성공 메시지 반환
    }
}

위의 코드는 사용자가 /users 엔드포인트로 POST 요청을 보낼 때, 유효성 검증을 통과하지 못하면 400 상태 코드와 함께 오류 메시지를 반환합니다. 유효성 검증을 통과한 경우에는 201 상태 코드와 함께 사용자 생성 성공 메시지를 반환합니다.

9. RESTful API에서 페이징 처리하기

대량의 데이터를 처리할 때는 클라이언트가 한 번에 모든 데이터를 요청하지 않고, 일부만 요청할 수 있도록 페이징 처리를 구현할 수 있습니다. Spring Data JPA를 사용하면 손쉽게 페이징 기능을 추가할 수 있습니다.

예시: 페이징 처리 구현

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @GetMapping("/users")
    public Page<User> getUsers(@RequestParam(defaultValue = "0") int page,
                               @RequestParam(defaultValue = "10") int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findAll(pageable);
    }
}

위의 코드는 /users 엔드포인트에서 pagesize 파라미터를 받아, 해당 페이지의 사용자 목록을 반환하는 방식입니다. Pageable 객체를 사용해 원하는 페이지와 크기를 설정할 수 있으며, 반환되는 값은 Page<User> 타입으로, 페이지 정보와 함께 사용자 목록이 반환됩니다.

10. RESTful API와 HATEOAS

HATEOAS(Hypermedia As The Engine Of Application State)는 RESTful API에서 리소스와 관련된 추가 링크를 제공하여 클라이언트가 다음에 어떤 작업을 할 수 있는지 안내하는 방식입니다. Spring HATEOAS를 사용하면 쉽게 구현할 수 있습니다.

예시: HATEOAS 적용하기

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

import org.springframework.hateoas.EntityModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @GetMapping("/users/{id}")
    public EntityModel<User> getUserById(@PathVariable int id) {
        User user = findUserById(id);

        EntityModel<User> resource = EntityModel.of(user);
        resource.add(linkTo(methodOn(UserController.class).getAllUsers()).withRel("all-users"));

        return resource;
    }
}

위의 코드는 사용자가 특정 사용자를 요청할 때, 해당 사용자의 정보와 함께 모든 사용자 목록을 조회할 수 있는 링크를 함께 제공하는 방식입니다. EntityModel 객체를 사용하여 리소스에 추가 링크를 더할 수 있습니다.

Comments