API란?

이언덕·2025년 10월 14일
post-thumbnail

1. 들어가며 — ‘API’라는 단어, 왜 이렇게 자주 들릴까?

사이드 프로젝트를 진행하다 보면 반드시 한 번은 “API 레이어 세팅”이라는 단계를 만나게 된다.
나 역시 이번 프로젝트에서 Supabase로 만든 데이터베이스를 프론트엔드와 연결하면서,
이 과정을 실제로 구현하고 있었다.

그런데 코드를 작성하다 문득 이런 생각이 들었다.


“API를 매번 쓰고 있는데… 정확히 API가 뭘 의미하는 걸까?


axios로 요청을 보내고, GET / POST 메서드를 사용하고, Swagger 문서로 엔드포인트를 확인하지만
정작 “API가 뭘 하는 놈인지” 설명하려니 말이 막혔다.

그래서 이번 글에서는 API의 개념을 처음부터 다시 짚어보기로 했다.
API가 왜 필요한지, 어떻게 작동하는지,
그리고 우리가 코드에서 자주 쓰는 CRUDREST 같은 개념이
이 안에서 어떤 역할을 하는지까지 하나씩 정리해보려 한다.




2. 정의 — API란 무엇인가?

APIApplication Programming Interface의 약자다.
직역하면 “응용 프로그램을 위한 프로그래밍 인터페이스”인데,
말이 너무 길다.
쉽게 말해, “프로그램과 프로그램이 서로 대화할 수 있게 돕는 통로”다.


📡 프로그램 간의 ‘약속된 대화법’

API는 “A 프로그램이 B 프로그램에게 어떤 형식으로 요청을 보내야 하는지,
그리고 어떤 형식으로 응답을 받아야 하는지”
를 정해놓은 규칙집이다.
이 규칙이 있기 때문에 전혀 다른 시스템끼리도 문제없이 통신할 수 있다.


예를 들어,
내 플래너 프로젝트에서는 프론트엔드(Next.js)가
Supabase 서버에게 데이터를 요청해야 한다.
이때 “어떤 URL로 요청을 보낼지, 어떤 메서드를 쓸지, 어떤 형식으로 데이터를 보낼지”
모두 API가 정해놓은 규칙을 따라야 한다.


🍽️ 비유로 이해하기

API를 식당으로 비유해보면 이해가 쉽다.

  • 손님(프론트엔드)은 “음식을 주문하는 사람”
  • 주방(서버)은 “요리를 실제로 만드는 곳”
  • API는 “주문을 전달하고 결과를 가져오는 웨이터

손님은 주방이 어떻게 요리를 하는지 알 필요가 없다.
메뉴판(=API 문서)에 적힌 대로 주문만 하면
요리가 완성돼서 나온다.


🧩 결국 API는 ‘연결을 위한 인터페이스’

정리하자면,
API는 프로그램이 직접 서로의 코드를 몰라도,
정해진 형식의 요청과 응답만 주고받으면 협력할 수 있게 해주는 인터페이스다.

즉,
“API는 소프트웨어 세계의 공용어라고 할 수 있다.”




3. 동작 원리 — API는 어떻게 작동할까?

API는 단순히 데이터를 주고받는 통로가 아니다.
서버 입장에서 보면, 요청을 해석하고, 유효성을 검사하고, 결과를 응답으로 돌려주는 하나의 프로세스다.
모든 API는 이 흐름을 따른다.


⚙️ 1) 요청(Request)

클라이언트(주로 프론트엔드)는 서버에 요청(Request)을 보낸다.
이 요청에는 “무엇을 하고 싶은지”가 명확히 담겨 있다.


예를 들어, 내 플래너 앱에서 “할 일 목록을 불러오기”를 요청한다고 하자.

GET /todos

이 한 줄로 “할 일 데이터를 달라”는 의미가 전달된다.
GET은 데이터를 조회(Read) 하겠다는 뜻이고, /todos자원(Resource) 의 경로다.


요청은 보통 HTTP 프로토콜을 통해 전송되며,
헤더(Header)에는 토큰이나 인증 정보, 바디(Body)에는 전송할 데이터가 담긴다.


🧠 2) 처리(Process)

서버는 요청을 받으면 우선 요청이 올바른지 검증한다.

  • 요청 경로(/todos)가 존재하는지
  • 인증 토큰이 유효한지
  • 필요한 데이터(title, id 등)가 모두 포함되어 있는지

이 검증 단계를 통과하지 못하면, 서버는 즉시 오류 응답(Error Response) 을 돌려준다.

예시:

{ "error": "Invalid request. Missing required field 'title'." }

반대로 요청이 정상이라면, 서버는 본격적으로 데이터를 처리한다.
데이터베이스를 읽거나, 새로운 항목을 추가하거나, 수정·삭제 작업을 수행한다.


예를 들어 Supabase의 /todos API는 이렇게 동작한다.
1. 요청한 사용자의 user_id를 확인하고
2. 해당 유저의 todos 데이터를 쿼리한 뒤
3. 그 결과를 JSON으로 반환한다.


📦 3) 응답(Response)

요청이 성공적으로 처리되면 서버는 응답(Response) 을 보낸다.
응답은 보통 status codebody로 구성된다.

✅ 정상 응답 예시:

200 OK
[
  { "id": 1, "title": "블로그 글 작성", "is_done": false },
  { "id": 2, "title": "API 테스트", "is_done": true }
]

❌ 오류 응답 예시:

400 Bad Request
{ "error": "Invalid parameters" }

이처럼 서버는 단순히 데이터를 주는 것이 아니라,
요청이 유효한지 판단하고 그 결과를 명확히 응답하는 역할을 한다.


🌐 클라이언트와 서버의 관계

  • 클라이언트(Client) → 데이터를 요청하는 쪽 (Next.js, React, 모바일 앱 등)
  • 서버(Server) → 요청을 검증하고, 처리한 뒤 응답을 돌려주는 쪽 (Supabase, Node.js 등)

이 둘은 서로의 내부를 알 필요가 없다.
중요한 건, 요청(Request)과 응답(Response)의 규칙만 지키는 것이다.
이 규칙이 바로 API의 언어다.


결국 API의 동작은 이렇게 요약된다.

“요청을 보내면, 서버는 그 요청을 검증하고, 결과를 응답으로 돌려준다.”
성공이면 데이터, 실패면 이유 — 이 단순한 구조가 웹 전체를 움직인다.




4. API의 기본 구조 — CRUD와 REST

API는 단순히 “요청하고 응답받는 구조”로 끝나지 않는다.
그 요청들이 무엇을 하기 위한 것인지까지 구체적인 목적을 갖고 있다.
대부분의 웹 서비스는 아래 네 가지 동작으로 구성된다.


🔄 4-1. CRUD란? — API가 수행하는 네 가지 기본 동작

CRUD는 모든 데이터 처리를 대표하는 네 가지 약어다.

동작설명예시 API 요청
Create새로운 데이터 추가POST /todos
Read데이터 조회GET /todos
Update데이터 수정PATCH /todos/:id
Delete데이터 삭제DELETE /todos/:id

예를 들어, Supabase의 todos 테이블과 연결된 REST API를 생각해보자.
프론트엔드(Next.js)는 axios로 이런 요청을 보낼 수 있다:

// 새로운 할 일 추가 (Create)
await axios.post("/todos", { title: "블로그 글 작성" });

// 목록 조회 (Read)
const { data } = await axios.get("/todos");

// 항목 수정 (Update)
await axios.patch(`/todos/${id}`, { is_done: true });

// 항목 삭제 (Delete)
await axios.delete(`/todos/${id}`);

이 네 가지 요청만으로 할 일 관리 서비스의 기본 기능이 완성된다.
즉, CRUD는 API의 근본적인 역할 모델이다.


🌍 4-2. REST API란? — 가장 널리 쓰이는 설계 방식

CRUD는 동작의 개념이라면,
REST API는 그 동작을 실제로 설계하고 표현하는 방식이다.

REST(Representational State Transfer)
“자원을 URL로 표현하고, 행동은 HTTP 메서드로 구분한다”는 철학을 가진다.

🧱 REST의 기본 원칙

  1. 모든 것은 자원(Resource)이다.
    /todos, /users, /notes 같은 엔드포인트는 각각 자원을 의미한다.

  2. 자원은 고유한 경로(URL)로 표현된다.
    /todos/123id = 123인 특정 할 일을 가리킨다.

  3. 행동은 HTTP 메서드로 표현한다.
    GET, POST, PATCH, DELETE 로 CRUD를 구현한다.

  4. 서버는 상태를 기억하지 않는다.
    → 각 요청은 독립적이며, 필요한 정보는 항상 요청에 포함되어야 한다.

🧩 CRUD ↔ REST 매핑

CRUD 동작HTTP 메서드예시 URL설명
CreatePOST/todos새 할 일 생성
ReadGET/todos전체 목록 조회
Read (단일)GET/todos/:id특정 항목 조회
UpdatePATCH 또는 PUT/todos/:id특정 항목 수정
DeleteDELETE/todos/:id특정 항목 삭제

REST API에 대해서 더 깊이 알고 싶다면 아래 글을 보는 것을 추천한다!
REST API란? REST, RESTful이란?


💬 REST의 장점

  • 구조가 단순하고 직관적이다.
  • 브라우저 개발자 도구나 Postman으로 쉽게 테스트할 수 있다.
  • 대부분의 백엔드 프레임워크(Supabase, Express, FastAPI 등)가 REST를 기본 지원한다.


    즉, REST는 웹 서비스의 공용 문법이라고 할 수 있다.
    URL과 메서드만 보면 “이 API가 무슨 일을 하는지” 바로 알 수 있다.

🚨 참고: REST API에서의 오류 처리

REST API는 항상 HTTP 상태 코드(Status Code) 를 함께 반환한다.

코드의미설명
200 OK성공요청이 정상적으로 처리됨
201 Created생성 성공새 리소스가 만들어짐
400 Bad Request잘못된 요청파라미터 오류 등
401 Unauthorized인증 실패로그인 필요
404 Not Found리소스 없음요청한 자원이 존재하지 않음
500 Internal Server Error서버 오류내부 로직 문제

이 상태 코드 덕분에 클라이언트는 “요청이 성공했는지, 실패했는지”를 일관된 방식으로 인식할 수 있다.


정리하자면,
CRUD는 API가 하는 일의 ‘종류’를 정의하고,
REST는 그 일을 ‘표현하는 방식’을 정의한다.

둘은 함께 웹 서비스의 핵심 구조를 이룬다.
플래너 프로젝트에서 Supabase가 자동으로 생성해주는 엔드포인트 역시
결국 이 REST 원칙을 따르고 있다.




5. 종류 — API에도 여러 형태가 있다

API라고 해서 모두 같은 형태로 동작하는 것은 아니다.
누가 사용할 수 있는지, 어떤 목적을 가지는지에 따라 여러 유형으로 나뉜다.
아래는 실제 서비스에서 자주 등장하는 세 가지 대표적인 유형이다.


🌍 5-1. Open API (공개 API)

누구나 사용할 수 있도록 공개된 API다.
대부분의 경우 회원가입만 하면 무료로 키(API Key) 를 발급받아 쓸 수 있다.

예시

  • 기상청 Open API → 날씨 데이터 제공
  • 카카오 지도 API → 지도, 장소 검색 기능
  • 공공데이터포털 API → 버스, 지하철, 행정 정보
  • TMDB API → 영화 포스터, 평점 데이터

특징

  • 접근성이 높다 (학습용, 테스트용으로 자주 사용됨)
  • 사용량 제한이 있음 (예: 하루 1,000건 등)
  • 인증키(API Key) 방식으로 호출

🏢 5-2. Private API (내부 API)

회사 내부 시스템끼리 통신할 때 사용하는 API다.
외부에는 공개되지 않고, 특정 서버 간 통신 또는 사내 대시보드용으로만 사용된다.

예시

  • 관리자(Admin) 대시보드 → 내부 사용자 관리
  • 통계 서버 → 프론트엔드용 데이터 전달
  • 마이크로서비스 간 통신 → user-servicetodo-service

특징

  • 인증 수준이 높고, 보안이 매우 중요함
  • 문서가 공개되지 않음
  • 변경 주기가 잦음 (내부 구조에 따라 유연하게 수정 가능)

내 플래너 프로젝트의 Supabase REST API도 기본적으로는 Private API다.
사용자 인증을 거친 뒤에만 각자의 데이터에 접근할 수 있다.


🤝 5-3. Partner API (제휴형 API)

특정 파트너나 제휴사에게만 제공되는 API다.
외부와의 통합(Integration) 목적을 가지며, 계약 기반으로 관리된다.

예시

  • 네이버페이 / 카카오페이 결제 API
  • 배민 × 토스포인트 적립 API
  • Slack, Notion, GitHub 등에서 제공하는 Third-Party Integration API

특징

  • 계약 및 제휴 관계가 있어야 접근 가능
  • SLA(Service Level Agreement)와 보안 규약이 명확함
  • 장애 대응 및 버전 관리가 체계적

📚 참고: REST 외의 다른 형태의 API

최근에는 REST 외에도 다양한 통신 구조가 쓰인다.

방식특징예시
GraphQL필요한 데이터만 선택적으로 요청 가능GitHub API v4
gRPC빠른 이진 통신, 서버 간 통신에 유리마이크로서비스 구조
WebSocket양방향 실시간 통신채팅, 알림 서비스
RPC (Remote Procedure Call)함수 호출 기반 구조내부 시스템 통신

REST가 ‘표준 언어’라면,
GraphQL이나 gRPC는 ‘상황에 따라 더 효율적인 대화법’이다.




6. 실생활 예시 — 우리가 매일 쓰는 API

사실 우리는 하루에도 수십 번씩 API를 사용한다.
단지 그걸 직접 호출하지 않을 뿐,
스마트폰의 거의 모든 앱이 백엔드와 API를 통해 연결되어 있다.


☁️ 날씨 앱 — 공공 Open API의 대표 예시

스마트폰에서 “오늘 날씨”를 확인하면,
앱은 기상청 Open APIGET 요청을 보낸다.

GET /weather?city=seoul

서버는 실시간 기상 데이터를 조회한 뒤 아래처럼 응답한다.

{ "temp": 24.3, "condition": "cloudy", "humidity": 62 }

앱은 이 JSON 데이터를 받아
아이콘과 텍스트로 예쁘게 보여주는 것뿐이다.

즉, “화면에 날씨가 보인다” = “API 응답을 시각화했다”는 뜻이다.


🗺️ 지도 불러오기 — 외부 서비스 API 연동

카카오맵, 네이버지도, 구글지도 모두 지도 API를 제공한다.
플래너 프로젝트에서 장소 기반 일정을 넣고 싶다면
카카오맵 API를 이렇게 불러올 수 있다.

GET /maps/search?query=카페

응답 예시:

[
  { "name": "스타벅스 홍대입구점", "lat": 37.556, "lng": 126.925 },
  { "name": "카페 어니언", "lat": 37.544, "lng": 127.055 }
]

앱은 이 좌표를 기반으로 지도에 마커를 표시한다.
즉, 우리가 클릭하는 지도 역시 API 호출의 결과다.


🔐 카카오 로그인 — 인증(Authentication) API

로그인도 결국 API다.
“카카오로 로그인하기” 버튼을 누르면
앱은 카카오 인증 서버로 요청을 보낸다.

POST /oauth/token

카카오 서버는 사용자의 동의를 확인하고
토큰(Token)을 발급해 응답한다.

{ "access_token": "abc123xyz", "expires_in": 7200 }

이 토큰이 있으면,
내 서버는 “이 사용자는 카카오에서 인증된 사람”임을 신뢰할 수 있다.
즉, 로그인 역시 API로 이루어진다.


💳 결제 — Partner API의 정수

결제창이 열리고 “결제 완료” 알림이 뜨는 순간,
백엔드에서는 다음과 같은 흐름이 진행된다.


1. 클라이언트 → 결제 요청 (POST /payments)
2. 서버 → PG사(카카오페이, 토스페이 등)에 API 요청
3. PG사 → 응답 (성공/실패 여부, 승인번호)
4. 서버 → 클라이언트에 최종 응답

결제 한 건을 처리하기 위해 실제로는 3~4개의 API가 연쇄적으로 작동한다.


💬 메신저 & 알림 — 실시간 API (WebSocket)

카카오톡, 슬랙, 디스코드 같은 메신저는
서버와 클라이언트가 항상 연결된 상태를 유지해야 한다.
이때 REST 대신 WebSocket API를 사용한다.


WebSocket은 요청-응답이 아니라
서버가 먼저 “새 메시지가 왔다”고 푸시할 수 있다.

즉, “알림이 바로 뜬다” = “서버가 실시간 API로 나에게 말 걸었다”는 뜻이다.


결국 우리가 날씨를 보고, 로그인하고, 결제하고, 대화하는 모든 순간 —
그 뒤에는 수많은 API 요청과 응답이 오가고 있다.
눈에 보이지 않지만,
API는 현대 서비스의 보이지 않는 혈관이다.




7. 개발 관점 — 프론트엔드와 백엔드는 API로 연결된다

API는 단순히 기술 용어가 아니다.
프론트엔드와 백엔드를 이어주는 실제 연결선이다.
우리가 작성하는 코드의 대부분은 결국 “API를 어떻게 주고받을 것인가”를 중심으로 움직인다.


🖥️ 프론트엔드 → API 요청 (Client Side)

프론트엔드는 사용자의 행동을 요청(Request) 으로 바꿔 서버에 보낸다.
예를 들어 사용자가 “할 일 추가” 버튼을 눌렀다고 해보자.

await axios.post("/api/todos", { title: "공부하기" });

이 한 줄의 의미는 단순하다.

“서버야, 새로운 할 일을 만들어줘.”

서버가 201 Created를 응답하면,
프론트엔드는 이 데이터를 받아 즉시 UI에 반영한다.


클릭 → 요청 → 응답 → UI 업데이트
이 한 사이클이 API 호출의 본질이다.


⚙️ 백엔드 → 요청 처리 (Server Side)

백엔드는 프론트엔드로부터 요청을 받으면,
다음과 같은 순서로 동작한다.


1. 요청의 유효성 검사 (토큰, 파라미터, Body 데이터 확인)
2. 해당 로직 수행 (DB에 삽입, 조회, 수정 등)
3. 결과를 응답으로 반환


예시 — Supabase REST API의 내부 흐름:

POST /todos
Authorization: Bearer {user_token}
Body: { "title": "공부하기" }

Supabase는 이 요청을 받아 내부에서 SQL을 실행한다.

insert into public.todos (user_id, title)
values (auth.uid(), '공부하기');

이후 응답으로 새로 생성된 레코드를 JSON 형태로 돌려준다.


🔄 API를 매개로 한 상태 동기화

프론트엔드는 데이터를 ZustandReact Query 같은 상태 관리 도구에 저장하고,
API를 통해 받은 서버 데이터와 동기화(Sync) 시킨다.


즉,

  • 로컬 상태(UI 중심)는 Zustand
  • 서버 상태(API 중심)는 React Query


    이렇게 역할을 분리하면,
    “화면이 즉시 반응하면서도 서버 데이터와 일관성 있는 상태 관리”가 가능하다.
const { data: todos } = useQuery(["todos"], fetchTodos);

이 한 줄이 사실상 API 호출 → 응답 → 상태 반영 과정을 자동으로 처리해준다.


🧩 BaaS (Backend as a Service)와 API 자동화

Supabase 같은 BaaS는 테이블을 만들면
자동으로 REST API를 생성해준다.


예를 들어 todos 테이블을 만들면 다음 엔드포인트가 자동으로 생긴다.

메서드엔드포인트기능
GET/rest/v1/todos데이터 조회
POST/rest/v1/todos데이터 추가
PATCH/rest/v1/todos?id=eq.{id}데이터 수정
DELETE/rest/v1/todos?id=eq.{id}데이터 삭제

이 구조는 우리가 직접 서버 코드를 작성하지 않아도,
즉시 CRUD API를 사용할 수 있게 해준다.
그래서 요즘은 “API를 만든다”보다 “API를 설계하고 연결한다”가 더 중요해지고 있다.


🔒 인증과 권한 관리

API는 모든 사용자의 요청을 받아들이면 안 된다.
로그인하지 않은 사용자가 내 데이터를 삭제할 수도 있기 때문이다.
그래서 대부분의 API는 인증(Authentication)인가(Authorization) 단계를 거친다.


예를 들어 Supabase에서는

  • 인증 토큰(Bearer Token)이 없으면 → 401 Unauthorized
  • 다른 사용자의 데이터에 접근하면 → 403 Forbidden


    이런 식으로 자동 차단된다.
    결국 보안이 포함된 데이터 통신 규칙이 바로 API다.


    결국 개발자 입장에서 API란

“UI를 실제 데이터와 연결시키는 유일한 통로이자,
사용자 행동을 서버 로직으로 변환하는 매개체”다.




8. API 문서 — 개발자들이 보는 ‘메뉴판’ (Swagger / OpenAPI)

API는 단순히 “요청 규칙”이 아니라,
팀 전체가 같은 규칙으로 협업할 수 있도록 정리된 공식 메뉴판이다.
이 문서 덕분에 프론트엔드와 백엔드가 “같은 언어”로 소통할 수 있다.


📘 Swagger / OpenAPI란?

  • OpenAPI: HTTP API를 기술하는 표준 스펙(JSON 또는 YAML 기반).
  • Swagger: 이 OpenAPI 스펙을 시각적으로 보여주고, 직접 테스트(Try it out)까지 가능한 도구 세트.


    예를 들어 아래는 실제 OpenMind API의 Swagger 문서 화면이다. 👇

위 문서에는

  • Base URL: https://openmind-api.vercel.app
  • 엔드포인트 예시: /team/questions/{id}/answers/
  • 메서드(GET, POST, PATCH, DELETE)
  • 모델 구조(AnswerRetrieveUpdate, Question, Reaction, Subject)
    등이 명확하게 정의되어 있다.

🧩 Models 구조 이해하기

Swagger 하단의 “Models” 영역에는 API가 주고받는 데이터 구조가 정리되어 있다.
예를 들어, 아래는 AnswerRetrieveUpdate, Question, Reaction, Subject 모델의 일부다. 👇

이 구조를 보면 어떤 필드가 필수인지(required),
각 필드의 타입이 무엇인지(string, boolean, integer)를 한눈에 파악할 수 있다.
결국 이 모델 정의가 API 요청/응답의 데이터 형태를 결정한다.


⚙️ Swagger 문서를 읽는 순서

  1. 상단의 Base URLAuth 방식(Bearer Token, API Key 등) 확인
  2. 좌측에서 엔드포인트 목록(GET /answers/{id}, POST /questions/{id}/answers/ 등) 확인
  3. 각 메서드 클릭 → Request Body, Response Example 구조 확인
  4. 필요한 파라미터(id, team, question_id) 체크 후 직접 “Try it out” 실행
  5. 성공하면 Response JSON을 복사해 실제 코드에서 예시로 활용

🧠 스펙 → 호출 코드로 옮기는 예시 (axios)

아래는 Swagger 스펙을 그대로 코드로 옮긴 4단계 구조 예시다.
(기준: OpenMind API의 POST /team/questions/{question_id}/answers/)

항목내용
Base URLhttps://openmind-api.vercel.app
엔드포인트POST /team/questions/{question_id}/answers/
요청 본문{ "content": "string", "isRejected": boolean }
응답 모델AnswerRetrieveUpdate
인증 방식Bearer 토큰

1) api/client.ts — 공통 axios 인스턴스

// api/client.ts
import axios from "axios";

export const api = axios.create({
  baseURL: "https://openmind-api.vercel.app", // Swagger Base URL
  headers: { "Content-Type": "application/json" },
});

// (선택) 요청 시 토큰 자동 첨부
api.interceptors.request.use((config) => {
  const token = localStorage.getItem("access_token"); // 프로젝트 컨벤션에 맞게 교체
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

2) types/openmind.ts — 스펙 기반 타입(필요 필드만)

// types/openmind.ts
export interface AnswerRetrieveUpdate {
  id: number;
  questionId: number;
  content: string;
  isRejected: boolean;
  createdAt: string; // ISO string
}

export interface Subject {
  id: number;
  title: string;
  // 필요 시 Swagger Models 보고 필드 추가
}

export interface Question {
  id: number;
  subjectId: number;
  content: string;
  like: number;
  dislike: number;
  createdAt: string;
}

3) api/subjects.ts, api/answers.ts — 엔드포인트 함수

// api/subjects.ts
import { api } from "./client";
import { Subject } from "../types/openmind";

// GET /{team}/subjects/
export async function listSubjects(team: string) {
  const { data } = await api.get<Subject[]>(`/${team}/subjects/`);
  return data;
}

// POST /{team}/subjects/
export async function createSubject(args: { team: string; body: { title: string } }) {
  const { team, body } = args;
  const { data } = await api.post<Subject>(`/${team}/subjects/`, body);
  return data;
}
// api/answers.ts
import { api } from "./client";
import { AnswerRetrieveUpdate } from "../types/openmind";

// POST /{team}/questions/{question_id}/answers/
export async function createAnswer(args: {
  team: string;
  questionId: number;
  body: { content: string; isRejected: boolean };
}) {
  const { team, questionId, body } = args;
  const { data } = await api.post<AnswerRetrieveUpdate>(
    `/${team}/questions/${questionId}/answers/`,
    body
  );
  return data;
}

// GET /{team}/answers/{id}/
export async function getAnswer(args: { team: string; id: number }) {
  const { team, id } = args;
  const { data } = await api.get<AnswerRetrieveUpdate>(`/${team}/answers/${id}/`);
  return data;
}

4) 사용 컴포넌트(간단 예시)

실전에서는 React Query 추천이지만, 여기서는 예시 간결성을 위해 기본 훅 사용

// app/examples/OpenMindExample.tsx
"use client";

import { useEffect, useState } from "react";
import { listSubjects } from "@/api/subjects";
import { createAnswer } from "@/api/answers";
import type { Subject, AnswerRetrieveUpdate } from "@/types/openmind";

export default function OpenMindExample() {
  const team = "my-team"; // 실제 팀 식별자로 교체
  const [subjects, setSubjects] = useState<Subject[]>([]);
  const [loading, setLoading] = useState(false);
  const [creating, setCreating] = useState(false);
  const [result, setResult] = useState<AnswerRetrieveUpdate | null>(null);

  // 1) 과목 목록 불러오기
  useEffect(() => {
    let mounted = true;
    setLoading(true);
    listSubjects(team)
      .then((data) => mounted && setSubjects(data))
      .finally(() => mounted && setLoading(false));
    return () => { mounted = false; };
  }, [team]);

  // 2) 임의 질문에 답변 달기 (예: questionId = 1)
  async function handleCreateAnswer() {
    try {
      setCreating(true);
      const data = await createAnswer({
        team,
        questionId: 1, // 실제 질문 ID로 교체
        body: { content: "안녕하세요! 예시 답변입니다.", isRejected: false },
      });
      setResult(data);
      alert("답변 생성 완료!");
    } finally {
      setCreating(false);
    }
  }

  return (
    <div className="p-4 space-y-4">
      <h2 className="text-xl font-bold">OpenMind API Example</h2>

      <section>
        <h3 className="font-semibold mb-2">1) Subjects 목록</h3>
        {loading ? <p>불러오는 중...</p> : (
          subjects.length ? (
            <ul className="list-disc pl-5">
              {subjects.map((s) => <li key={s.id}>{s.title}</li>)}
            </ul>
          ) : <p>과목이 없습니다.</p>
        )}
      </section>

      <section>
        <h3 className="font-semibold mb-2">2) 질문에 답변 생성</h3>
        <button className="rounded px-3 py-2 border" onClick={handleCreateAnswer} disabled={creating}>
          {creating ? "생성 중..." : "답변 생성"}
        </button>

        {result && (
          <pre className="mt-3 text-sm bg-neutral-100 p-3 rounded">
            {JSON.stringify(result, null, 2)}
          </pre>
        )}
      </section>
    </div>
  );
}

📋 Swagger 문서를 코드로 변환하는 5단계 요약

  1. Base URLaxios.create({ baseURL })
  2. 경로(Path)/team/questions/${id}/answers/
  3. Request Bodycontent, isRejected 등 필드 정의
  4. Response Model → Swagger의 AnswerRetrieveUpdate를 타입으로 정의
  5. Auth 헤더Authorization: Bearer <token>

Swagger 문서만 정확히 읽을 수 있다면,
이런 axios 코드는 거의 자동으로 변환할 수 있다.




9. 보안과 버전 — API를 안전하게 관리하기

API는 데이터를 주고받는 통로이기 때문에,
보안과 버전 관리가 되어 있지 않으면 서비스 전체가 흔들린다.
특히 로그인, 사용자 데이터, 토큰이 오가는 부분에서는 기본 원칙만이라도 지켜야 한다.


1) 인증(Authentication)

“이 요청이 누구로부터 왔는가?”


가장 흔한 방식은 Bearer Token이다.
사용자가 로그인하면 서버에서 토큰을 발급하고,
이 토큰을 매 요청마다 함께 보낸다.

// axios 요청 시 토큰을 헤더에 포함
api.interceptors.request.use((config) => {
  const token = localStorage.getItem("access_token");
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

서버는 이 토큰이 유효한지 검사하고,
만료되었거나 위조된 경우엔 401 Unauthorized로 응답한다.


2) 인가(Authorization)

“이 사용자가 이 행동을 해도 되는가?”


인증을 통과했다고 해서 모든 데이터에 접근할 수 있는 건 아니다.
예를 들어 다른 사용자의 todo를 수정하는 건 금지되어야 한다.

Supabase 같은 서비스는 RLS(Row Level Security) 로 이걸 자동으로 관리한다.
SQL 정책 예시:

create policy "사용자 본인 데이터만 보기"
on public.todos
for select using (auth.uid() = user_id);

4) HTTPS와 CORS

✅ HTTPS — “모든 데이터는 암호화되어야 한다”

  • 왜 필요한가?
    HTTP는 평문으로 통신하므로 토큰이나 개인정보가 유출될 수 있다.
    HTTPS는 TLS 암호화로 데이터를 보호한다.

  • 기본 원칙
    • 배포 환경에서는 항상 https://만 사용 (Vercel, Netlify 기본 지원)
    • 내부 이미지나 API도 전부 HTTPS로 통일 (혼합 콘텐츠 방지)

  • 실무 팁
    • 개발용 http://localhost만 예외
    • 서버(혹은 프록시)에서 HTTPS 리다이렉트를 한 번 더 걸면 완벽하다.

✅ CORS — “내 API를 누가 부를 수 있는가”

  • 개념
    CORS(Cross-Origin Resource Sharing)
    “어떤 웹사이트의 JS가 내 API를 호출할 수 있는가”를 정하는 브라우저 규칙이다.
    즉, 허용된 도메인만 요청 가능하도록 막는 보안 장치다.

  • 예시
    • https://myapp.vercel.app → ✅ 허용
    • https://randomsite.com → ❌ 차단 (CORS 에러 발생)

  • 설정 원칙
    • Access-Control-Allow-Origin정확한 도메인만 허용
      *(와일드카드)는 인증 요청(Authorization/Cookie) 과 함께 쓰면 안 된다.
    • 인증이 필요한 경우 credentials: true 설정 후 정확한 출처를 명시

  • 자주 겪는 에러
    • “CORS 정책에 의해 차단됨” → 서버 응답에 Access-Control-Allow-Origin 누락
    • Preflight(OPTIONS) 요청 무시 → 서버에서 200/204 응답 필요


      👉 요약
항목핵심 요약
HTTPS데이터 암호화, HTTP 금지
CORS허용 도메인만 접근 가능
Tip인증이 있다면 * 대신 정확한 도메인 명시

4) 버전 관리(Versioning)

“기존 사용자에게 영향을 주지 않고 API를 바꾸는 법”


새로운 기능을 추가하더라도,
기존 클라이언트가 사용하는 구조는 깨지지 않아야 한다.
그래서 API는 보통 버전을 붙여 관리한다.

버전의미
/v1/...첫 버전
/v2/...구조가 달라진 새 버전

예:

GET /v1/todos
GET /v2/todos

기존 /v1은 그대로 유지하고, /v2를 새로 추가한다.
나중에 /v1을 제거할 땐 “Deprecated(지원 종료 예정)” 문구를 미리 안내한다.


✅ 요약

  • 로그인/민감 데이터는 반드시 토큰 인증.
  • 권한은 본인 데이터만 허용 (Supabase RLS 추천).
  • HTTPS + CORS 허용 도메인 지정.
  • 버전은 /v1, /v2 로 구분, 기존 API는 바로 없애지 않는다.



10. 마무리 — 결국 API는 ‘연결’이다

API는 단순히 데이터를 주고받는 통로가 아니다.
사람과 시스템, 프론트엔드와 백엔드, 서비스와 서비스 사이를 연결하는 약속이다.


우리가 버튼을 클릭하면,
그 행동은 Request가 되어 서버로 전달되고,
서버는 그 의미를 해석해 Response를 돌려준다.
이 단순한 흐름이 반복되며, 하나의 서비스가 완성된다.


💬 사이드 프로젝트에서 느낀 점

이번 프로젝트에서 API 레이어를 직접 만들면서
가장 크게 느낀 건 “모든 기능은 결국 API 위에 있다”는 사실이었다.
화면이 아무리 예뻐도,
데이터가 정확히 오가지 않으면 서비스는 동작하지 않는다.
즉, API는 서비스의 ‘실제 뼈대’다.


🔗 좋은 API의 조건

  1. 일관성 — 이름, 구조, 응답 형식이 어디서든 같다.
  2. 명확성 — 문서를 읽으면 바로 사용할 수 있다.
  3. 안정성 — 보안, 버전, 오류 처리가 정리되어 있다.
  4. 확장성 — 새로운 기능이 추가되어도 기존 코드는 깨지지 않는다.

이 네 가지를 지키는 게 결국 “잘 만든 API”의 기준이었다.

결국 API는 코드를 넘어서 “약속”이다.
팀이 같은 규칙으로 말하고,
시스템이 같은 언어로 소통할 수 있게 만드는,
프로덕트의 가장 중요한 연결선이다.

0개의 댓글