Node.js 가볍게 배워보기

짜스의 하루 ·2024년 7월 8일

Node.js란?

공식 홈페이지에서 정의하는 Node.js란,

"V8 Java Script 엔진으로 빌드 된 Java Script 런타임" 이다.
--> Node.js의 등장으로 Java Script를 웹 브라우저로부터 독립시켜 서버 구현을 가능하게 했다.

여기서 런타임이란 ? 특정 언어가 구동되는 환경을 의미하는데, 과거에 Java Script의 런타임이 웹 브라우저 였다면, 현재는 Node.js로 인해 서버 구현까지 가능한 새로운 런타임이 생기게 된 것을 의미한다!

Node.js의 특징

비동기 이벤트 기반 환경

  • 이벤트 기반의 비동기 처리는 입출력 작업을 기다리는 동안 다른 작업을 수행할 수 있기 때문에 CPU 자운을 효율적으로 활용할 수 있다

단일 언어 사용

  • Node.js는 Java Script를 기반으로 하며 프론트엔드와 백엔드 모두에서 동일한 언어를 사용할 수 있다 (내가 지금 배우는 이유 중에도 하나다)

경량화

  • Node.js의 환경은 매우 가볍고, 구조가 모듈화 되어 있기 때문에 병렬 작업과 대용량 데이터를 처리하는데 높은 성능을 보장한다.

Node.js를 사용했을 때의 이점은 ?

기존의 웹 서버들 대부분은 스레드 기반으로 입출력을 처리했지만, Node.js는 대규모 네트워크 서비스를 개발하기 위해 고안되어 있기 때문에 이벤트 기반으로 처리된다.
(여기서 스레드란> 프로세스 냉서 실제로 작업을 수행하는 주체를 의미)

하지만 이벤트 기반에서는 각각의 작업을 준비하다가 첫번째 작업 도중 두번째 작업을 처리해야 한다면, 두번째 작업 이벤트를 발생시켜 첫번째 작업을 잠시 멈춰두고 두번째 작업을 실행한다.
--> 이러한 상황을 이벤트 기반의 비동기 처리 라고 할 수 있다.

스레드 기반 작업은 요청마다 스레드를 생성하기 때문에 메모리의 소모가 크며 스레드들이 동시에 똑같은 자원을 필요로 하는 경우 해당 자원에 대한 병목 현상도 일어날 수 있는데,

하지만 이벤트 기반의 Node.js는 비동기를 구현할 수 있으며, 이 덕분에 Non-Blocking을 하나의 스레드로 수행할 수 있다

Non-blocking이란 ? --> A라는 작업 중, B라는 작업을 실행시켜야 할 때 A라는 작업이 끝나지 않더라도 기다리지 않고 B작업이 실행될 수 있도록 하는 방식을 의미한다.


Node.js로 서버 구축해보기


예를 들어서 , 한 소비자가 상품 상세 페이지를 클릭한다면, 상세 페이지를 요청하게 되고(Request), 이에 대해서 서버는 상세 페이지를 보여주게 된다(Response)

정말 간단하게 Node.js의 내장 http 모듈을 사용하여 기본적인 HTTP 서버를 설정한다.
--> 서버가 요청을 받으면, "hello node"라는 단순한 텍스트 메시지로 응답합니다. 서버는 포트 3000번을 이용한다.

HTTP 모듈 가져오기 : const http = require('http');
--> require('http')는 Node.js의 내장 http 모듈을 가져옵니다. 이 모듈은 HTTP 서버를 만들 수 있는 기능을 제공한다.

서버 생성: const server : ...
--> http.createServer(...)는 HTTP 서버를 생성한다. 이 서버는 요청을 받고 응답을 보낸다 (req, res)

요청 처리 및 응답 보내기:
res.writeHead(200, { 'Content-Type': 'text/plain' });:

  • res.writeHead는 응답 헤더를 설정한다.
  • 200은 "OK"를 의미하는 HTTP 상태 코드이다.
  • { 'Content-Type': 'text/plain' } 는 콘텐츠 유형이 text/plain(텍스트)임을 지정한다.

res.write('hello node');

  • res.write는 응답 본문의 일부를 보낸다.

res.end();

  • res.end는 응답이 완료되었음을 알리며, 이 메서드를 호출하면 더 이상 데이터를 보낼 수 없다.

서버 시작: server.listen
server.listen(3000, ...) : listen은 서버가 3000번 포트에서 들어오는 요청을 듣도록 한다.

() => { console.log('Server is Listening on port 3000'); } :
이 부분은 서버가 요청을 듣기 시작할 때 실행되는 콜백 함수이다.
서버가 실행 중임을 나타내는 메시지를 콘솔에 출력하게 된다.

지금까지 내가 위에서 작성했던 방식은

type: "commonjs"
--> 이 설정은 JavaScript 파일을 CommonJS 방식으로 해석한다.
CommonJS는 Node.js에서 오래전부터 사용되던 모듈 시스템이다.

지금 현재 commonJs보다는 module을 더 많이 사용하기 때문에
먼저, coomonJs => ECMAScript Modules 스타일로 위에 구축했던 서버를 변경해보도록 하겠다.

딱 달라진 것은 하나 http에 속해있는 createServer 를 import 해서 가져온다는 점이다.
나머지는 일절 동일하다!


Express

Express 미들 웨어(Middleware) 의 연결이다
(여기서 미들 웨어란? --> 요청과 응답 사이에서 목적에 맞는 일을 수행하는 함수)

이처럼 하나의 요청이 들어오면 여러 미들웨어를 수행한 후, 응답으로 이루어진다.


간단하게 express로 메인 페이지를 구현해 보았는데
import express from express를 통해 express 모듈을 가져와서 사용할 준비를 하고,
const app = express()로 express()를 호출하여 Express 애플리케이션 객체를 생성한다.

그 사이에 middleware에서는 응답을 처리할 코드들이 들어갈 예정이다!
(미들웨어는 요청과 응답을 처리하거나 그 과정에서 어떤 작업을 수행하는 함수)

블로그 생성하기

node.js 실습을 위해서 웹 페이지(간단한 블로그 만들기)를 만들 예정이다.

먼저 간단하게 (react 관련 제외하고 정말 기초적으로 html, css, js) 파일들로 이번 실습을 해볼 예정이다.

/public 폴더 안에 main.html 파일을 생성해보자

Express를 사용하여 main.html 파일을 웹 브라우저에 보여주는 방법을 사용해보자

루트 경로 설정 (/):
app.get('/', ...) 은 GET 요청이 루트 경로 (/)로 들어올 때 실행되는 핸들러를 설정한다.
res.sendFile : 파일을 클라이언트로 전송한다.
--> 여기서는 __dirname + '/public/main.html'경로에 있는 main.html 파일을 전송하게 된다.

간단학 정리하자면,
app.get('/', ...)는 루트 URL (/)로 들어오는 HTTP GET 요청에 대해 /public/main.html 파일을 클라이언트에게 보내준다! 라고 이해하면 된다.

% 또한, const __dirname = path.resolve();
path.resolve(): 현재 스크립트의 디렉토리 경로를 절대 경로로 반환한다.
__dirname: Node.js 환경에서 현재 디렉토리의 절대 경로를 나타내는 변수로 설정된다.

이렇게 설정해주어야 오류가 발생하지 않는다.


NodeMon

NodeMon : Node.js 애플리케이션 개발에서 유용한 도구로, 파일 변경 시 자동으로 서버를 재시작해 주는 유틸리티이다.
이는 개발자가 코드를 수정할 때마다 수동으로 서버를 재시작하는 번거로움을 줄여주어 개발 속도를 크게 높일 수 있게 된다.

이제 nodeMon을 실행하기 위해 스크립트 설정 (package.json)에 nodemon을 추가해 보도록 하겠다.

 "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  },

요 scripts를 사용하기 위해서는
npm run + scripts문 으로 실행해야 한다!

이렇게 실행하고 나면,
이제 코드를 수정하고 저장을 한 다음, 다시 서버를 재시작 하지 않아도
localhost:8080에서 새로고침만 해도 ! 변경이 저장되는 것을 확인할 수 있다


Template Engine (템플릿 엔진)

템플릿 엔진(Template Engine) 은 웹 개발에서 동적으로 HTML을 생성하는 데 사용되는 도구이다.
정적 HTML 파일은 고정된 내용을 가지고 있어서 동적인 데이터 표현이 필요할 때는 한계가 있다.
--> 이러한 경우 템플릿 엔진을 사용하여 동적으로 데이터를 HTML에 삽입하거나 반복적으로 구성할 수 있다.

먼저, template Engine 중에서 numkucks를 사용하도록 하겠다.
index.js에서 nunkucks에 대해 이렇게 추가하도록 한다.

nunjucks.configure() 메서드는 템플릿 엔진의 설정을 초기화하고, Express 애플리케이션에 통합하는 역할을 한다.
views 디렉토리 설정 ('views'): nunjucks가 HTML 템플릿 파일을 찾는 디렉토리를 지정한다. --> views 디렉토리에 있는 .html 파일들을 템플릿으로 사용하는 것을 의미한다.

이제 views라는 폴더 안에서 base.html(공통으로 사용할 html 코드), write(글 작성 페이지 관련 코드)들을 작성해 보도록 하겠다.


공통된 요소를 생성하고 (nav, footer)
바뀌는 요소는 따로 생성해야 한다(block 단위의 content를 사용해야 한다)

{% block content %} : nunjucks에서 사용되는 템플릿 블록(Block)에 대한 문법 --> 이 문법은 주로 템플릿 상속 및 확장을 구현할 때 유용하게 사용된다.

상위 템플릿(base.html)에서 하위 템플릿(write.html)로 블록을 확장해보도록 하겠다.

{% extends 'base.html'%}
<!-- write라는 파일은 base파일을 상속받게 된다 -->

{% block content %}

<h1>글 작성 페이지 입니다</h1>
{% endblock %}

{% extends 'base.html' %} 문법을 사용하여 base.html 템플릿을 상속받고,
{% block content %} ... {% endblock %} 문법을 사용하여 content 블록을 오버라이드 한다
--> 이를 통해 base.html에서 정의된 기본 레이아웃을 유지하면서 content 영역의 내용을 변경할 수 있게 된다!


app.set('view engine', 'html') : Express의 뷰 엔진으로 nunjucks를 설정한다. 이 설정은 .html 확장자를 가진 파일을 렌더링할 수 있도록 한다.

/write 경로에서는 nunjucks 템플릿 엔진을 사용하여 write.html을 렌더링한다.
(res.render를 사용하여 템플릿을 렌더링하게 된다)

Express를 통해 라우팅 및 미들웨어를 설정하고, nunjucks를 통해 HTML 템플릿을 동적으로 렌더링하여 클라이언트에게 제공

이렇게 작성한 뒤, 저장을 하고, 렌더링을 하게 되면,
/write 페이지에 extends 를 받아온 base.html에 작성한 공통 부분과 ,
write.html에 작성한 block.content부분이 잘 렌더링 되는 것을 확인할 수 있다.

우리가 앞으로 만들 블로그에 대해서 간단하게 설명하자면
이런식으로 구성이 이루어질 예정이다.

main page (단순히 글 목록을 모여주기만 하기 때문에 GET)
write page (글작성 페이지 접속 -> GET, 글 작성 -> POST)
detail page (글 목록에서 하나의 글을 선택해서 상세 페이지로 이동 -> GET)
edit page (글 수정 페이지 접속 -> GET, 글 수정 -> POST)

로 이루어질 예정이다!


단순하게 write.json파일에 데이터 저장해보기

아직 데이터베이스를 연결하기 전이기 때문에 간단하게
write.json 파일을 생성해서 데이터를 저장해보도록 하겠다.

const filePath = path.join(__dirname, 'data', 'write.json');

...

app.post('/write', async (req, res) => {
  const title = req.body.title;
  const contents = req.body.contents;
  const date = req.body.date;

  //data/write.json 파일 안에 데이터가 저장되도록 해보자
  const fileData = fs.readFileSync(filePath); //파일 읽기
  const writing = JSON.parse(fileData); //파일 변환

  //request 데이터 저장
  writing.push({
    title: title,
    contents: contents,
    date: date,
  });

  //다시 data/write.json 파일에 데이터 저장하기
  fs.writeFileSync(filePath, JSON.stringify(writing));

  res.render('detail', {
    detail: { title: title, contents: contents, date: date },
  });
});

먼저 /write 로 POST 요청을 보내서 title, contents, data를 얻어온다.
이후, 데이터를 저장할 공간을 filePath로 지정해두고,
filePath를 fs.readFileSync로 불러와 fileData로 저장한다.
(이때, fileData는 원초적인 데이터이기 때문에 JSON 형태로 불러와야 한다)

JSON.parse(fileData) --> JSON 형태로 변환 후 writing 변수에 저장한 뒤,
writing에 각각 title, contents, date의 변수 이름으로 /write에서 가져온 데이터들을 저장해준다.

이렇게 저장한 값을 다시 /write에 저장해주어야 하는데,

이때 fs.writeFileSync() 를 사용한다
filepath 경로로, JSON.stringify(writing)을 저장한다.라고 이해하면 될 것이다.

이렇게 하면
/write에 데이터를 불러오고 (POST),
데이터를 저장할 공간(filepath)의 파일을 읽은 뒤, JSON 형태로 변환 뒤, 불러온 데이터를 저장해주고, 다시 /write 에 데이터를 저장한다. 라고 이해하면 될 것이다.

1 . 데이터를 /write 경로로 POST 요청으로 보낸다
2 . 서버는 요청 데이터를 받아 파일에서 기존 데이터를 읽어온다
3 . 새로운 데이터를 기존 데이터에 추가한다
4 . 변경된 데이터를 JSON 파일로 저장한다
5 . 응답으로 detail 페이지를 렌더링하여 새로운 데이터 내용을 보여준다.

⭐️ 그럼 여기서, / 링크를 타고 들어갔을 때 (메인페이지) 저장된 글 목록을 보여주어야 하는데 어떻게 해야할 까 ?

app.get('/', async (req, res) => {
  const fileData = fs.readFileSync(filePath);
  const writing = JSON.parse(fileData);
  res.render('main', { list: writing });
});

filePath의 파일을 읽어온 뒤 fileData에 저장한 뒤, JSON 형식으로 파싱을 한다.
이후, main.html 파일이 렌더링될 때, writing 변수가 list라는 이름으로 템플릿에 전달되게 된다.

main.html

{% extends 'base.html' %}

{% block content %}
<h1>메인 페이지</h1>
{% if list %}
<table class="table">
  <tr>
    <th>글 제목</th>
    <th>글 내용</th>
    <th>작성 날짜</th>
  </tr>
  {% for writing in list %}
  <tr>
    <td>{{ writing.title }}</td>
    <td>{{ writing.contents }}</td>
    <td>{{ writing.date }}</td>
  </tr>
  {% endfor %}
</table>
{% else %}
<p> 작성된 글이 없습니다.</p>
{% endif %}
{% endblock %}

{% extends 'base.html' %}: 'base.html'이라는 기본 템플릿을 확장한다.
{% block content %}{% endblock %} 사이에 HTML 내용을 작성한다.
--> 이 내용이 'base.html'의 content 블록에 삽입된다.

조건문
{% if list %} : list 변수가 존재하고 데이터가 있는 경우를 검사한다.
list가 비어 있지 않다면 테이블을 표시한다!

테이블
{% for writing in list %} : list의 각 항목을 반복하며 테이블 행을 생성
{{ writing.title }}, {{ writing.contents }}, {{ writing.date }} : writing 객체의 title, contents, date 속성에 접근하여 테이블 셀에 데이터를 삽입한다.

이제 렌더링하게 되면,

localhost:3000 사이트에 (메인페이지) 이렇게 테이블 형식으로 글 목록이 나타나는 것을 확인할 수 있다


데이터베이스에 저장 (MongoDB)

간단하게 데이터에 대해서 설명하자면 관계형 데이터베이스와 비 관계형 데이터베이스로 나눌 수 있는데
spring을 공부했을 때에는 MySQL, Oracle을 사용했었던 기억이 있다.

하지만 간단한 데이터베이스를 구축할 때에는 MongoDB만 사용해도 된다.

간단하게 MongoDB을 이해하고, 이제 MongoDB를 설치해보자

homebrew를 사용해서 mongodb를 설치하려고 한다

homebrew설치 : /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

환경변수 설정 : echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc eval "$(/opt/homebrew/bin/brew shellenv)"
mongoDB 설치 : brew install mongodb-community

Mongo 서비스 시작 : brew services start mongodb/brew/mongodb-community

MongoDB 상태 확인 : brew services list

MongoDB 명령어 사용:
MongoDB 서버가 실행 중이면, mongo 명령어로 MongoDB 셸에 접근할 수 있음
mongo

만약, not found라는 오류가 뜬다면,

brew install mongodb-community-shell을 입력해서 설치해주면 이제 실행이 될 것이다

MongoDB는 127.0.0.1:27017 주소에서 실행되며, 해당 포트로 연결 요청을 처리한다.

이제,mongoose를 vscode 터미널에서 설치하면 된다.

mongoose는 MongoDB 데이터베이스와 상호 작용할 수 있는 Node.js용 객체 데이터 모델링(ODM) 라이브러리이다.
MongoDB는 비정형 데이터베이스로 스키마가 고정되어 있지 않은데, mongoose는 이를 보완하여 MongoDB 데이터를 JavaScript 객체처럼 다루도록 해준다.
--> 즉, 데이터 모델을 정의하고 데이터베이스와의 상호작용을 보다 직관적이고 편리하게 할 수 있도록 도와준다.


npm install mongoose 명령어로 설치를 하게 된다면 끝! mongodb를 사용할 준비를 끝냈다

여기서, 127.0.0.1:27017 포트에 들어가보면
이 뜨게 되는데, mongodb가 잘 돌아가고 있다는 것을 의미한다!
(다만, 브라우저에서 mongodb를 접속할 수 없다는 메시지이다)


mongoose란 ?

mongoose에서 schema를 제공한다
Schema란 ? --> Node.js에서 데이터처리를 쉽게 해줄 수 있도록 도와주는 역할
Schema의 종류 --> string, number. data, boolean, buffer, mixed(모든 타입 가능), ObjectedId(객체 ID), Array(배열)

mongoose set

MongoDB 데이터베이스에서 사용할 데이터 모델을 정의해보자
--> mongoose를 사용하여 스키마(Schema)를 정의하고, 해당 스키마를 기반으로 모델을 생성하게 된다.

const { Schema } = mongoose;
const writingSchema = new Schema({
  title: String,
  contents: String,
  date: {
    type: Date,
    default: Date.now(),
  },
});

const writing = mongoose.model('writing', writingSchema);

const { Schema } = mongoose; : Schema는 MongoDB에서 사용할 데이터의 구조를 정의하기 위해 사용한다.

writingSchema : writingSchema라는 스키마를 정의
--> 이 스키마는 MongoDB의 writing 컬렉션에 저장될 문서의 구조를 정의한다.

const writing = mongoose.model('writing', writingSchema); : 해석: writing이라는 이름의 모델을 생성한다.
이 모델은 writingSchema를 기반으로 하며, MongoDB의 writings 컬렉션에 대응한다. (주의: 컬렉션 이름은 보통 소문자로 변환되고 복수형으로 만들어집니다. writing -> writings)
이유: 모델을 생성하면 MongoDB의 해당 컬렉션과 상호작용할 수 있게 된다.
모델은 스키마를 기반으로 데이터베이스의 데이터를 쿼리하거나 조작하는 데 사용된다.

Schema 정의: writingSchema는 MongoDB에 저장될 데이터의 구조를 정의
각 필드(title, contents, date)의 데이터 타입과 기본값(default)을 설정하여 문서가 어떤 형태로 저장될지를 명확히 정의한다.

Model 생성: mongoose.model 메서드를 사용하여 writing이라는 모델을 생성한다. 이 모델은 writingSchema를 기반으로 하며, MongoDB의 writings 컬렉션과 연결된다.
--> 따라서 writing 모델을 사용하여 데이터를 생성(Create), 읽기(Read), 업데이트(Update), 삭제(Delete)하는 등의 작업을 수행할 수 있다.

기존에 서버를 실행할 때에는 npm run dev를 통해서 3000 port를 실행시켰지만,
mongoDB를 통해서는 brew service start mongodb-community를 통해서 27017 port를 실행시켰다.

이 둘을 연결시켜주기 위해서 mongoose를 이용하는 것이라고 생각하면 된다.

//mongoose connect
mongoose
  .connect('mongodb://127.0.0.1:27017')
  .then(() => console.log('DB 연결 성공'))
  .catch(() => console.error(e));

mongoose.connect() : MongoDB 데이터베이스에 연결한다.
mongoose 모듈의 connect() 메서드를 사용하여 MongoDB 서버의 주소와 포트를 지정할 수 있다.

연결 성공(.then()) : connect() 메서드가 성공적으로 호출되면, Promise 객체의 then() 메서드가 실행된다. 이 경우 'DB 연결 성공'이라는 메시지를 콘솔에 출력할 수 있다.

이렇게 연결 성공! 한 것을 확인할 수 있다.

MongoDB의 데이터 구조를 살펴보면
Database 안에 Collection이 있고, Collection 안에 Document들이 있다.

Database: MongoDB 서버에는 여러 개의 데이터베이스가 존재할 수 있다. 각 데이터베이스는 데이터를 물리적으로 분리하고 관리하는 단위를 의미한다.

Collection: 각 데이터베이스는 하나 이상의 컬렉션을 포함할 수 있다. 컬렉션은 MongoDB에서 문서(document)를 저장하는 단위이다. 컬렉션은 동적으로 스키마가 정의되며, 비슷한 유형의 문서들을 그룹화한다.

Document: 컬렉션에 저장되는 실제 데이터 단위를 문서(document)라고 한다.
각 문서는 JSON 형식의 데이터로 키(key)와 값(value) 쌍으로 구성되어 있다.
MongoDB는 유연한 스키마를 제공하여 각 문서가 다른 구조를 가질 수 있다.

이제 터미널에서 Collection을 만들어보자

db.createClooection('writings') 를 통해서 collections를 하나 생성했다.


db.writings.insert ({...}) 를 통해서 직접 데이터를 넣을 수 있고,
writings이라는 collection에서 데이터를 찾으려면 ? --> find()함수를 사용해서 데이터를 확인할 수 있다.
(여기서 조금 예쁘게 데이터를 확인하려면, pretty() 메서드를 사용해서 좀 편하게 확인할 수 있다)


mongoDB -> write

이제 mongoDB에 데이터를 연결해보도록 하겠다

app.post('/write', async (req, res) => {
  const { title, contents } = req.body;

  // MongoDB에 저장하기
  const writing = new Writing({
    title,
    contents,
  });

  const result = await writing
    .save()
    .then(() => {
      console.log('Success');
      res.render('detail', { title: title, contents: contents });
    })
    .catch((e) => {
      console.error(e);
      res.render('write');
    });
});

/write 경로에 대한 POST 요청을 처리하여 사용자가 제출한 글을 MongoDB에 저장하고, 저장 성공 시 글의 세부 정보를 화면에 렌더링하는 역할을 한다.

const { title, contents } = req.body; : req.body에서 title과 contents 필드를 추출--> 이는 사용자가 폼에 입력한 데이터를 나타낸다.

const writing = new Writing({
  title,
  contents,
});

Writing 모델의 새 인스턴스를 생성 --> 이 모델은 MongoDB writing 컬렉션에 매핑되며, title과 contents를 인스턴스의 필드로 초기화한다.
--> 이 인스턴스는 MongoDB 문서(document)로 저장될 객체를 나타낸다.

try {
  const result = await writing.save();
  console.log('Success');

writing.save() 는 새로운 writing 인스턴스를 MongoDB에 저장

res.render('detail', { title: title, contents: contents }); : 저장이 성공하면, detail 페이지를 렌더링하며, title과 contents를 템플릿에 전달

비동기적으로 실행 : 이 함수는 비동기적으로 실행되며, 데이터베이스 작업이 완료될 때까지 기다린 후 작업이 성공하면 detail 페이지로, 실패하면 write 페이지로 이동하게 설계

를 하게 되면,

이렇게 생성되는 것을 확인할 수 있다.


mongoDB -> Delete

delete는 디테일 페이지에서 글 삭제 버튼을 누르면 -> / 페이지 (메인페이지)로 돌아갈 수 있도록 코드를 작성할 예정이다.

app.post('/delete/:id', async (req, res) => {
  const id = req.params.id;

  try {
    const result = await Writing.findByIdAndDelete(id);
    if (result) {
      res.redirect('/');
      console.log('success delete');
    } else {
      res.send('해당 글을 찾을 수 없습니다.');
    }
  } catch (e) {
    console.error(e);
    res.status(500).send('서버 오류');
  }
});

const result = await Writing.findByIdAndDelete(id); : Writing 모델의 findByIdAndDelete 메서드를 사용하여 데이터베이스에서 id와 일치하는 문서를 삭제한다.

이 라우트 핸들러는 클라이언트로부터 POST 요청을 받아 데이터베이스에서 해당 ID를 가진 문서를 삭제한다.
성공적으로 삭제되면 메인 페이지로 리디렉션하고, 실패하면 에러 메시지를 사용자에게 반환하게 된다.

detail.html에서 delete관련 코드를 작성하자면

여기서 잊지 말아야 할 부분 ! --> 템플릿에서 detail.id 대신 detail._id를 사용해야 한다!
--> MongoDB에서 기본적으로 _id 필드를 사용하므로, id 대신 _id를 사용해야 한다


여기서 글 삭제 버튼을 누르면, 성공적으로 삭제가 이루어지는 것을 확인할 수 있다.


mongoDB -> Edit

수정페이지는 -> 디테일 -> 글 수정 누르면 -> edit.html로 이동 -> 글 수정 -> detail로 이동

이렇게 이루어질 예정이다.

app.get('/edit/:id', async (req, res) => {
  const id = req.params.id;

  try {
    const writing = await Writing.findById(id);
    if (writing) {
      res.render('edit', { edit: writing });
    } else {
      res.send('해당 글을 찾을 수 없습니다.');
    }
  } catch (e) {
    console.error(e);
    res.status(500).send('서버 오류');
  }
});

app.post('/edit/:id', async (req, res) => {
  const id = req.params.id;
  const { title, contents } = req.body;

  try {
    const writing = await Writing.findByIdAndUpdate(
      id,
      { title, contents },
      { new: true }
    );
    if (writing) {
      res.redirect(`/detail/${writing._id}`);
    } else {
      res.send('해당 글을 찾을 수 없습니다.');
    }
  } catch (e) {
    console.error(e);
    res.status(500).send('서버 오류');
  }
});

GET /edit/:id

  • Route 설명: /edit/:id 경로에 GET 요청이 들어오면 실행
    --> :id는 동적으로 변하는 부분으로, 여기서는 글의 고유 ID를 의미한다.

  • 변수 설정: req.params.id를 통해 요청의 ID를 추출하여 id 변수에 저장한다.

  • DB 조회: Writing.findById(id) 를 사용하여 MongoDB에서 해당 ID를 가진 글을 찾는다. await 키워드를 사용하여 비동기적으로 처리하며, Writing은 Mongoose 모델로부터 생성된 객체로 가정한다.

  • 조회 결과 처리:
    writing이 존재하면, res.render('edit', { edit: writing }) 를 통해 edit 템플릿을 렌더링한다. 이때 edit 템플릿에 writing 객체를 전달하여 해당 글의 정보를 편집할 수 있는 페이지를 보여주게 된다.

  • writing이 존재하지 않으면, '해당 글을 찾을 수 없습니다.' 메시지를 반환한다.
    --> 에러 처리: try-catch 구문을 사용하여 예외가 발생할 경우, 콘솔에 에러를 기록하고 클라이언트에게 500 Internal Server Error를 반환한다.

POST /edit/:id

  • Route 설명: /edit/:id 경로에 POST 요청이 들어오면 실행된다. GET 요청과 동일한 URL을 사용하지만 HTTP 메서드가 다르다.

  • 변수 설정: req.params.id를 통해 요청의 ID를 추출하여 id 변수에 저장한다.
    req.body를 통해 클라이언트로부터 전송된 데이터에서 title과 contents를 추출한다.

  • DB 업데이트: Writing.findByIdAndUpdate()를 사용하여 MongoDB에서 해당 ID를 가진 글을 업데이트한다. 업데이트할 필드와 새 값은 { title, contents }로 전달되며, { new: true } 옵션은 업데이트된 문서를 반환하도록 설정한다.

  • 업데이트 결과 처리: writing이 존재하면, res.redirect(/detail/${writing._id}); 를 통해 업데이트된 글의 상세 페이지로 리다이렉션한다.

  • writing이 존재하지 않으면, '해당 글을 찾을 수 없습니다.' 메시지를 반환한다.
    에러 처리: 업데이트 과정에서 예외가 발생하면, 콘솔에 에러를 기록하고 클라이언트에게 500 Internal Server Error를 반환하게 된다.

edit.html

{% extends 'base.html' %}

{% block content %}
<h1>상세 페이지 수정</h1>
<form action="/edit/{{ edit._id }}" method="post">
  <p>글 제목: <input type="text" name="title" value="{{ edit.title }}"></p>
  <p>글 내용: <input type="text" name="contents" value="{{ edit.contents }}"></p>
  <input type="submit" value="수정 완료">
</form>
{% endblock %}

이렇게 구현을 하게 되면,

글 수정 버튼을 누르면 ->
이렇게edit/:id 로 이동하게 되면서 edit.html이 열리게 된다!

수정 완료 버튼을 누르면

수정이 완료된 상태로 detail/id로 되돌아간다!

profile
2024. 01. 02 ~ 백앤드 공부 시작, 2024. 04.01 ~ 프론트 공부 시작

0개의 댓글