Section2 Unit10 [Web Server] ๊ธฐ์ด - StatesAirline Server
โญ๏ธ ๊ณผ์ . StatesAirline Server
โ๏ธ Bare Minimum Requirement
โ statesairline/controller/flightController.js์ statesairline/controller/bookController.js ์ ์ฝ๋๋ฅผ ์์ฑํ์ธ์.
โ Express ๊ณต์๋ฌธ์์์ req.query , req.params๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ํ์ธํ์ธ์. Query์ Params๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํํฐ๋งํ๋ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
โ ์์ฝ ๋ฐ์ดํฐ๋ controller/bookController.js ์์ ์์ฑ๋ let booking = []; ๋ฐฐ์ด์ ์ ์ฅํด์ผ ํฉ๋๋ค.
โ Flight API -> ํญ๊ณตํธ ์์ ์ Advanced Challenges์ ๋๋ค.
๊ตฌํ๊ณผ์ & ์ฝ๋
API ๋ฌธ์๋ฅผ ๋ณด๋ฉฐ Bare Minimum๊ณผ Advanced Challenges ๋ชจ๋ ๊ตฌํ ์๋ฃํ๋ค.
Node.js์ Express๋ฅผ ๊ณต๋ถํ๋ฉฐ ์ ์ ์ด ํ๋๋ ์์ด์ ๋ธ๋ก๊ทธ์ ๋ค๋ฅธ ๋ด์ฉ์ ์ ๋ฆฌ๋ฅผ ๋ชปํ์์ง๋ง, ์ด๋ฒ์ ๊ณผ์ ๋ฅผ ํตํด req.body, req.params, req.query ๊ณต๋ถํ๋ฉฐ ์ ๋ฆฌํด๋ณด์๋ค.
์๋ฒ ์ชฝ์ ๊ณต๋ถํด๋ณผ ์๊ฐ์กฐ์ฐจ ๋ชปํ์๋๋ฐ, ์ด๋ฒ ์ ๋์ ํตํด Node.js์ Express๋ฅผ ๋ค๋ค๋ณผ ์ ์์ด์ ๋คํ์ด๋ผ๋ ์๊ฐ์ด ๋ค์๋ค. (ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ ์๋ฒ๋ฅผ ๋ง๋ค์ค์ ์์์ผ ํ๋ค๊ณ ํ๋ค...๐ญ ํ ์ค ์๋ฉด ์ข์๊ฑด ๋น์ฐํ ์๊ฒ ์ง๋ง ์๊ฐ๋ณด๋ค ์ฌ๋ฐ์ด์ ๋ ๊ณต๋ถํด๋ณด๊ณ ์ถ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. )
๊ณผ์ ์์ ๋ฉ์ธ์ผ๋ก ๋ค๋ค์ผ ํ ์ฝ๋๋ flightController.js ์ bookController.js ์ด๋ค.
ํ์ง๋ง ์ด๋ฒ ๊ณผ์ ์์๋ ๊ด์ฌ์ฌ๋ถ๋ฆฌ๊ฐ ๋ ํด๋๊ตฌ์กฐ์ ๋๋จธ์ง ํ์ผ๋ค๋ ๋์ฌ๊ฒจ ๋ณด๋ฉด ์ข์ ๊ฒ ๊ฐ์์ ๋๋จธ์ง ์ฝ๋๋ ํจ๊ป ์ดํด๋ณด์์๋ค.
โญ ๊ณผ์ ์ ์ถ ํ ๋ ํผ๋ฐ์ค ์ฝ๋๋ฅผ ๋ณด๊ณ ๊ณต๋ถ๋ฅผ ํ๋ฉฐ ๋น๊ตํด๋ณด๊ณ , ์ผ๋ถ ์์ ์ ์ถ๊ฐํ๋ค.
1. ๊ฐ ํจ์๋ง๋ค ํ์ํ ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํ์ฌ ์ ๋ฌ๋ฐ์ ๊ฐ์ด ์ ํจํ ๊ฐ์ธ์ง ํ์ธํ๋ ์์ ์ ์ถ๊ฐํ์๊ณ
2. ์์ฒญ ํ ์๋ต์ ์ํ์ฝ๋ ๋ํ ์ ์ ํ ๊ฐ์ผ๋ก ์ ๋ฌํด ์ฃผ์๋ค.
์ ํจ์ฑ ๊ฒ์ฌ๋ก ๋ ๋ช ํํ ๊ฒ์ฌ๋ฅผ ์คํํ์ฌ ๋ค์ํ ์ผ์ด์ค๋ฅผ ๊ตฌ๋ถํ ์ ์์๊ณ , ์ฑ๊ณตํ ์์ฒญ ๋ฟ๋ง ์๋๋ผ ์ ํจํ์ง ์์ ์์ฒญ์ ๋ํ ์ํ์ฝ๋ ๋ํ ์ ๋ฌํ ์ ์์๋ค.
์๋๋ flightController.js ์ bookController.js์ ๋ด๊ฐ ์์ฑํ๋ ์ฝ๋์ ๋ ํผ๋ฐ์ค ์ฝ๋๋ฅผ ๋น๊ตํ์ฌ ๋ค์ ์ ๋ฆฌํ ์ฝ๋ ๐
๐ฉ๐ป๐ป flightController.js
const flights = require('../repository/flightList');
module.exports = {
// [GET] /flight
findAll: (req, res) => {
const { departure_times, arrival_times, destination, departure } = req.query;
/** ์ ํจํ Airport Code์ธ์ง ํ์ธ
* /^[A-Z]{3}$/ ์ธ ๊ฐ์ ์ฐ์๋ ๋๋ฌธ์ ์ํ๋ฒณ
* .test() ์ ๊ท ํํ์ ํจํด์ ๋ํ ๋ฌธ์์ด ๋งค์นญ ํ
์คํธ๋ฅผ ์ํํ๋ JS ๋ฉ์๋ (true ๋๋ false ๋ฐํ)
*/
const isValidAirportCode = (code) => {
return typeof code === 'string' && code.length === 3 && /^[A-Z]{3}$/.test(code)
}
/** ์ ํจํ ๋ ์ง์ธ์ง ํ์ธ
* date.toString() === 'Invalid Date' : Date ๊ฐ์ฒด ์ ํจ์ฑ ๊ฒ์ฌ (์ ํจํ์ง ์์ ๋ ์ง์ผ ๊ฒฝ์ฐ Invalid Date ๋ฌธ์์ด์ ๋ฐํํ๋ค.)
* date.toISOString() !== timestamp : ์ ํจํ ๋ ์ง/์๊ฐ ๊ฐ์ธ์ง ๊ฒ์ฌ
*/
const isValidDate = (timestamp) => {
if (!timestamp) {
return false;
}
const date = new Date(timestamp);
return date.toString() === 'Invalid Date' || date.toISOString() !== timestamp;
}
/** TODO
* ์์ฒญ ๋ ํ๋ผ๋ฏธํฐ departure_times, arrival_times, departure, destination ๊ฐ๊ณผ ๋์ผํ ๊ฐ์ ๊ฐ์ง ํญ๊ณตํธ ๋ฐ์ดํฐ ์กฐํ
*/
// ์์ฒญ๋ ์ฟผ๋ฆฌ๊ฐ ์์ ๊ฒฝ์ฐ ์ ์ฒด ํญ๊ณตํธ ์ถ๋ ฅ
if (Object.keys(req.query).length === 0) return res.json(flights);
// departure_times, arrival_times
// Ex. http://localhost:3001/flight?departure_times=2021-12-02T12:00:00&arrival_times=2021-12-03T12:00:00
else if (isValidDate(departure_times) && isValidDate(arrival_times)) {
console.log("test")
const filteredData = flights.filter (
(flight) => flight.departure_times === departure_times && flight.arrival_times === arrival_times
)
return res.json(filteredData);
}
// departure, destination
// EX. http://localhost:3001/flight?departure=ICN&destination=CJU
else if (isValidAirportCode(departure) && isValidAirportCode(destination)) {
const filteredData = flights.filter (
(flight) => flight.departure === departure && flight.destination === destination
)
return res.json(filteredData);
}
// departure๋ง ์ ๋ฌํ ๊ฒฝ์ฐ
// EX. http://localhost:3001/flight?departure=ICN
else if (isValidAirportCode(departure)) {
const filteredData = flights.filter (
(flight) => flight.departure === departure
)
return res.json(filteredData);
}
// ๊ทธ ์ธ์ ๊ฒฝ์ฐ
else {
return res.status(400).json('Incorrect request');
}
},
// [GET] /flight/:uuid
// TODO: ์์ฒญ ๋ uuid ๊ฐ๊ณผ ๋์ผํ uuid ๊ฐ์ ๊ฐ์ง ํญ๊ณตํธ ๋ฐ์ดํฐ๋ฅผ ์กฐํํฉ๋๋ค.
// Ex. http://localhost:3001/flight/af6fa55c-da65-47dd-af23-578fdba40bed
findById: (req, res) => {
console.log(req.params) // { uuid: 'af6fa55c-da65-47dd-af23-578fdba40bed' }
const { uuid } = req.params;
const filteredData = flights.filter((flight) => flight.uuid === uuid);
// ์ ํจํ์ง ์์ uuid์ ๊ฒฝ์ฐ 404
if (filteredData.length) {
return res.json(filteredData);
} else {
return res.status(404).json('Not Found');
}
},
// Advanced
// [PUT] /flight/:uuid ์์ฒญ์ ์ํํฉ๋๋ค.
// TODO: ์์ฒญ ๋ uuid ๊ฐ๊ณผ ๋์ผํ uuid ๊ฐ์ ๊ฐ์ง ํญ๊ณตํธ ๋ฐ์ดํฐ๋ฅผ ์์ณฅ ๋ Body ๋ฐ์ดํฐ๋ก ์์ ํฉ๋๋ค.
/* ๊ธฐ์กด data
[{
"uuid": "af6fa55c-da65-47dd-af23-578fdba40bed",
"departure": "ICN",
"destination": "CJU",
"departure_times": "2021-12-02T12:00:00",
"arrival_times": "2021-12-03T12:00:00"
}]
*/
update: (req, res) => {
const { uuid } = req.params;
const bodyData = req.body;
/*
// -----------------------------------------
// 1. ๋ด๊ฐ ์์ฑํ๋ ์ฝ๋
const filteredData = flights.filter((flight) => flight.uuid === uuid);
// Object.assign() bodyData์ ์์ฑ์ ๋ณต์ฌํด filteredData[0]์ ๋ฐ์ํ ํ ๋ฐํํ๋ค.
const updatedData = Object.assign(filteredData[0], bodyData);
// -----------------------------------------
*/
// -----------------------------------------
// 2. ๋ ํผ๋ฐ์ค ์ฝ๋
// findIndex() ์ ๋ฌ๋ฐ์ ํจ์๋ฅผ ๋ง์กฑํ๋ ๋ฐฐ์ด์ ์ฒซ๋ฒ์งธ ์์์ ์ธ๋ฑ์ค๋ฅผ ๋ฐํ. ๋ง์กฑํ๋ ๊ฐ์ด ์์ผ๋ฉด -1์ ๋ฐํ.
const updatedIdx = flights.findIndex((flight) => flight.uuid === uuid);
if (updatedIdx === -1) {
return res.status(404).json('Not Found')
}
const updatedData = { ...flights[updatedIdx], ...bodyData }
console.log(updatedData)
// splice() ์ธ๋ฑ์ค updatedIdx ๋ถํฐ, 1๊ฐ์ ์์๋ฅผ ์ ๊ฑฐํ๊ณ , updatedData ์ฝ์
// ์ฆ, ๊ธฐ์กด ๋ฐ์ดํฐ๋ฅผ ์ ๊ฑฐํ๊ณ , ์๋ก์ด ๋ฐ์ดํฐ์ธ updatedData ์ฝ์
flights.splice(updatedIdx, 1, updatedData)
// -----------------------------------------
res.status(200).json(updatedData)
}
};
๐ฉ๐ป๐ป bookController.js
// POST /book์์ ์ฌ์ฉํ uuid์
๋๋ค.
const { v4: uuid } = require('uuid');
// ํญ๊ณตํธ ์์ฝ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค.
// booking์ ๋๋ฏธ ๋ฐ์ดํฐ ์ถ๊ฐ (ํ
์คํธ์ฉ)
let booking = [
{
"booking_uuid": "1c69bd78-9404-4138-9e01-9b66db9d65ff",
"flight_uuid": "af6fa55c-da65-47dd-af23-578fdba40bed",
"name": "ํ๋",
"phone": "010-1234-5678",
}
];
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;
if (phone) {
const filteredData = booking.filter (
(book) => book.phone === phone
)
return res.status(200).json(filteredData)
}
},
// [GET] /book/:phone/:flight_uuid ์์ฒญ์ ์ํํฉ๋๋ค.
// TODO: ์์ฒญ ๋ id, phone๊ณผ ๋์ผํ uuid, phone ์์ฝ ๋ฐ์ดํฐ๋ฅผ ์กฐํํฉ๋๋ค.
findByPhoneAndFlightId: (req,res) => {
const { phone, flight_uuid } = req.params;
const filteredData = booking.filter (
(book) => book.phone === phone && book.flight_uuid === flight_uuid
)
return res.status(200).json(filteredData)
},
// [POST] /book ์์ฒญ์ ์ํํฉ๋๋ค.
// TODO: ์์ฒญ ๋ ์์ฝ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค.
create: (req, res) => {
// POST /book์์ ์ฌ์ฉํ booking_uuid์
๋๋ค.
const booking_uuid = uuid();
/*
// -----------------------------------------
// 1. ๋ด๊ฐ ์์ฑํ๋ ์ฝ๋
req.body.booking_uuid = booking_uuid;
booking.push(req.body);
return res.status(201).json(booking)
// -----------------------------------------
*/
// -----------------------------------------
// 2. ๋ ํผ๋ฐ์ค ์ฝ๋
const { flight_uuid, name, phone } = req.body;
if (booking.find ((book) => book.phone === phone && book.flight_uuid === flight_uuid)) {
return res.status(409).json("It's already booked"); // 409 Conflict (์ถฉ๋)
} else {
const newBooking = { booking_uuid, flight_uuid, name, phone }
booking.unshift(newBooking); // ๊ฐ์ฅ ์์ชฝ์ผ๋ก ์ ๋ ฌ
// res.location์ HTTP ์๋ต์ Location ํค๋๋ฅผ ์ค์ ํ๋ ๋ฉ์๋๋ก ํด๋ผ์ด์ธํธ์๊ฒ ๋ฆฌ๋ค์ด๋ ์
์ ์ง์ํ๊ธฐ ์ํด ์ฌ์ฉ
res.location(`/book/${booking_uuid}`);
return res.status(201).json(booking[0])
}
// -----------------------------------------
},
// Optional
// [DELETE] /book/:booking_uuid ์์ฒญ์ ์ํํฉ๋๋ค.
// TODO: ์์ฒญ ๋ id ๊ฐ๊ณผ ๋์ผํ ์์ฝ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํฉ๋๋ค.
deleteByBookingId: (req, res) => {
const { booking_uuid } = req.params;
booking = booking.filter((book) => book.booking_uuid !== booking_uuid);
return res.status(204).json("No Content")
// 204 No Content
// 204 ์ํ์ฝ๋๋ ์์ฒญ์ด ์ฑ๊ณตํ์ผ๋, ์ปจํ
์ธ ๋ฅผ ์ ๊ณตํ์ง ์๋๋ค. (HTTP Response body๊ฐ ์กด์ฌํ์ง ์์)
// → ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ "No Content"๋ ์ถ๋ ฅ๋์ง ์๋๋ค.
}
};
๐ฌ
๋ ํผ๋ฐ์ค ์ฝ๋๋ฅผ ๋ณด๋ฉฐ ๊ธฐ์กด์ ์๊ณ ์๋ ๋ด์ฉ์ ํ์คํํ๊ณ , ํ์ฉํ๋ ๋ฐฉ๋ฒ๊ณผ ๋ ๋์ ์ฝ๋ ์์ฑ๋ฒ(์ ์ ํ status๋ฅผ ๋ณด๋ด์ฃผ๋ ๊ฒ)์ ๋ฐฐ์ธ ์ ์์๊ณ ,
๋ชฐ๋๋ ๋ถ๋ถ(res.location์ด๋, ํนํ 204๋ก status๋ฅผ ๋ณด๋ด๋ฉด ๋ณธ๋ฌธ์ ์ ๊ณตํ์ง ์๋๋ค๋ ๋๋ผ์ด ์ ๋...)๋ค๋ ๋ฐฐ์ธ ์ ์์๋ค.
๐ ์ค๋์ ํ๊ณ
API ๋ฌธ์๋ฅผ ๋ณด๋ฉฐ StatesAirline๋ ์๋ฒ ๊ตฌํ์ ํ๋ค. node.js์ express๋ฅผ ์ ๊น ํ์ตํ ํ์ ํ๋ ์์ ์ธ๋ฐ๋ค๊ฐ ๋ฐ๊ทธ๋ฆผ์ ์ฝ๋์คํ ์ด์ธ ๊ธฐ์กด ์ฝ๋์ ์ด๋ฏธ ๋ค ์์ฑ์ด ๋์ด ์์ด์ ์ด๋ฒ ๊ณผ์ ๋ ๊ทธ๋๋ง ์ข ์์ํ๋ ๊ฒ ๊ฐ๋ค. ์ด๋ฒ ์ ๋๋์ ํ์ด ํ๋์ ํ๋ฉฐ ๋ด๊ฐ ๋ช ํํ ์์ง ๋ชปํ๊ณ ์์ฑํ๋ ์ฝ๋๋ค๊ณผ ์ด๋ก , ๊ฐ๋ ๋ค์ด ์ ๋ง ๋ง๋ค๋ ๊ฒ๋ ๊นจ๋ซ๊ณ , ํ์ด์๊ฒ์๋ ๋ง์ ๊ฑธ ๋ฐฐ์ ๋ค. ์ข ๋ ํ์คํ๊ฒ ์๊ณ ๋์ด๊ฐ์ผ ํ ๋ถ๋ถ๋ค์ ๋ค์ ์๊ฐํด๋ด์ผ ๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.
์ ๊ท ์์ ์ด ๋๋๊ณ ์คํฐ๋ ์ฃผ์ (? ์ ํ์ด ๋ฌด์์ ์ํ ๊ฑด์ง๋ ์ ๋ชจ๋ฅด๊ฒ ์ง๋ง)์๋ movie app server ๋ง๋ค๊ธฐ ๊ณผ์ ๋ ์๋ฃํ๊ณ , ๊ฐ์๋ฅผ ๋ค์๋ค. ๊ณผ์ ์์ฒด์์ ๊ตฌํ์ ์๊ตฌํ ์ฝ๋๋ ์ด๋ฏธ ์ค๋ ํ๋ StatesAirline์ ๋์๋ ์ฝ๋์ ๋น์ทํ ์ ํ์ด๋ผ ์ด๋ ต์ง๋ ์์๋๋ฐ, ๊ทธ ํ์ ๊ด์ฌ์ฌ๋ถ๋ฆฌ๋ฅผ ์ํด ํ๋์ ํ์ผ์ ์์ฑ๋์ด ์๋ ์ฝ๋๋ฅผ routes, controller ํด๋์ ๊ฐ๊ฐ ๋๋์ด ๋ถ๋ฆฌ์์ผ์ค ๋ถ๋ถ์ด ํฅ๋ฏธ๋ก์ ๋ค. StatesAirline์์๋ ์ ๊ณต๋ฐ์ ์ฝ๋๊ฐ ์ด๋ฏธ ๊ด์ฌ์ฌ๋ถ๋ฆฌ๊ฐ ๋์ด์์๊ธฐ ๋๋ฌธ์ ์๊ฐํ ๊ธฐํ๊ฐ ์์๋๋ฐ, ์ด๋ฒ ๊ณผ์ ๋ก ๊ด์ฌ์ฌ๋ถ๋ฆฌ๋ฅผ ์ํด ์์ฑ๋ ์ฝ๋๋ฅผ ์ผ๋ถ ์์ ํ๊ณ , ์ฎ๊ธฐ๋ ์์ ์ ํด ๋ณธ ๊ฒ์ด ์ฌ๋ฏธ์์๋ค๋ ์๊ฐ์ ํ๋ค. ๐ 4์ฃผ์ฐจ ์ค์ต๊น์ง ๋๋๋ฉด ๋ธ๋ก๊ทธ์๋ ์ ๋ฆฌ ํ ๋ฒ ํด๋ณด๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.