REST API란?

wonway·2024년 2월 7일
0
post-thumbnail

HTTP 메서드와 URI를 사용한 웹 표준 통신 규칙

HTTP 메서드 는 동작과 자원을 포함

URI 는 서버의 위치와 리소스의 경로를 포함

api 통신을 할 때 표준 형식을 따라 클라이언트와 서버가 만들어져있으면 다른 서비스로의 확장성, 서버의 교체가 쉬운 점 등의 장점이 있음

REST 디자인 원칙

Design Principles ( 디자인 원칙 )을 따르는 API를 REST API라고 함

REST Design Principles ( 디자인 원칙 )

  • 1) decouple ( 분리 )
    • 독립성
    • 요청과 응답만 신경 쓰고 데이터를 어떻게 다룰지는 서로 개입하지 않음
  • 2) Statelessness ( 무상태성 )
    • 일회성

    • 장점
      - 리퀘스트에 모든 정보가 있기 때문에 디버깅 수월 함, 다른 곳에서 찾을 것이 없음
      - state를 보관하지 않기 때문에 다른 서버로 대체 하기 쉬움

      예시) 음식 배달 주문

      Statelessness
      손님: 메뉴 , 주소 정보를 담아 요청
      가게: 요청한 메뉴 를 해당 주소로 응답

      Stateful
      손님: 어제와 같은거 로 주세요
      가게: 어제의 주문 명세 에서 메뉴 , 주소 를 찾아서 응답

  • 3) Uniform Interface (일관된 인터페이스)
    • 간소화, 일관성
    • 상호작용은 표준화된 방법을 통해 이루어짐
    • HTTP 메서드, URI 사용
  • 4) Cacheability ( 캐시 가능성)
    • 확장성 , 성능개선
    • 캐시할 수 있어야 함
    • 캐시하면 동일 요청을 불필요하게 반복할 필요가 적어짐
    • HTTP 헤더를 통해 캐시 정책을 명확히 정의해야 함. 예를 들어, Cache-Control, Expires, ETag, Last-Modified 등의 헤더를 사용하여 리소스의 캐시 가능 여부, 캐시 유지 기간, 캐시된 리소스의 유효성 검사 등을 관리할 수 있음.
  • 5) Layered system architecture ( 계층화된 시스템 아키텍처 )
    • 보안 가공 추상화 캡슐화
    • 프록시, 게이트웨이, 로드밸런서 등 통신의 중간 매개체 가 존재 할 수 있음

REST API 작동 방법

HTTP를 사용해서 Resource의 CRUD 기능을 지원

  • GET (읽기)
  • POST (생성)
  • PUT/PATCH (수정)
  • DELETE (삭제)

예제 ) express를 사용해서 로컬 서버를 만들어 통신

  1. 아래와 같이 폴더와 파일을 생성
  • index.html
  • server.js

  1. 루트 폴더에서 터미널을 열고 express, cors 설치
npm i express
npm i cors
  1. 서버와 클라이언트 코드 작성
//server.js
const express = require("express");
const cors = require("cors"); // CORS를 위한 패키지
const app = express();
const PORT = 3000;

app.use(cors()); // CORS 미들웨어 사용
app.use(express.json());

// 간단한 데이터 저장소
let todos = [
  { id: 1, title: "Learn REST API", completed: false },
  { id: 2, title: "Build a project", completed: false },
];

// GET 요청 처리: 모든 할 일 목록 반환
app.get("/api/todos", (req, res) => {
  res.status(200).json(todos);
});

// POST 요청 처리: 새 할 일 추가
app.post("/api/todos", (req, res) => {
  const todo = {
    id: todos.length + 1,
    title: req.body.title,
    completed: false,
  };
  todos.push(todo);
  res.status(201).send(todo);
});

// DELETE 요청 처리: 특정 할 일 삭제
app.delete("/api/todos/:id", (req, res) => {
  const { id } = req.params; // 요청에서 ID 추출

  const newTodo = todos.filter(todo => todo.id !== parseInt(id));
  todos = newTodo;
  res.status(204).send(); // 성공적으로 처리되었으나 클라이언트에 반환할 내용이 없음
});

//PUT 요청 처리 : 특정 할 일 수정
app.put("/api/todos/:id", (req, res) => {
  const { id } = req.params; // 요청에서 ID 추출
  let found = false; // 수정할 할 일이 없는 경우를 위한 변수 선언
  // 수정 로직
  todos = todos.map(todo => {
    if (todo.id === parseInt(id)) {
      found = true;
      return {
        ...todo, // 기존 todo 객체의 속성을 복사
        ...req.body, // req.body에서 받은 속성으로 덮어쓰기
      };
    }
    return todo; // 수정할 할 일이 없는 경우 기존 todo 객체 반환
  });

  if (found) {
    res.status(200).send({ message: "Todo updated succecfully" });
  } else {
    res.status(404).send({ message: "Todo not found" }); // 수정할 할 일이 없는 경우 404 응답 보냄
  }
});

// 서버 시작
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
// index.html
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Todo List</title>
  </head>
  <body>
    <h1>Todo List</h1>
    <ul id="todo-list"></ul>
    <form id="todo-form">
      <input
        type="text"
        id="todo-input"
        placeholder="Add a new todo"
        required
      />
      <button type="submit">Add Todo</button>
    </form>

    <script>
      document.addEventListener("DOMContentLoaded", fetchTodos);

      const form = document.getElementById("todo-form");
      form.addEventListener("submit", addTodo);

      function fetchTodos() {
        // 서버에 있는 투두리스트 정보 읽기 GET
        fetch("http://localhost:3000/api/todos")
          .then(response => response.json())
          .then(data => {
            console.log("GET 성공, todos:", data);
            const list = document.getElementById("todo-list");
            list.innerHTML = ""; // Clear current todos
            data.forEach(todo => {
              //가져온 데이터를 랜더링함
              const li = document.createElement("li");
              li.textContent = todo.title;
              list.appendChild(li);

              //삭제 버튼
              const deleteButton = document.createElement("button");
              deleteButton.innerHTML = "삭제";
              li.appendChild(deleteButton);
              deleteButton.onclick = function () {
                deleteTodo(todo.id);
              };

              //수정 버튼
              const editButton = document.createElement("button");
              editButton.textContent = "수정";
              editButton.onclick = function () {
                editTodo(todo.id);
              };
              li.appendChild(editButton);
            });
          });
      }

      // 특정 할 일 수정 PUT
      function editTodo(todoId) {
        const newTitle = prompt("Enter new title:");
        if (!newTitle) {
          alert("Title cannot be empty!");
          return;
        }

        fetch(`http://localhost:3000/api/todos/${todoId}`, {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ title: newTitle, completed: false }),
        })
          .then(response => response.json())
          .then(() => {
            fetchTodos();
          })
          .catch(error => conssole.error("Error:", error));
      }

      // 특정 할 일 삭제 DELETE
      function deleteTodo(todoId) {
        console.log("todoId", todoId);
        fetch(`http://localhost:3000/api/todos/${todoId}`, {
          method: "DELETE",
        })
          .then(() => {
            fetchTodos(); // 할 일 목록 새로고침
          })
          .catch(error => console.error("Error:", error));
      }

      //할 일 생성 POST
      function addTodo(event) {
        event.preventDefault();
        const input = document.getElementById("todo-input");
        const title = input.value;
        fetch("http://localhost:3000/api/todos", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ title: title }),
        })
          .then(response => response.json())
          .then(data => {
            fetchTodos(); // Refresh the list
            input.value = ""; // Clear input
          })
          .catch(error => console.error("Error:", error));
      }
    </script>
  </body>
</html>

코드를 실행해보면 CRUD 기능이 동작함을 볼 수 있음

일부분만 잘라서 살펴보면

클라이언트 측

  • body에 title을 담아서 POST 요청
fetch("http://localhost:3000/api/todos", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ title: title }),
        })

서버 측

  • body에 담긴 title을 받아서 데이터저장소에 추가
  • 새로 생성한 todo 객체를 반환
    • 클라이언트는 반환된 객체를 랜더링
    • 저장소와 클라이언트 화면에 데이터가 추가됨
app.post("/api/todos", (req, res) => {
  const todo = {
    id: todos.length + 1,
    title: req.body.title,
    completed: false,
  };
  todos.push(todo);
  res.status(201).send(todo);
});

여기까지 간략하게 REST의 개념을 정리하고 적용을 해보며 API 호출하는 코드를 그냥 공식처럼 쓰는 것이 아니라 왜 그런 형식으로 작성하는지 좀 더 이해가 넓어졌다.


REST API에 대해 더 깊이 공부할 것이 많이 있다.

https://restfulapi.net/

profile
문제를 컴퓨터로 해결하는 데서 즐거움을 찾는 프론트엔드 개발자

0개의 댓글