자원들은 각각의 독립적인 인터페이스를 가지며 각각의 자원들이 URI 자원 식별, 표현을 통한 자원조작, Self-descriptive messages, HATEOAS 구조를 가지는 것을 말함
URI 자원 식별
표현을 통한 자원 조작
Self-descriptive messages
.json
을 반환한다면 application/json으로 명시해주어야 함font/ttf
, text/plain
, text/csv
)const express = require("express");
const app = express();
app.get("/good", (req, res) => {
res.status(200).json({ a: 1 });
});
app.get("/bad", (req, res) => {
res.setHeader("content-type", "application/json");
res.send("leesfact");
});
const server = app.listen(12010, () => {
console.log("오늘도 재밌는 CS공부서버 ~ : http://127.0.0.1:12010/good");
});
잘못된 부분
res.send("leesfact")
수정된 부분
app.get("/bad", (req, res) => {
res.status(200).json({ message: "leesfact" });
});
application/json
으로 설정했지만, 실제로 보내는 데이터는 유효한 JSON 형식이 아님, "leesfact"는 단순한 문자열이므로 JSON 파싱 오류가 발생_links
속성에 담기도 함, 링크를 유추할 수 있는 변수명을 쓰면 됨)const express = require("express");
const app = express();
const books = [
{ id: 1, title: "자바스크립트 풀스택 MEVN", author: "아무개1" },
{ id: 2, title: "CS지식노트", author: "아무개2" },
{ id: 3, title: "자바스크립트 어나더레벨", author: "아무개3" },
];
app.get("/books", (req, res) => {
const bookCollection = books.map((book) => ({
...book,
links: [{ rel: "self", href: `/books/${book.id}` }],
}));
res.status(200).json(bookCollection);
});
app.get("/books/:id", (req, res) => {
const bookId = parseInt(req.params.id);
const book = books.find((book) => book.id === bookId);
if (book) {
book.links = [{ rel: "self", href: `/books/${book.id}` }];
res.status(200).json(book);
} else {
res.status(404).json({ error: "Book not found" });
}
});
app.listen(12010, () => {
console.log("책서버 시작 : http://127.0.0.1:12010/books");
});
참고
rel : relation으로 링크와 요청한 자원과의 관계를 나타냄
self : 해당 자원에 대한 링크를 나타냄
3 Cacheable
HTTP는 아무런 로직을 구현하지 않더라도 자동적으로 캐싱이 됨
이는 HTTP 메서드 중 GET에 한정되며 Cache-Control:max-age=100
(100초) 이런 식으로 한정된 시간을 정할 수 있으며 캐싱된 데이터가 유효한지를 판단하기 위해 Last-modified
와 Etag
라는 헤더값을 씀
Etag
는 전달되는 값에 태그를 붙여서 캐싱되는 자원인지를 확인해주는 것참고
HTTP 헤더의 Cache-control = no-store로 하게 되면 캐싱이 안됨, 기본적으로 cache-control = public으로 되어있기 때문에 캐싱이 되며 HTTP header를 기반으로 캐싱 컨트롤을 하는게 중요함
/books/delete/1
이렇게 표기하면 안됨유저가 책을 소유한다 라는 것을 표현한다면
→ 유저/유저아이디/inclusion/책/책아이디
유저가 소유한 아파트를 조회한다 를 표현한다면
→ /users/{userid}/aparts
또한 /getAllUsers 식의 동사를 집어넣으면 안됨
계층적인 내용을 담고 있어야 함
대문자가 아닌 소문자로만 쓰며 너무 길 경우에 바를 써야 할 경우 언더바_
가 아닌 일반적인 바-
를 씀
HTTP 응답 상태코드를 적재적소에 활용해야 함
app.get('/books')
// 모든 책을 조회합니다.
app.post('/books/:booksid')
// 책을 생성합니다.
app.put('/books/:booksid')
// 책을 수정합니다.
app.get('/books/:booksid')
// 특정 책을 조회합니다.
app.put('/users/:userid/books/:booksid')
// 어떤 유저가 특정 책을 빌립니다.
app.patch('/users/:userid/books/:booksid')
// 어떤 유저가 특정 책을 빌립니다.
/
를 기반으로만 구축되는 것도 있지만 적절히 쿼리스트링을 혼재해서 쓰기도 함실제 워드프레스에서 제공하는 REST API
posts의 2번째 결과물을 나타냄
/wp/v2/posts?page=2
[참고] api를 설정할 때 /v2, /v1으로 버전을 명시해 놓는게 좋음, 이를 통해 현재 버전을
사용하다가 새로운 버전이 안정화되면 자체적으로 마이그레이션할 수 있음
실제 KAKAO API
/oauth/token?grant_type=refresh_token&client_id=${REST_API_KEY}
바벨이 필요한 운동정보를 가져온다.
/api/v1/workouts?equipment=barbell
모든 운동정보를 생성날짜 기준으로 내림차순으로 가져온다면
/api/v1/workouts?sort=-createdAt