StatesAirline Server
CORS란?
Cross Origin Resource Sharing의 약자로, 시스템 수준에서 타 도메인 간 자원 호출을 승인하거나 차단하는 것을 결정하는 것이다. 예를들어, 나 너네 서버에 POST 요청해도 되니? 라는 요청을 먼저 보내고 해도 된다는 응답이 오면 그때 POST 요청을 보내는 것이다.
그럼 CORS가 왜 필요한 것인가?
다른 도메인에서의 자원을 호출하는 행위에 제한이 없으면 안전하지 않다. 그래서 서버가 허용한 클라이언트의 요청에만 응답하기 위해서 필요한 것이다.
서버가 CORS를 허용하기 위한 HTTP 헤더 각각의 목적을 이해해보자.
- Access-Control-Allow-Origin : 단일 출처를 지정해서 브라우저가 해당 출처가 리소스에 접근하도록 허용한다. "*" 처리를 해주면 origin에 상관없이 모든 리소스에 접근하도록 허용해준다.
- Access-Control-Allow-Methods : 리소스에 접근할 때 허용되는 메서드를 지정한다.
- Access-Control-Allow-Headers : 사용자 정의 헤더를 받을 수 있음을 나타내준다. 예를 들어, " 'Access-Control-Allow-Headers' : 'X-PINGOTHER, Content-Type' " 이렇게 작성이 되어있다면, 'X-PINGOTHER, Content-Type' HTTP 헤더를 사용할 수 있게 된다.
- Access-Control-Max-Age : prefilght request 요청 결과를 캐시할 수 있는 시간을 나타낸다. 다시 말해서 preflight request 유효기간을 설정해주는 것이다. 예를 들어 'Access-Control-Max-Age : 86400' 이라고 되어있으면, preflight request를 한번 보내고 허가를 받으면 86400초(24시간) 동안 preflight request를 굳이 보내지 않아도 리소스 요청을 할 수 있다.
Getting Started
1. statesairline
- statesairline/app.js 는 서비스에 필요한 미들웨어와 웹 서버를 실행하는 코드가 작성되어 있습니다.
2. router
- statesairline/router/ 안에는 airport API, book API, flight API 요청을 수행하는 라우터가 작성되어 있습니다. 작성된 라우터 내용을 통해 API 요청을 받을 수 있습니다.
3. controller
- statesairline/controller/ 안에는 정의된 API 요청을 수행하는 코드를 작성해야 합니다. ( airportController.js 에 작성된 내용을 참고하여 작성합니다. )
4. repository
- statesairline/repository/flightlist.js 는 서비스에서 제공하는 항공편 데이터가 작성되어 있습니다. 이 데이터로 서비스를 구현합니다.
- statesairline/repository/airportlist.js 는 서비스에서 제공하는 공항 데이터가 작성되어 있습니다.
5.test
- statesairline/__test__/statesairline.test.js 은 Jest 스펙을 가지고 있으며 코드 테스트를 위한 테스트 케이스가 작성되어 있습니다.
- npm install 을 통해서 package.json에 설정된 패키지를 설치하세요.
- npm start를 통해 서버를 실행하세요.
- Postman으로 API 동작 여부를 확인하세요.
- npm test를 통해 테스트를 실행하세요.
- 서버에서 사용되는 포트 번호로 실행되지 않는 경우, 관리자 권한을 부여하여 실행하거나 해당 포트를 사용 중인 프로그램을 종료 후 재시작을 합니다.
Bare Minimum Requirements
- statesairline/controller/flightController.js와 statesairline/controller/bookController.js 에 코드를 작성하세요.
- Express 공식문서에서 req.query , req.params를 사용하는 방법을 확인하세요. Query와 Params를 기준으로 데이터를 필터링하는 코드를 작성해야 합니다.
- 예약 데이터는 controller/bookController.js 안에 작성된 let booking = []; 배열에 저장해야 합니다.
- Flight API -> 항공편 수정은 Advanced Challenges입니다.
Advanced Challenges
Advanced 콘텐츠의 문제를 해결하려면, 충분한 컨텍스트가 필요할 수 있습니다. Bare minimum requirements 를 완료했다면 도전하세요.
생성한 데이터를 수정할 수 없다면, 매번 데이터를 지우고 다시 생성해야 합니다. 이런 불편을 서비스에서 제거하려면, 데이터를 수정할 수 있는 기능을 추가하면 됩니다.
Advanced 콘텐츠에서는 PUT 요청에 따라 항공편을 수정하는 기능을 구현합니다.
1. 목표
- Flight API -> 항공편 수정에서 정의한 API 요청을 수행하는 코드를 작성하세요.
- Advanced Challenges에 있는 모든 테스트를 통과해야 합니다.
2. 참고 내용
- flight.uuid는 변경되면 안 됩니다.
- flight JSON의 일부만 수정할 수 있어야 합니다.
Sprint 복기
States Airline 서버를 구현하는 과제이다. express프레임워크를 이용해서 만든 서버이고, 클라이언트 요청에 따라 항공편과 예약 데이터를 조회, 생성, 수정, 삭제하는 기능을 수행할 수 있어야 한다.
app.js
express 프레임워크를 사용해서 서버를 구현하였고 app.use를 통해 각각 flightRouter, bookRouter, airportRouter에 분기점을 만들어 주었다.
const express = require('express');
const cors = require('cors');
const app = express();
// 모든 서버는 요청을 받을수 있는 포트 번호를 필요로 합니다.
// HTTP server의 표준 포트는 보통 80 번 이지만, 보통 다른 서버에서 사용중이기 때문에 접근할 수 없습니다.
// 따라서 우리는 보통 테스트 서버 포트로 3000, 8080, 1337 등을 활용합니다.
// PORT는 아파트의 호수와도 같습니다. 서버로 요청을 받기 위해서는 다음과 같이 포트 번호를 설정 합니다.
// (* 때에 따라 다른 포트번호를 열고 싶다면, 환경 변수를 활용 하기도 합니다.)
const port = 3001;
const flightRouter = require('./router/flightRouter');
const bookRouter = require('./router/bookRouter');
const airportRouter = require('./router/airportRouter');
app.use(cors());
app.use(express.json());
app.use('/flight', flightRouter);
app.use('/book', bookRouter);
app.use('/airport', airportRouter);
app.get('/', (req, res) => {
res.status(200).send('Welcome, States Airline!');
});
app.use((req, res, next) => {
res.status(404).send('Not Found!');
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send({
message: 'Internal Server Error',
stacktrace: err.toString()
});
});
app.listen(port, () => {
console.log(`[RUN] StatesAirline Server... | http://localhost:${port}`);
});
module.exports = app;
Router.js
- 각 Router.js 파일에서는 Countroller에서 작성된 함수들을 가져와서 각 요청에 맞는 함수들을 시켜주고 있다.
결국 Controller 함수들이 Router.js에 불려와서 실행이되고, 이 라우터 들이 app.js에 불려와서 사용된다는 것을 알 수 있다. 그래서 app.js에 있는
app.use('/flight', flightRouter); 는
app.get('/flight', findAll), app.get('/flight/:id', findById), app.put('/flight/:id', update)을 포함하고 있다고 볼수 있고,
app.use('/book', bookRouter); 는
app.get('/book', findById), app.post('/book', create), app.delete('/book', deleteById)을 포함하고 있다고 볼수 있고,
app.use('/airport', airportRouter); 는
app.get('/airport', findAll)을 포함하고 있다고 볼수 있다.
flightController.js
const flights = require('../repository/flightList');
const fs = require('fs');
module.exports = {
// [GET] /flight
// 요청 된 파라미터 departure_times, arrival_times 값과 동일한 값을 가진 항공편 데이터를 조회합니다.
// 요청 된 파라미터 departure, destination 값과 동일한 값을 가진 항공편 데이터를 조회합니다.
findAll: async (req, res) => {
const { departure_times, arrival_times, destination, departure } = req.query;
// TODO:
if(req.query.departure_times && req.query.arrival_times){
let filtering1 = flights.filter( el => el.departure_times === req.query.departure_times && el.arrival_times === req.query.arrival_times)
return res.status(200).json(filtering1);
}
if(req.query.departure && req.query.destination){
let filtering2 = flights.filter(el => el.departure === req.query.departure && el.destination === req.query.destination)
return res.status(200).json(filtering2);
}
return res.json(flights);
},
// [GET] /flight/:uuid
// 요청 된 uuid 값과 동일한 uuid 값을 가진 항공편 데이터를 조회합니다.
findById: async (req, res) => {
const { uuid } = req.params;
// TODO:
if(req.params.id) {
let filter = flights.filter(el => el.uuid === req.params.id)
return res.status(200).json(filter);
}
return res.json(flights);
},
// Advanced
// [PUT] /flight/:uuid 요청을 수행합니다.
// 요청 된 uuid 값과 동일한 uuid 값을 가진 항공편 데이터를 요쳥 된 Body 데이터로 수정합니다.
update: (req, res) => {
const { uuid } = req.params;
const bodyData = req.body;
// TODO:
if (uuid) {
const list = flights.filter((item) => {
return item.uuid === uuid;
})
let obj = Object.assign(list[0], bodyData);
return res.json(obj);
}
}
};
bookController.js
// POST /book에서 사용할 uuid입니다.
const flights = require('../repository/flightList');
const { v4: uuid } = require('uuid');
// 항공편 예약 데이터를 저장합니다.
let booking = [];
module.exports = {
// [GET] /book 요청을 수행합니다.
// 전체 예약 데이터를 조회합니다.
findAll: (req, res) => {
return res.status(200).json(booking);
},
// [GET] /book/:phone 요청을 수행합니다.
// 요청 된 phone과 동일한 phone 예약 데이터를 조회합니다.
findByPhone: (req, res) => {
const {phone} = req.params;
let list = booking;
if(phone) {
list = list.filter((item) => {
return phone === item.phone;
})
return res.status(200).json(list);
}
return res.status(200).json(list)
},
// [GET] /book/:phone/:flight_uuid 요청을 수행합니다.
// 요청 된 id, phone과 동일한 uuid, phone 예약 데이터를 조회합니다.
findByPhoneAndFlightId: (req,res) => {
const {phone, flight_uuid} = req.params;
// TODO:
let list = booking;
if(flight_uuid){
list = list.filter((item) => {
return flight_uuid === item.flight_uuid;
})
res.status(200).json(list);
}
if(phone){
list = list.filter((item) => {
return phone=== item.phone;
})
res.status(200).json(list);
}
return res.status(200).json(list)
},
// [POST] /book 요청을 수행합니다.
// 요청 된 예약 데이터를 저장합니다.
create: (req, res) => {
// POST /book에서 사용할 booking_uuid입니다.
const booking_uuid = uuid();
// TODO:
booking.push(req.body)
return res.status(201).json({});
},
// Optional
// [DELETE] /book/:booking_uuid 요청을 수행합니다.
// 요청 된 id, phone 값과 동일한 예약 데이터를 삭제합니다.
deleteByBookingId: (req, res) => {
const {booking_uuid} = req.params;
// TODO:
if(req.query.phone && req.query.flight_uuid) {
booking = booking.filter(el => el.phone !== req.query.phone && el.flight_uuid !== req.query.flight_uuid)
return res.status(200).filter(booking);
}
}
};
추가.
app.use
"app.use"는 Node.js의 인기 있는 웹 애플리케이션 프레임워크인 Express.js의 메서드이다. "app.use" 메서드는 Express.js 응용 프로그램에 미들웨어 기능을 추가하는 데 사용됩니다.
미들웨어 기능은 요청 및 응답 개체에 액세스할 수 있는 기능으로, 요청 데이터 구문 분석, 응답 헤더 추가 또는 오류 트리거와 같은 작업을 수행할 수 있습니다. 미들웨어 기능은 응용 프로그램에 추가된 순서대로 실행되므로 "app.use"를 사용하여 특정 순서로 실행되는 여러 미들웨어 기능을 추가할 수 있습니다.
const express = require("express");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
console.log("Request received!");
next();
});
app.use("/", (req, res) => {
res.send("Hello, World!");
});
app.listen(3000, () => {
console.log("App listening on port 3000!");
});
위의 예에서 "app.use"는 express.json()과 express.urlencoded()라는 두 가지 미들웨어 기능을 추가하는 데 사용됩니다. 이러한 미들웨어 기능을 통해 애플리케이션은 각각 JSON 및 URL 인코딩 요청 데이터를 처리할 수 있습니다. 두 번째 app.use 메서드는 요청이 수신되면 콘솔에 메시지를 기록하고 마지막 app.use 메서드는 루트 경로에 대한 "GET" 요청을 처리합니다.