API 연동
프론트엔드에서는 서버와 데이터 주고 받기 위해 ajax
를 사용한다. 보통은 api 서버를 어딘가에 띄우고(혹은 로컬 호스트에 띄우고) 프론트 서버와 함께 개발한다. 개발 환경에서 이러한 api 서버 구성을 어떻게 하는지 알아 보자.
목업 API 1: devServer.before
웹팩 개발 서버 설정 중 before 속성
은 웹팩 서버에 기능을 추가할 수 있는 여지를 제공
한다. 이것을 이해하려면 노드 Express.js
에 사전지식이 있으면 유리한데, 간단히 말하면 익스프레스는 미들웨어 형태로 서버 기능을 확장할 수 있는 웹프레임워크다.
devServer.before에 추가하는 것이 바로 미들웨어인 셈이다. 아래 코드를 보자.
- webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module.exports = {
devServer: {
before: (app) => {
app.get("/api/users", (req, res) => {
res.json([
{
id: 1,
name: "Alice"
},
{
id: 2,
name: "Bek"
},
{
id: 3,
name: "Chris"
},
])
})
},
},
}
before에 설정한 미들웨어는 익스프레스에 의해서 app 객체가 인자로 전달되는데 Express 인스턴스다. 이 객체에 라우트 컨트롤러를 추가할 수 있는데 app.get(url, controller) 형태로 함수를 작성한다. 컨트롤러에서는 요청 req과 응답 res 객체를 받는데 여기서는 res.json() 함수로 응답하는 코드를 만들었다.
웹팩 개발 서버는 GET /api/users
요청시 3개의 엔트리를 담은 배열을 반환할 것이다. 서버를 다시 구동하고 Dom 로드시 ajax 요청을 해서 데이터를 받아오도록 변경해보자.
먼저 ajax 라이브러리인 axios를 설치한자.
1
npm i axios
그리고 app.js 를 아래와 같이 바꿔보자.
- app.js
1
2
3
4
5
6
7
8
9
10
11
12
import axios from "axios";
document.addEventListener("DOMContentLoaded", async () => {
const res = await axios.get("/api/users");
console.log(res);
document.body.innerHTML = (res.data || [])
.map(user => {
return `<div>${user.id}: ${user.name}</div>`;
})
.join("");
});
기존에는 data에 데이터를 관리했는데 이제는 ajax 호출 후 응답된 데이터를 반환하도록 변경했다. 화면을 확인해 보면 웹펙에서 설정한 api 응답이 화면에 나오는걸 확인할 수 있다.
목업 API 2: connect-api-mocker
목업 api 작업이 많을때는 connect-api-mocker
라이브러리의 도움을 받자. 특정 목업 폴더를 만들어 api 응답을 담은 파일을 저장한 뒤, 이 폴더를 api로 제공해주는 기능을 한다.
먼저 이 패키지를 설치하고,
1
npm i -D connect-api-mocker
mocks/api/keywords/GET.json
경로에 API 응답 파일을 만든다.
GET 메소드를 사용하기때문에 GET.json
으로 파일을 만들었다.(물론 POST, PUT, DELETE 도 지원)
- GET.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[
{
"id": 1,
"name": "Alice"
},
{
"id": 2,
"name": "Bek"
},
{
"id": 3,
"name": "Chris"
},
{
"id": 4,
"name": "Daniel"
}
]
기존에 설정한 목업 응답 컨트롤러를 제거하고 connect-api-mocker
로 미들웨어를 대신한다.
- webpack.config.js
1
2
3
4
5
6
7
8
9
const apiMocker = require("connect-api-mocker")
module.exports = {
devServer: {
before: (app) => {
app.use(apiMocker("/api", "mocks/api"))
},
},
}
익스프레스 객체인 app은 get() 메소드 뿐만 아니라 미들웨어 추가를 위한 범용 메소드 use()를 제공하는데, 이를 이용해 목업 미들웨어를 추가했다.
첫번째 인자
는 설정할 라우팅 경로
인데 /api로 들어온 요청에 대해 처리하겠다는 의미다.
두번째 인자
는 응답으로 제공할 목업 파일 경로
인데 방금 만든 mocks/api 경로를 전달했다.
목업 API 갯수가 많다면 직접 컨트롤러를 작성하는 것 보다 목업 파일로 관리하는 것을 추천한다.
실제 API 연동: devServer.proxy
이번에는 api 서버를 로컬환경에서 띄운 다음 목업이 아닌 이 서버에 직접 api 요청을 해보자. 로컬호스트 8081 포트에 아래와 같이 서버가 구성되었다고 가정하겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ curl localhost:8081/api/users
[
{
"id": 1,
"name": "Alice"
},
{
"id": 2,
"name": "Bek"
},
{
"id": 3,
"name": "Chris"
},
{
"id": 4,
"name": "Daniel"
}
]
ajax 요청 부분의 코드를 변경한다.
- src/app.js
1
2
3
4
5
6
7
8
9
const model = {
async get() {
// const result = await axios.get('/api/users');
// 직접 api 서버로 요청한다.
const { data } = await axios.get("http://localhost:8081/api/users")
return data
},
}
웹팩 개발서버를 띄우고 화면을 확인해 보자. 잘 나오는가? 브라우져 개발자 도구에 보면 다음과 같은 오류 메세지가 출력된다.
출처: https://jeonghwan-kim.github.io/series/2020/01/02/frontend-dev-env-webpack-intermediate.html
localhost:8080에서 localhost:8081 로 ajax 호출을 하지 못하는데 이유는 CORS 정책
때문이라는 메세지다. 요청하는 리소스에 "Access-Control-Allow-Origin"
헤더가 없다는 말도 한다.
CORS(Cross Origin Resource Shaing) 브라우져와 서버간의 보안상의 정책인데 브라우저가 최초로 접속한 서버에서만 ajax 요청을 할 수 있다는 내용이다. 방금같은 경우는 localhost로 같은 도메인이지만 포트번호가 8080, 8081로 달라서 다른 서버로 인식하는 것이다.
해결하는 방법은 두 가지(서버측에서 해결, 프론트측에서 해결)이다.
서버측에서 해결하는 방법은 다음과 같다. 해당 api 응답 헤더에 "Access-Control-Allow-Origiin: *" 헤더를 추가
한 뒤 응답하면, 브라우져에서 응답 데이터를 받을 수 있다.
- server/index.js (노드 서버 코드)
1
2
3
4
app.get("/api/users", (req, res) => {
res.header("Access-Control-Allow-Origin", "*") // 헤더를 추가한다
res.json(keywords)
})
한편 프론트엔드 측 해결방법을 보자. 서버 응답 헤더를 추가할 필요없이 웹팩 개발 서버에서 api 서버로 프록싱
하는 것이다. 웹팩 개발 서버는 proxy 속성으로 이를 지원한다.
- webpack.config.js
1
2
3
4
5
6
7
module.exports = {
devServer: {
proxy: {
"/api": "http://localhost:8081", // 프록시
},
},
}
개발서버에 들어온 모든 http 요청중 /api로 시작되는것은 http://localhost:8081로 요청하는 설정이다. api 호출코드를 다시 복구한 뒤,
- src/app.js
1
2
3
4
5
6
7
8
const model = {
async get() {
// const { data } = await axios.get('http://localhost:8081/api/users');
const { data } = await axios.get("/api/users")
return data
},
}
확인해보면 정상 동작하게 될 것이다.
Note: 강의에서는 실습을 진행하지 않는다.