효율적인 RESTful API 설계와 구현 가이드
KUKJIN LEE • 5개월 전 작성
RESTful API는 현대 웹 개발의 중요한 요소입니다. 효율적으로 설계된 RESTful API는 애플리케이션의 확장성과 유지보수성을 크게 향상시킵니다.
1. RESTful API란?
REST(Representational State Transfer)는 웹의 설계 원칙 중 하나로, HTTP 프로토콜을 통해 데이터를 주고받는 아키텍처 스타일입니다. RESTful API는 이러한 REST 원칙을 따르는 API를 의미합니다. 주요 특징으로는 자원의 명확한 URI, HTTP 메소드 사용, 상태 비저장성 등이 있습니다.
2. RESTful API 설계 원칙
-
자원(Resource) 기반 URI: 자원은 URI를 통해 명확하게 식별됩니다. 예를 들어,
/users
는 사용자 자원을 나타냅니다. -
HTTP 메소드 사용: GET, POST, PUT, DELETE 등의 HTTP 메소드를 사용하여 자원에 대한 CRUD(생성, 읽기, 업데이트, 삭제) 작업을 수행합니다.
-
상태 비저장성: 서버는 클라이언트의 상태를 저장하지 않으며, 모든 요청은 독립적으로 처리됩니다.
-
표현(Representation): 클라이언트와 서버 간 데이터 교환은 JSON, XML 등의 표현 형식을 사용합니다.
3. RESTful API Best Practices
-
명확하고 일관된 네이밍: URI와 메소드 사용을 일관성 있게 유지합니다.
-
HTTP 상태 코드 사용: 적절한 HTTP 상태 코드를 반환하여 요청 결과를 명확하게 나타냅니다.
-
페이징, 필터링, 정렬: 대량의 데이터를 효율적으로 처리하기 위해 페이징, 필터링, 정렬 기능을 제공합니다.
-
보안: 인증 및 권한 부여 메커니즘을 사용하여 API를 보호합니다. 예를 들어, JWT(Json Web Token)를 이용한 인증.
Node.js를 이용한 RESTful API 구현
물론 아키텍처 패턴(디자인 패턴)으로 설계하면 아래 코드의 유지보수성을 높이고, 재사용성을 높일 수 있습니다. 예시를 위해 작성했습니다.
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
let users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
// 모든 사용자 조회
app.get('/users', (req, res) => {
res.json(users);
});
// 특정 사용자 조회
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).send('사용자를 찾을 수 없습니다.');
res.json(user);
});
// 사용자 생성
app.post('/users', (req, res) => {
if (!req.body.name) return res.status(400).send('이름이 필요합니다.');
const newUser = {
id: users.length + 1,
name: req.body.name
};
users.push(newUser);
res.status(201).json(newUser);
});
// 사용자 업데이트
app.put('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).send('사용자를 찾을 수 없습니다.');
if (!req.body.name) return res.status(400).send('이름이 필요합니다.');
user.name = req.body.name;
res.json(user);
});
// 사용자 삭제
app.delete('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).send('사용자를 찾을 수 없습니다.');
users = users.filter(u => u.id !== parseInt(req.params.id));
res.status(204).send();
});
app.listen(port, () => {
console.log(`서버가 http://localhost:${port} 에서 실행 중입니다.`);
});
Java (Spring Boot)를 이용한 RESTful API 구현
Java의 예시 또한 아키텍처 패턴(디자인 패턴)으로 설계하면 아래 코드의 유지보수성을 높이고, 재사용성을 높일 수 있습니다. 예시를 위해 작성했습니다.
(어노테이션을 사용하면 아래 코드는 훨씬 간단하게 작성할 수 있기 때문에, RESTful API 이해 용도로만 봐주세요!)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@SpringBootApplication
public class RestfulApiApplication {
public static void main(String[] args) {
SpringApplication.run(RestfulApiApplication.class, args);
}
}
@RestController
@RequestMapping("/users")
class UserController {
private List<User> users = new ArrayList<>();
public UserController() {
users.add(new User(1, "Alice"));
users.add(new User(2, "Bob"));
}
@GetMapping
public List<User> getUsers() {
return users;
}
@GetMapping("/{id}")
public User getUser(@PathVariable int id) {
Optional<User> user = users.stream().filter(u -> u.getId() == id).findFirst();
if (!user.isPresent()) {
throw new UserNotFoundException("사용자를 찾을 수 없습니다.");
}
return user.get();
}
@PostMapping
public User createUser(@RequestBody User newUser) {
newUser.setId(users.size() + 1);
users.add(newUser);
return newUser;
}
@PutMapping("/{id}")
public User updateUser(@PathVariable int id, @RequestBody User updatedUser) {
User user = getUser(id);
user.setName(updatedUser.getName());
return user;
}
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable int id) {
User user = getUser(id);
users.remove(user);
}
}
class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
// getters and setters
}
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "사용자를 찾을 수 없습니다.")
class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}