Node.js 학습형 2주차

송채원·2025년 4월 6일

Node.js

목록 보기
2/3

3. 웹 애플리케이션 서버 만들기

3-1 몽고DB 설정하기


✏️ 데이터베이스란?
애플리케이션 개발 시 다양한 정보를 관리함.
이 정보들은 데이터베이스에 저장되며 데이터베이스는 크게 관계형 데이터베이스와 NoSQL 데이터베이스로 나뉜다.

  1. 관계형 데이터베이스

  • 자료 관리 방식: 표 형태로 데이터를 관리하며, 이는 흔히 사용되는 엑셀과 유사함.
  • 데이터 간 관계: 표 형태의 데이터는 서로 연결하여 사용할 수 있음.
  • SQL 사용: 자료 추가, 수정, 관리를 위해 SQL 언어를 사용해야 함. 따라서 SQL을 공부하는 것이 필요함.
  1. NoSQL 데이터베이스

    SQL을 사용하지 않는 데이터베이스로, JSON 형식으로 자료를 저장함. 자바스크립트를 알고 있다면 NoSQL을 다루기가 수월함.

몽고DB 가입 및 설정

JSON 형식 사용
클라우드 기반으로 데이터베이스를 생성할 수 있음.

  • 계정 보안 강화: 이중 인증을 설정할 수 있으며, 초기 로그인 시 데이터베이스 설정을 위한 다양한 질문(사용 목적, 사용 언어, 데이터 모델 등)에 답해야 함.

  • 서비스 선택:

    • 클라우드 제공업체를 선택함 (예: AWS, 구글 클라우드 등).
    • 리전 및 클러스터 이름을 설정한 후 데이터베이스를 생성함.

데이터베이스 접근 설정

  • 아이디 및 비밀번호 설정: 데이터베이스 접근을 위해 아이디와 비밀번호를 설정함. 가입 시 사용한 이메일이 아이디로 설정되며, 비밀번호는 랜덤하게 생성되어 기록이 필요함.
  • IP 주소 설정: IP 주소 설정을 통해 데이터베이스의 접근 가능성을 조정할 수 있음.
  • 기본 설정 완료: 설정 완료 후 데이터베이스 공간을 확인할 수 있음.

VS 코드와 몽고DB 연결

VS 코드에서 몽고DB를 관리하기 위해 몽고DB 확장을 설치할 수 있음.

  • 확장 설치 및 연결:
    • 몽고DB 웹사이트에서 커넥션 스트링을 복사함.
    • VS 코드에 커넥션 스트링을 붙여넣고 비밀번호를 입력하여 연결함.
    • 연결 성공 시 데이터베이스를 확인할 수 있음.

3-2 몽고DB 사용하기

  1. MongoDB 연동을 위한 환경 설정

    • 환경 변수를 사용하여 데이터베이스 연결 정보를 안전하게 관리함.
    • .env 파일을 생성하여 DB 접속 정보를 저장하고, 중요한 정보를 보호하기 위해 .env 파일은 깃허브에 공유하지 않도록 설정함.
    • DB 접속 주소(커넥션 스트링)는 몽고DB 사이트에서 확인할 수 있음.
      아틀라스 화면에서 데이터베이스 -> Connect 버튼을 클릭-> "MongoDB Compass" 옵션을 선택-> 커넥션 스트링 복사
      이 내용을 .env 파일에 저장하고 비밀번호를 실제 비밀번호로 수정함.
  2. mongoose 라이브러리 설치 및 설정

    • mongoose 라이브러리는 Node.js에서 MongoDB를 쉽게 사용할 수 있게 해줌.
    • 터미널에서 npm install mongoose dotenv 명령어를 통해 mongoosedotenv 라이브러리를 설치함.
    • JavaScript 파일에서 mongoose 라이브러리를 import함:
      const mongoose = require('mongoose');
    • dotenv 모듈을 사용하여 .env 파일의 환경 변수를 로드함:
      require('dotenv').config();
  3. 데이터베이스 연결 모듈 생성

    • 데이터베이스 연결 코드를 모듈화하여 재사용성을 높임. config 폴더 안에 dbConnect.js 파일을 생성하고 비동기 처리를 위해 async/await 구문을 사용함:
      const connectDB = async () => {
        try {
          await mongoose.connect(process.env.DB_URL, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
          });
          console.log('DB Connected');
        } catch (error) {
          console.error('Error connecting to DB:', error);
        }
      };
    • mongoose.connect() 함수를 사용하여 데이터베이스에 연결하며, 커넥션 스트링은 .env 파일에서 가져옴.
    • 데이터베이스 연결 함수를 모듈로 export함:
      module.exports = connectDB;
  4. app.js에서 데이터베이스 연결 모듈 사용

    • 애플리케이션의 app.js 파일에서 데이터베이스 연결 모듈을 가져와 사용함:
      const connectDB = require('./config/dbConnect');
    • connectDB() 함수를 호출하여 데이터베이스에 연결함:
      connectDB();
    • 서버 실행 후 데이터베이스 연결 성공 여부를 확인할 수 있으며, 콘솔에 "DB Connected" 메시지가 출력되면 성공적으로 연결된 것임.

3-3 스키마와 모델

1. 스키마(Schema)란?

스키마는 MongoDB에서 저장할 데이터의 구조를 정의함.
예: 연락처 앱-> 이름·번호·이메일 같은 필드를 어떻게 구성할지

스키마의 특징

  • 필드 타입을 지정해 일관성 있는 데이터 저장 가능
  • 필수/옵션 속성을 설정해 데이터 안정성 확보
  • 자동 생성 시간, 수정 시간 기록 가능

📌 코드 예시:

const mongoose = require('mongoose');

const contactSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true, // 필수값
  },
  phone: {
    type: String,
    required: true,
  },
  email: {
    type: String,
    required: false,
  },
}, {
  timestamps: true, // createdAt, updatedAt 자동 생성
});
  1. 모델(Model)이란?
    스키마를 실제 데이터로 만들 수 있도록 변환한 객체

▪️역할

  • 스키마를 기반으로 만들어짐.
  • 데이터베이스와의 상호작용을 담당함.
  • create, find, update, delete 등 기본 메서드를 내장함.

📌 코드 예시:

const Contact = mongoose.model('Contact', contactSchema);

이제 Contact 모델로 데이터를 생성/조회할 수 있음:

const newContact = new Contact({
  name: '홍길동',
  phone: '010-1234-5678',
});
newContact.save();
  1. 도큐먼트(Document)와 컬렉션(Collection)

MongoDB 기본 단위는 도큐먼트, 컬렉션임. 다른 RDBMS에서는 각각 행(Row), 테이블(Table)에 해당됨.

▪️도큐먼트

  • 하나의 개별 데이터 (JSON 형식)
  • 예: { name: "홍길동", phone: "010-1234-5678" }

▪️컬렉션

  • 도큐먼트들의 묶음
  • 예: Contacts 컬렉션 안에 여러 연락처 도큐먼트 저장

스키마 → 도큐먼트 구조 정의
도큐먼트 → 실제 데이터
컬렉션 → 도큐먼트 모음
모델 → 스키마 기반 도큐먼트 생성 및 DB 조작 도구

  1. Mongoose

1️⃣ 스키마 정의하기

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
  },
  age: Number,
}, {
  timestamps: true,
});

2️⃣ 모델 생성하기

const User = mongoose.model('User', userSchema);

3️⃣ 모델로 데이터 저장하기

const newUser = new User({
  username: 'johndoe',
  age: 25,
});
newUser.save().then(() => console.log('User saved!'));

모델명(User)이 자동으로 소문자 복수형(users) 컬렉션으로 매핑됨!

3-4 컨트롤러 만들기

1. RESTful API란?

API(Application Programming Interface)는 애플리케이션 간 데이터를 주고받고 기능을 실행하기 위한 인터페이스임.

RESTful API는 HTTP 프로토콜을 기반으로 데이터를 주고받는 형식의 API를 의미함. HTTP 메서드(POST, GET, PUT, DELETE)와 URI(자원의 주소)를 조합해 자원에 접근함.

🌐 예시 URI

GET /contacts/10
→ ID가 10번인 연락처 정보를 요청함.

📌 주요 요청 방식 (CRUD)

요청 방식역할
POSTCreate (생성)
GETRead (조회)
PUTUpdate (수정)
DELETEDelete (삭제)
  1. MVC 패턴

MVC(Model-View-Controller) 패턴은 애플리케이션 기능을 역할별로 분리하여 구축하는 설계 방식임.

구성 요소

  • Model: DB와 연결되는 데이터 형식 정의 (mongoose schema 등)
  • View: 사용자에게 보여질 화면 (실습에서는 주로 API 응답으로 대체됨)
  • Controller: 라우팅에 따른 로직 처리 담당
  1. 라우터와 컨트롤러

컨트롤러는 실제 API 동작을 처리하는 함수를 담고 있음. 라우터에서는 컨트롤러 함수만 호출함으로써 역할을 구분함.

✔️ 구조 예시

// routes/contactRoutes.js
const express = require('express');
const router = express.Router();
const {
  getAllContacts,
  createContact,
} = require('../controllers/contactController');

router.get('/', getAllContacts);
router.post('/', createContact);

module.exports = router;

// controllers/contactController.js
const asyncHandler = require('express-async-handler');

// GET /contacts
exports.getAllContacts = asyncHandler(async (req, res) => {
  res.send('전체 연락처 조회 페이지');
});

// POST /contacts
exports.createContact = asyncHandler(async (req, res) => {
  // 실제로는 DB에 데이터 저장 로직이 들어감
  res.send('새 연락처 생성');
});
  1. async handler를 이용한 에러 처리

비동기 API에서는 try-catch를 일일이 작성해야 하는 번거로움이 있음. express-async-handler 모듈을 사용하면 자동으로 에러를 처리함.

설치

npm install express-async-handler

사용 예시

const asyncHandler = require('express-async-handler');
exports.getAllContacts = asyncHandler(async (req, res) => {
  // 에러 발생 시 자동으로 next(err) 호출됨
  res.send('연락처 목록');
});
  1. 컨트롤러 함수 예제

📌 GET - 연락처 전체 조회

  • 경로: GET /contacts
  • 컨트롤러 함수: getAllContacts

contactController.js:

exports.getAllContacts = asyncHandler(async (req, res) => {
res.send('전체 연락처 목록');
});

📌 POST - 연락처 생성

  • 경로: POST /contacts
  • 컨트롤러 함수: createContact
contactController.js:

exports.createContact = asyncHandler(async (req, res) => {
  // req.body에서 전달된 데이터 처리
  res.send('새 연락처 생성됨');
});

✍ 라우터 구조 요약

routes/contactRoutes.js:

const express = require('express');
const router = express.Router();
const {
  getAllContacts,
  createContact,
} = require('../controllers/contactController');

router.get('/', getAllContacts);    // GET /contacts
router.post('/', createContact);    // POST /contacts

module.exports = router;

3-5 CRUD코드 작성하기

Node.js에서 MongoDB와 연동해 CRUD(Create, Read, Update, Delete) 기능을 구현, Mongoose 라이브러리를 사용해 데이터베이스에 접근하고 데이터를 조작함.

주요 Mongoose 함수

함수설명
create()새 도큐먼트를 생성함
find()조건 없이 전체 조회 / 조건 있으면 해당 도큐먼트만 조회함
findOne()조건에 맞는 첫 번째 도큐먼트를 조회함
updateOne()조건에 맞는 첫 번째 도큐먼트를 수정함
updateMany()조건에 맞는 모든 도큐먼트를 수정함
deleteOne()조건에 맞는 첫 번째 도큐먼트를 삭제함
deleteMany()조건에 맞는 모든 도큐먼트를 삭제함
findById()ID로 도큐먼트를 조회함
findByIdAndUpdate()ID로 찾아 수정함
findByIdAndDelete()ID로 찾아 삭제함

데이터베이스 연결 및 모델 정의

  • 스키마를 정의하고 모델을 생성함
  • 모델명은 대문자 단수형을 사용하고, 실제 컬렉션명은 소문자 복수형으로 생성됨

4. 웹 애플리게이션 완성하기

4-1. EJS 템플릿 엔진 사용법

EJS는 템플릿 파일과 데이터를 연결해 동적 콘텐츠를 생성하는 템플릿 엔진임.

설치 및 설정 방법

  1. npm install ejs로 설치함
  2. app.js에서 app.set('view engine', 'ejs')로 설정
  3. 템플릿 파일 저장용 views 폴더를 생성함

  1. EJS 템플릿을 활용한 동적 콘텐츠 생성
  • 컨트롤러에서 res.render()로 EJS 파일을 렌더링함
    예: res.render('get_all.ejs', { heading: '유저 리스트' })
  1. EJS 태그 사용법

태그사용 예시설명
<%= %><%= 변수명 %>변수 출력
<% %><% JS 코드 %>JS 코드 실행
<%- %><%- HTML %>HTML 삽입
  1. 데이터 반복 출력 예시

<% users.forEach(user => { %>
  <%= user.name %>
<% }); %>
  1. 정적 파일 관리 및 퍼블릭 폴더 설정

  • CSS, JS, 이미지 등 정적 파일은 public 폴더에 저장함
  • public 안에 css, js, images 폴더 등을 생성함
  • app.js에서 app.use(express.static('public'))으로 설정함

4-2 전체 연락처 표시하기

  • 전체 연락처를 표 형태로 보여주는 화면 구성
  • 추가, 수정, 삭제 기능 구현됨
  • 요청 방식에 따라 동작함

GET - 폼 표시 등 데이터 요청
POST - 새 연락처 저장
PUT - 연락처 수정
DELETE - 연락처 삭제

  1. 사용되는 EJS 파일

  • index.ejs: 전체 연락처 표시
  • add.ejs: 새 연락처 추가 폼
  • update.ejs: 기존 연락처 수정 폼
  1. EJS 파일 모듈화 및 코드 재사용

  • 중복되는 헤더/푸터 코드를 모듈화함
  • views/includes 폴더에 _header.ejs, _footer.ejs 파일 생성
  • 각 EJS 파일에서 다음과 같이 불러옴:
    <%- include('includes/_header') %>
    ...
    <%- include('includes/_footer') %>
    물론이지! 아래처럼 "~한다", "~함" 말투로 간단하고 깔끔하게 정리해봤어 😊

4-3 연락처 추가하기

  • contacts/add 경로를 만들어 연락처 추가 화면을 띄움

  • 해당 경로로 GET 요청 시, 컨트롤러에서 add.ejs 파일을 렌더링함

  • routes 파일에 해당 경로를 연결하여 요청 처리함

  • 사용자가 폼에 입력 후 저장 버튼 클릭 시 POST 요청 발생

  • 같은 contacts/add 경로로 POST 요청이 들어오면, 컨트롤러에서 DB에 새 연락처를 저장함

  • EJS 폼에서 method="POST", action="/contacts/add"로 설정함

  • 저장 후 연락처 목록으로 리다이렉트함


4-4 연락처 수정 및 삭제하기

[수정]

  • GET /contacts/:id 경로를 통해 특정 연락처 수정 화면을 보여줌
    *컨트롤러에서 해당 ID에 맞는 연락처 정보를 DB에서 찾아 update.ejs로 전달함

  • update.ejs에서 각 입력 필드에 value로 기존 정보 표시

  • index.ejs에서 연필 아이콘을 클릭하면 /contacts/:id 경로로 이동하게 설정

  • method-override 모듈 설치 후, app.js에 등록

    npm install method-override
  • update.ejs에서 폼 method는 POST, action은 ?_method=PUT으로 설정하여 PUT 요청으로 처리

  • 수정 완료 시 /contacts로 리다이렉트

[삭제]

  • index.ejs에서 X 버튼 클릭 시 삭제 요청 보내도록 설정
  • form 태그 사용, method="POST", action="/contacts/:id?_method=DELETE"
  • 숨겨진 필드를 통해 DELETE 방식으로 요청 보냄
  • 컨트롤러에서 findByIdAndDelete() 사용해 해당 ID의 연락처 삭제
  • 삭제 후 /contacts로 리다이렉트

4-4 로그인 처리하기

  • 루트(/) 경로 접속 시 로그인 화면이 나타나도록 home.ejs 생성
  • 헤더와 푸터는 include 문으로 삽입하여 중복 제거

로그인용 컨트롤러 파일(loginController.js) 생성
GET / 요청 시 home.ejs를 렌더링하는 getLogin 함수 작성
routes 설정 파일에서 루트 경로와 연결함
app.js에서 로그인 라우터를 앱에 등록함

로그인 폼에서는 POST 방식 사용, action="/"으로 설정
로그인 버튼 클릭 시 loginUser 함수가 호출되어 아이디/비밀번호 확인
아이디가 admin, 비밀번호가 1234이면 성공 메시지 출력
추후 DB에서 사용자 정보 비교하는 방식으로 변경 예정


4-5~6 로그인 처리하기 및 사용자 인증

오케이! 짧은 핵심 코드만 남기고 나머지 긴 코드는 다 빼고, 벨로그 스타일로 다시 깔끔하게 정리해줄게:


관리자 등록하기

  1. 사용자 등록 화면 구현

  • register.ejs 파일을 만들어 아이디, 비밀번호, 비밀번호 확인 필드를 포함함
  • views/ 폴더에 저장
  • form 태그는 POST 방식, action="/register"로 설정함
<form action="/register" method="POST">
  ...
</form>

  1. MongoDB에 사용자 저장

  • User 모델 생성
  • usernamepassword 필드 포함
  • usernameunique: true로 중복 방지함
  1. 비밀번호 암호화 처리

  • bcrypt 사용하여 비밀번호를 해시함
  • 안전하게 암호화하여 DB에 저장함
bcrypt.hash(password, 10);
  1. 비밀번호 검증

  • 입력된 두 비밀번호가 일치하는지 확인
  • 일치할 경우에만 암호화 후 저장함
  1. 사용자 등록 컨트롤러

  • 폼 데이터 받음 → 비밀번호 확인 → 암호화 → DB 저장
  • 저장 완료 후 로그인 페이지로 리다이렉트함

사용자 인증하기

  1. JWT 기반 인증

  • JWT는 헤더, 페이로드, 서명으로 구성됨
  • 로그인 성공 시 토큰 생성 후 쿠키에 저장함
jwt.sign(payload, secretKey, { expiresIn: '1h' });
  1. 인증 흐름

  2. 클라이언트 → 로그인 요청
  3. 서버 → 사용자 확인 후 JWT 생성
  4. JWT → 쿠키에 저장되어 클라이언트에 전달
  5. 이후 요청 시 쿠키의 JWT로 사용자 인증 수행
  1. 필수 모듈

  • jsonwebtoken: JWT 생성 및 검증
  • cookie-parser: 쿠키 파싱용
npm install jsonwebtoken cookie-parser
  1. 로그인 컨트롤러

  • 사용자 조회 → 비밀번호 비교
  • 일치 시 JWT 발급 후 쿠키에 저장하고 /contacts로 이동
res.cookie('token', token).redirect('/contacts');

질문 1: 사용자 인증 로직에서 JWT 토큰을 클라이언트 측 localStorage나 sessionStorage가 아닌 쿠키(cookie) 에 저장하는 이유
질문 2: 비밀번호를 bcrypt로 암호화해서 저장하는 이유
<-굳이 이렇게 복잡하게 하지 않고 그냥 사용자가 입력한 비밀번호를 그대로 데이터베이스에 저장하면 더 편하지 않을까?

0개의 댓글