100일 코딩챌린지 - Node.js로 백엔드 개발하기(with Express) 2

하파타카·2023년 6월 20일
0

node.js에 HTML파일 포함하기

const fs = require('fs');
const path = require('path');

const express = require('express');

const app = express();

app.use(express.static('public'));
app.use(express.urlencoded({ extended: false }));

app.get('/', function (req, res) {
  res.render('index');
});

app.get('/', function (req, res) {
  const htmlFilePath = path.join(__dirname, 'views', 'index.html');
  res.sendFile(htmlFilePath);
});

app.get('/recommend', function (req, res) {
  const htmlFilePath = path.join(__dirname, 'views', 'recommend');
  res.sendFile(htmlFilePath);

app.get('/confirm', function (req, res) {
  const htmlFilePath = path.join(__dirname, 'views', 'confirm.html');
  res.sendFile(htmlFilePath);
});

app.get('/about', function (req, res) {
  const htmlFilePath = path.join(__dirname, 'views', 'about.html');
  res.sendFile(htmlFilePath);
});

app.listen(3000);

이후 index.html, recommend.html, confirm.html, about.html파일의 a태그의 링크도 파일경로가 아닌 /confirm /about 처럼 주소형식으로 바꿔준다.

ejs 템플릿 엔진

참고링크 - ejs 사용설명서
ejs란 Javascript가 내장된 html파일이다.
아마 jsp와 유사한 느낌으로 이해하면 될 것 같다.

1) 터미널에서 npm install ejs 을 입력하여 ejs를 설치.
2) html파일의 확장자를 모두 .ejs로 변경
3) js파일 코드 수정
- app.js -

const fs = require('fs');
const path = require('path');

const express = require('express');

const app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(express.static('public'));
app.use(express.urlencoded({ extended: false }));

app.get('/', function (req, res) {
  res.render('index');
});

app.get('/restaurants', function (req, res) {
  res.render('restaurants');
});

app.get('/recommend', function (req, res) {
  res.render('recommend');
});

app.post('/recommend', function (req, res) {
  const restaurant = req.body;
  const filePath = path.join(__dirname, 'data', 'restaurants.json');

  const fileData = fs.readFileSync(filePath);
  const storedRestaurants = JSON.parse(fileData);

  storedRestaurants.push(restaurant);

  fs.writeFileSync(filePath, JSON.stringify(storedRestaurants));

  res.redirect('/confirm');
});

app.get('/confirm', function (req, res) {
  res.render('confirm');
});

app.get('/about', function (req, res) {
  res.render('about');
});

app.listen(3000);

여기서 app.post('/recommend', function (req, res)부분은 post로 데이터를 받아와 json에 저장하는 역할을 하는 부분이므로 이 부분은 코드를 변경하지 않음.

json에서 읽어온 데이터를 화면에 보여주기

json파일에서 읽어온 레스토랑 목록을 restaurants.ejs에서 보여주는 예제.

ejs에서 반복문 사용하기

- restaurants.ejs -

<h1>Recommended restaurants</h1>
<p>Find your next favorite restaurants with help of our other users!</p>
<p>We found <%= numberOfRestaurants %> restaurants.</p>
<ul id="restaurants-list">
  <% for (const restaurant of restaurants) { %>
  <li class="restaurant-item">
    <article>
      <h2><%= restaurant.name %></h2>
      <div class="restaurant-meta">
        <p><%= restaurant.cuisine %></p>
        <p><%= restaurant.address %></p>
      </div>
      <p><%= restaurant.description %></p>
      <div class="restaurant-actions">
        <a href="<%= restaurant.website %>">View Website</a>
      </div>
    </article>
  </li>
  <% } %>
</ul>

json에 저장된 레스토랑 데이터를 받아와 반복문을 이용해 li마다 할당하여 보여줌.
현재 json파일의 데이터는 아래와 같음.
- restaurants.json -

[
  {
    "name": "테스트",
    "address": "테스트거리 5, 테스트시",
    "cuisine": "이탈리안",
    "website": "http://mytest.com",
    "description": "맛나요"
  },
  {
    "name": "테스트2",
    "address": "테스트2거리 5, 테스트시2",
    "cuisine": "멕시칸",
    "website": "http://mytest2.com",
    "description": "가나다라"
  }
]

app.js파일에서 json파일을 읽어온 후 ejs파일에 작성해둔 restaurants변수에 데이터를 전달하도록 코드 변경.
- app.js -

app.get('/restaurants', function (req, res) {
  const filePath = path.join(__dirname, 'data', 'restaurants.json');

  const fileData = fs.readFileSync(filePath);
  const storedRestaurants = JSON.parse(fileData);

  res.render('restaurants', { numberOfRestaurants: storedRestaurants.length, restaurants: storedRestaurants });
});

storedRestaurants.length변수는 json파일 내 데이터의 갯수를 읽어와 저장하는 변수, restaurants변수는 전체데이터를 객체로 가져와 저장하는 변수.


제대로 적용되었을 경우 위와 같은 화면이 나옴.

고유id로 데이터 관리하기

각 레스토랑마다 고유한 id를 부여해 구분하고 관리할 수 있도록 하기.
외부 패키지 다운로드 => 터미널에 npm install uuid 입력하여 다운.

자바스크립트는 객체에 아직 존재하지 않는 속성에 엑세스하면 자동으로 생성해줌.

- app.js -

const uuid = require('uuid');

~생략~
  
app.post('/recommend', function (req, res) {
  const restaurant = req.body;
  restaurant.id = uuid.v4();
  const filePath = path.join(__dirname, 'data', 'restaurants.json');

  const fileData = fs.readFileSync(filePath);
  const storedRestaurants = JSON.parse(fileData);

  storedRestaurants.push(restaurant);

  fs.writeFileSync(filePath, JSON.stringify(storedRestaurants));

  res.redirect('/confirm');
});

restaurant.id = uuid.v4();에서 restaurant객체에 아직 id라는 속성은 없는 상태이며 uuid.v4()는 외부 패키지에서 제공받은 uuid객체를 통해 무작위로 생성되지만 고유성을 보장하는 id를 제공해주는 기능을 함.

레스토랑 상세페이지에서 id속성으로 상세페이지를 찾아가도록 a태그 주소수정.

- restaurant-item.ejs -

<li class="restaurant-item">
  <article>
    <h2><%= restaurant.name %></h2>
    <div class="restaurant-meta">
      <p><%= restaurant.cuisine %></p>
      <p><%= restaurant.address %></p>
    </div>
    <p><%= restaurant.description %></p>
    <div class="restaurant-actions">
      <a href="/restaurants/<%= restaurant.id %>">View Restaurant</a>
    </div>
  </article>
</li>

- restaurant-detail.ejs -

<!DOCTYPE html>
<html lang="en">
  <head>
    <%- include('includes/head') %>
    <link rel="stylesheet" href="/styles/restaurants.css" />
  </head>
  <body>
    <%- include('includes/header') %> <%- include('includes/sidebar') %>
    <main>
      <h1><%= restaurant.name %></h1>
      <p><%= restaurant.cuisine %> | <%= restaurant.address %></p>
      <p><%= restaurant.description %></p>
      <p><a href="<%= restaurant.website %>">View Website</a></p>
    </main>
  </body>
</html>


레스토랑 리스트 중 하나를 클릭하면 해당 레스토랑의 id를 주소로 상세페이지를 로드함.

에러 페이지 핸들링

url에러 등 원하는 경로의 페이지가 없을 경우 시스템에러페이지가 아닌 커스텀페이지를 보여주도록 변경하는 작업.

경우 1. 상세조회 할 페이지의 경로를 잘못입력한 경우 (404 상태코드)
레스토랑 상세조회 url인 http://로컬ip:3000/restaurants/r1 입력 중 레스토랑의 id를 잘못 입력한 경우.
404페이지를 만들어 대응할 수 있다.

경우 2. 서버가 요청을 이행하지 못해 에러가 나는 경우 (500 상태코드)
url경로를 틀려 에러가 나는 경우, json파일을 읽어오지 못해 에러가 나는 경우 등.
이 경우에는 경우 1과 달리 사용자가 url의 어디에서 에러를 낼지 알 수 없는 등 다양한 상황에 대한 개별적 대응이 불가능하므로 모든 요청을 받는 함수가 필요하다.

경우 1(404)의 작업

- 404.ejs -

<body>
  <%- include('includes/header') %> <%- include('includes/sidebar') %>
  <main>
    <h1>Page not found!</h1>
    <p>요청하신 페이지를 찾지 못했습니다.</p>
  </main>
</body>

- app.js -

app.get('/restaurants/:id', function (req, res) {
  const restaurantId = req.params.id;
  const filePath = path.join(__dirname, 'data', 'restaurants.json');

  const fileData = fs.readFileSync(filePath);
  const storedRestaurants = JSON.parse(fileData);

  for (const restaurant of storedRestaurants) {
    if (restaurant.id === restaurantId) {
      return res.render('restaurant-detail', { restaurant: restaurant });
    }
  }
  res.status(404).render('404');
});

for~if문단에서 적절한 데이터를 찾아 return되지 않고 블록을 넘어갈 경우 res.render('404');로 받아 404.ejs페이지로 넘겨준다.


http://로컬ip:3000/restaurants/r100 경로(id가 r100인 레스토랑은 없는 상태)로 접속할 시 시스템 에러페이지 대신 404.ejs페이지를 보여준다.

경우 2(500)의 작업

app.js에서 모든 url을 받을 수 있는 함수를 만들어 대응한다.

- app.js -

app.use(function (error, req, res, next) {
  res.status(500).render('500');
});


http://로컬ip:3000/resta 경로(잘못된 요청)로 접속하거나 레스토랑의 데이터를 찾는 json파일의 이름을 변경한 경우(json파일요청에러) 시스템 에러페이지 대신 위와 같이 개발자가 커스텀한 페이지를 사용자에게 보여주게 된다.

res.status(500).render('500');에서 .status(500)는 사용자에게 보여주는 화면은 커스텀페이지로 유지하되 개발자모드로 페이지를 볼때 에러코드를 확인할 수 있도록 하기 위해 추가한코드.
.status(500)가 없을 경우 개발자모드로 커스텀에러화면을 볼때 정상적인 요청코드(303 등)로 보임.

profile
천 리 길도 가나다라부터

0개의 댓글