이전 포스트에서 화면별로 어떤 데이터를 어느 시점에 서버로부터 받아올 지 생각했습니다. 그걸 토대로 API 문서를 작성해 봅시다.
작성자나 사내 컨벤션에 따라 조금씩 다르겠지만 거의 모든 API문서가 공통으로 적는 항목들이 있습니다.
Interface ID: 여러 API 항목들을 구별하는 IDEndpoint: 요청 경로 + HTTP 메서드Description: 간단한 설명Request Payload: 클라이언트가 요청 시 보내는 데이터Response Data: 서버가 응답으로 보내는 데이터저는 저 5가지만 적도록 하겠습니다.
원래는 Request Payload와 Response Data 항목에 각 데이터의 타입, 필수여부, 설명, 비고 등 적을 게 더 많습니다.
근데 저는 지금 Markdown의 table 문법을 이용해서 써내려가야 하니 그런 복잡한 표는 만들기 힘드네요..! 이번엔 이름과 타입만 적어주고 넘어가겠습니다.
보통 API 문서에는 Response Data나 Request Payload에 들어가는 데이터들의 타입을 명시하기 위해 시트를 따로 둡니다.
타입을 따로 명시함으로써 문서 본문을 좀 더 효율적으로 작성할 수 있습니다. 문서에 나올 데이터 타입들을 먼저 적어보겠습니다.
| Name | Description | Item |
|---|---|---|
| TContentPreview | 컨텐츠 요약 정보 | - idx: number - title: string - category: string - thumbnail: string - createdAt: string |
| TContent | 컨텐츠 상세 정보 | - idx: number - title: string - category: string - createdAt: string - content: string |
| TCntContents | 총 컨텐츠 수, 카테고리별 컨텐츠 수 집계 | - total: number - perCategory: [string, number][] |
| TCntLike | 총 따봉 수, 카테고리별 따봉 수 집계 | - total: number - perCategory: [string, number][] |
| TCategoryDetail | 카테고리 상세 정보 | - name: string - idx: number - priority: number (= 순서) |
| TVisitor | 각종 방문자 수 | - total: number - today: number - months: [string, number][] (= 순서) |
이제 한 화면씩 필요한 API를 작성합니다
| ID | Endpoint | Description | Request Payload | Response Data |
|---|---|---|---|---|
| MAIN_1 | GET /main | 방문자 수와 HOT 컨텐츠 | - | - cntToday: number - cntTotal: number - contentPreviewsByHit: TContentPreview[] - contentPreviewsByLike: TContentPreview[] |
| CATEGORY_1 | GET /categories | 카테고리 이름 목록 | - | - categories: string[] |
| ID | Endpoint | Description | Request Payload | Response Data |
|---|---|---|---|---|
| CONTENT_1 | GET /contents | 특정 카테고리에 해당하는 컨텐츠 목록 (요약 정보) | - category: string - offset: string - limit: string | - contentPreviews: TContentPreview[] |
+ CATEGORY_1 (카테고리 이름 목록)
중복 내용은 적지 않고 위처럼
+로 표기 하겠습니다
Request Payload의 offset과 limit는 무한 스크롤이나 페이징을 구현할 때 불가피한 데이터입니다. 서버로에게 "n번째 데이터부터 m개 줘" 라고 해야 하니까 n과 m을 의미하는 데이터를 보내야 하며, offset=n, limit=m입니다.
| ID | Endpoint | Description | Request Payload | Response Data |
|---|---|---|---|---|
| CONTENT_2 | GET /content | 특정 컨텐츠 상세 정보와 이전/다음 컨텐츠 링크 | - cid: string | - contentData: TContent - prevContentPreview: TContentPreview - nextContentPreview: TContentPreview |
| CONTENT_3 | POST /like | 특정 게시물에 따봉 | - cid: number | - code: number - msg: string |
+ CATEGORY_1 (카테고리 이름 목록)
CONTENT_2와 CONTENT_3의 Request Payload를 보면 똑같이 cid를 실어 보내는데 타입이 다릅니다.
GET 요청을 보내면 Request Payload들이 URL에 String형태로 따라 붙습니다. (예시. /content?cid=12)
number로 넣어봤자 string으로 보내질테니 GET 요청에서는 string으로 썼습니다.
| ID | Endpoint | Description | Request Payload | Response Data |
|---|---|---|---|---|
| MASTER_1 | POST /adm/auth | 관리자 인증 (로그인) | - k: string | - token: string OR null - code: number |
Request Payload의
k
관리자 패스워드입니다.
보통 로그인이라 하면 id, password를 입력하는데
여러 사용자 중 password가 겹치는 사용자가 있을 수 있습니다.
즉, 회원 테이블 내에서 password는 테이블의 row(회원)을 특정하는 컬럼이 될 수 없습니다.
id로 회원을 특정하고 그 회원의 패스워드와 입력 받은 패스워드를 비교하여 사용자를 인증합니다.
ID는 말그대로 Identifier(식별자)입니다. 지금 로그인하려는 이 사람이 뭐하는 사용자인지 식별하기 위한 값입니다.
그래서 제 관리자 사이트에는 아이디가 필요 없습니다. 로그인 대상은 애초에 저 한명이거든요.
그래서 Identifier없이 패스워드만 입력하고자 하며, 이름은 password말고 key라고 부르고 싶습니다. 근데 key도 또 줄여서 k라고만 쓰고 싶습니다.
| ID | Endpoint | Description | Request Payload | Response Data |
|---|---|---|---|---|
| MASTER_2 | GET /adm/dash | 월별 방문자 집계, 총 방문자 수 | - t: string | - code: number - cntVisitor: number - cntContents: TCntContents - cntLike: TCntLike |
Request Payload의
t
관리자 로그인 성공 시 응답 받는 세션유지용 토큰입니다.
컨텐츠 내용을 보는 화면은 이미 있으니 (관리자에는 없지만) 컨텐츠 제목들 출력, 수정, 삭제만 하고 싶습니다.
수정은 수정페이지로 내비게이팅하고, 출력을 위한 GET 요청, 삭제를 위한 POST 요청만 필요합니다.
| ID | Endpoint | Description | Request Payload | Response Data |
|---|---|---|---|---|
| CONTENT_4 | POST /adm/remove | 컨텐츠 삭제 | - cid: number - t: string | - code: number - msg: string |
+ CONTENT_1 (특정 카테고리에 해당하는 컨텐츠 목록)
+ CATEGORY_1 (카테고리 이름 목록)
컨텐츠에 이미지를 넣어야 하는데, 그 이미지는 서버에 따로 업로드 시킨 후 서버 컴퓨터에 저장된 경로를 받아와야 합니다. 받아온 경로를 Markdown 텍스트 안에 넣어줘야 하죠. 따라서 컨텐츠를 업로드하는 요청 외에도 이미지를 업로드하는 요청이 필요합니다.
컨텐츠 업로드 시 카테고리까지 지정해야겠죠? 카테고리 이름들을 가져와서 HTML Select태그에 넣어줌으로써 카테고리를 지정할 수 있도록 합시다.
근데 여기서 또 생각할 점.
컨텐츠를 다 작성했고 카테고리를 지정하려고 하는데 새로운 카테고리에 이 컨텐츠를 넣고 싶습니다. 근데 이미 있는 카테고리 안에서만 선택할 수 있으면 안되겠죠? 새로운 카테고리를 추가할 수 있게 해야합니다.
두 가지 방법이 생각나는데요.
1. 서버에 컨텐츠 등록 요청을 보낼 때 category값을 넣어 줄 건데, 서버에서는 이 카테고리가 새로운 놈이면 DB에 추가한 후에 컨텐츠를 저장합니다.
2. 화면에서 새로운 카테고리를 입력해서 서버에 새로운 카테고리가 추가되도록 요청을 보냅니다. 이후 등록 요청을 보내면 서버는 이미 DB에 존재하는 카테고리만 입력값으로 받는다고 판단합니다.
그러면 1번이 더 좋은 걸까요? 무작정 그렇다고 판단하긴 이릅니다.
1번으로 하면 '새로운 카테고리'말고 '기존 카테고리'에 컨텐츠를 추가하는 경우에도 쓸데없는 카테고리 검색 작업을 수행할 겁니다.
새로운 카테고리에 컨텐츠를 추가하는 빈도 vs 기존 카테고리에 추가하는 빈도
당연히 후자가 훨씬 많습니다. 고로, 기존 카테고리에 추가하는 케이스에 초점을 두고 판단해야 합니다.
2번으로 갑니다. = 새로운 카테고리로 컨텐츠를 등록할 거면, 서버에 카테고리를 추가하는 요청을 따로 보냅니다.
| ID | Endpoint | Description | Request Payload | Response Data |
|---|---|---|---|---|
| CONTENT_5 | POST /adm/content | 컨텐츠 등록 및 수정 | - cid (수정 시): number - t: string - content: string - category: string | - code: number - msg: string |
| CONTENT_6 | POST /adm/pic | 이미지 업로드 | - cid (수정 시): number - t: string - pic: string (base64) | - code: number - msg: string - path: string |
| CATEGORY_4 | POST /adm/add-category | 카테고리 추가 | - t: string - category: string | - code: number - msg: string |
+ CATEGORY_1 (카테고리 이름 목록)
+ CONTENT_2 (컨텐츠 상세 정보) (수정 시)
| ID | Endpoint | Description | Request Payload | Response Data |
|---|---|---|---|---|
| CATEGORY_2 | GET /adm/category | 전체 카테고리의 상세 정보 | - t: string | - categories: TCategoryDetail[] |
| CATEGORY_3 | POST /adm/category | 수정된 카테고리 반영 | - t: string - categories: TCategoryDetail[] | - code: number - msg: string |
다 썼습니다 이제.. 흩어져 있는 API 문서 내용들을 하나로 합쳐줍니다.
근데 그냥 ID 순으로 합치면 관리자용 API랑 일반사용자용 API랑 뒤죽박죽 섞이니까 일반사용자용 먼저 ID순으로 쭉 쓰고, 그 다음 관리자용 API를 ID순으로 쭉 쓰겠습니다.
| ID | Endpoint | Description | Request Payload | Response Data |
|---|---|---|---|---|
| MAIN_1 | GET /main | 방문자 수와 HOT 컨텐츠 | - | - cntToday: number - cntTotal: number - contentPreviewsByHit: TContentPreview[] - contentPreviewsByLike: TContentPreview[] |
| CATEGORY_1 | GET /categories | 카테고리 이름 목록 | - | - categories: string[] |
| CONTENT_1 | GET /contents | 특정 카테고리에 해당하는 컨텐츠 목록 (요약 정보) | - category: string - offset: string - limit: string | - contentPreviews: TContentPreview[] |
| CONTENT_2 | GET /content | 특정 컨텐츠 상세 정보와 이전/다음 컨텐츠 링크 | - cid: string | - contentData: TContent - prevContentPreview: TContentPreview - nextContentPreview: TContentPreview |
| CONTENT_3 | POST /like | 특정 게시물에 따봉 | - cid: number | - code: number - msg: string |
| CATEGORY_2 | GET /adm/category | 전체 카테고리의 상세 정보 | - t: string | - categories: TCategoryDetail[] |
| CATEGORY_3 | POST /adm/category | 수정된 카테고리 반영 | - t: string - categories: TCategoryDetail[] | - code: number - msg: string |
| CATEGORY_4 | POST /adm/add-category | 카테고리 추가 | - t: string - category: string | - code: number - msg: string |
| CONTENT_4 | POST /adm/remove | 컨텐츠 삭제 | - cid: number - t: string | - code: number - msg: string |
| CONTENT_5 | POST /adm/content | 컨텐츠 등록 및 수정 | - cid (수정 시): number - t: string - content: string - category: string | - code: number - msg: string |
| CONTENT_6 | POST /adm/pic | 이미지 업로드 | - cid (수정 시): number - t: string - pic: string (base64) | - code: number - msg: string - path: string |
| MASTER_1 | POST /adm/auth | 관리자 인증 (로그인) | - k: string | - token: string OR null - code: number |
| MASTER_2 | GET /adm/dash | 월별 방문자 집계, 총 방문자 수 | - t: string | - code: number - cntVisitor: TVisitor - cntContents: TCntContents - cntLike: TCntLike |
다음엔 DB를 설계합니다. 그리고 바로 서버 코딩할 텐데, 빨리 코딩하고 싶어요..
정말 잘 보고 가요