[2024.05.14 TIL] 내일배움캠프 21일차 (Node.js 입문 강의 시청)

My_Code·2024년 5월 15일
0

TIL

목록 보기
27/113
post-thumbnail

본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.


💻 TIL(Today I Learned)

📌 Today I Done

✏️ mongoose 란?

  • mongoose는 MongoDB에 데이터를 쉽게 읽고 쓰게 해주는 JavaScript 라이브러리

  • mongoose를 ODM(Object Document Mapper)이라고도 부름

  • ODM(Object Document Mapper)이란, JavaScript의 객체(Object)와 MongoDB의 문서(Document) 사이에서 ‘매핑’을 수행하는 도구

  • 즉, 객체(Object)를 MongoDB 데이터베이스의 문서(Document)로 쉽게 변환하거나, 반대로 문서를 객체로 변환해주는 작업을 수행


✏️ mongoose의 문서(Document)란?

  • MongoDB에서 가지고 있는 각 데이터 하나하나를 문서(Document)라고 정의

  • 1개 이상의 Key-Value 쌍 형태, JSON 형식으로 구성

{
    "_id": ObjectId("6682192a1c155bd2f27881"),
    "name": "lyw",
}

✏️ mongoose의 컬렉션(Collection)이란?

  • 여러개의 문서(Document)를 보유할 수 있는 MongoDB의 구성요소

  • JSON 형식의 여러가지 문서(Document)를 보유할 수 있

  • 관계형 데이터베이스(RDB)의 Table과 동일한 역할


✏️ mongoose의 스키마(Schema)란?

  • 컬렉션(Collection)에 들어가는 문서(Document)가 어떤 종류의 값을 가질 것인지 정의하기위해 사용

  • 스키마(Schema)는 어떤 필드가 있어야 하는지, 필드는 어떤 데이터 타입을 가져야 하는지를 정의

const UsersSchema = new mongoose.Schema({
    name: String, // 문자열 타입입니다.
    age: Number, // 숫자 타입입니다.
    favorites: [String], // 문자열 배열 타입입니다.
    createdAt: { type: Date, default: Date.now }, // 날짜 타입입니다.
    someId: mongoose.Schema.Types.ObjectId // ObjectId 타입입니다.
});

✏️ 대표적인 스키마의 타입

    • null : null 값과 존재하지 않는 필드
      • ex: null
  • String : 문자열
    • ex: "mongoDB"
  • Number : 숫자
    • ex: 3.14
  • Date : 날짜
    • ex: new Date()
  • Buffer : 파일을 담을 수 있는 버퍼, UTF-8이 아닌 문자열을 저장
    • ex: 0x65
  • Boolean : true or false
    • ex: true
  • ObjectId(Schema.Types.ObjectId) : 객체 ID, 주로 다른 객체를 참조할 때 넣음
    • ex: ObjectId()
  • Array : 배열 형태의 값
    • ex: ["a", "b", "c"]

✏️ mongoose를 이용해 데이터베이스 연결하기

// schemas/index.js

import mongoose from 'mongoose';

const connect = () => {
    mongoose
        .connect(
            'mongodb+srv://<username>:<password>@express-mongo.bwepo2q.mongodb.net/?retryWrites=true&w=majority&appName=express-mongo',
            {
                dbName: 'todo_memo',
            },
        )
        .then(() => console.log('MongoDB 연결에 성공하였습니다.'))
        .catch(() => console.log(`MongoDB 연결에 실패하였습니다. ${err}`));
};

mongoose.connection.on('error', (err) => {
    console.error('MongoDB 연결 에러', err);
});

export default connect;

✏️ API 서버 준비하기

  • 1) 프로젝트 초기화하기
    • 먼저, 프로젝트 폴더를 생성하고 express, mongoose 라이브러리를 설치
# 프로젝트를 초기화합니다.
yarn init -y

# express와 mongoose를 yarn을 이용해 설치합니다.
yarn add express mongoose
  • 2) yarn을 이용해 생성된 package.json 파일에서 type을 module로 꼭! 변경
// package.json

{
    "name": "spa_todo",
    "version": "1.0.0",
    "main": "app.js",
    "license": "MIT",
    "type": "module",
    "dependencies": {
        "express": "^4.19.2",
        "joi": "^17.13.1",
        "mongoose": "^8.3.4"
    },
    "devDependencies": {
        "prettier": "^3.2.5"
    }
}
  • 3) 간단한 HTTP 요청을 받을 수 있도록 express 라이브러리를 이용하여 서버 만들기
// app.js

import express from 'express';

const app = express();
const PORT = 3000;

// Express에서 req.body에 접근하여 body 데이터를 사용할 수 있도록 설정합니다.
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

const router = express.Router();

router.get('/', (req, res) => {
    return res.json({ message: 'Hi!' });
});

// http://localhost:3000/api 경로로 접근하는 경우에만
app.use('/api', router);

app.listen(PORT, () => {
    console.log(PORT, '포트로 서버가 열렸어요!');
});
  • 4) 프론트엔드 파일을 서버에 추가
    • assets 이라는 폴더를 만들고 todo-list-static-files.zip 압축을 해제해서 나온 파일을 모두 넣어줌
  • 5) assets 파일을 서빙할 수 있도록 app.js에 static 미들웨어를 추가
// app.js

import express from 'express';

const app = express();
const PORT = 3000;

// Express에서 req.body에 접근하여 body 데이터를 사용할 수 있도록 설정합니다.
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 정적 파일들을 가져오기 위한 미들웨어
// 기본 localhost:3000 으로 접속하면 assets에 있는 파일이 바로 출력됨
app.use(express.static('./assets'));

const router = express.Router();

router.get('/', (req, res) => {
    return res.json({ message: 'Hi!' });
});

// http://localhost:3000/api 경로로 접근하는 경우에만
app.use('/api', router);

app.listen(PORT, () => {
    console.log(PORT, '포트로 서버가 열렸어요!');
});

✏️ Mongoose Schema 설계하기

  • 1) MongoDB 연결하기 (코드는 위 코드 참조)

  • 2) app.js에 mongoose 연결

// app.js

import express from 'express';
import connect from './schemas/index.js';

const app = express();
const PORT = 3000;

connect();  // schemas/index.js에서 작성한 mongoose 코드를 서버에 연결

// Express에서 req.body에 접근하여 body 데이터를 사용할 수 있도록 설정합니다.
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 정적 파일들을 가져오기 위한 미들웨어
// 기본 localhost:3000 으로 접속하면 assets에 있는 파일이 바로 출력됨
app.use(express.static('./assets'));

const router = express.Router();

router.get('/', (req, res) => {
    return res.json({ message: 'Hi!' });
});

// http://localhost:3000/api 경로로 접근하는 경우에만
app.use('/api', router);

app.listen(PORT, () => {
    console.log(PORT, '포트로 서버가 열렸어요!');
});
  • 3) Schema 설계하기

    • 필요한 데이터와 형식을 정의하는 것은 스키마(Schema) 설계의 가장 중요한 요소
    • 스키마 설계는 추후에 수정될 가능성을 미리 예측하고, 고려하여 설계
    • 해야 할 일(value):
      • 할 일의 내용을 나타내는 문자열(String) 형식의 데이터
    • 해야 할 일의 순서(order):
      • 할 일의 순서를 나타내는 숫자(Number) 형식의 데이터
    • 완료 날짜(doneAt):
      • 할 일이 언제 완료되었는지 나타내는 날짜(Date) 형식의 데이터
      • 완료되지 않았다면 null, 완료 되었다면 날짜(Date) 형식의 데이터를 가짐
  • 4) mongoose - Todo 모델 작성하기

// schemas/todo.schema.js

import mongoose from 'mongoose';

const TodoSchema = mongoose.Schema({
    value: {
        type: String,
        required: true,
    },
    order: {
        type: Number,
        required: true,
    },
    doneAt: {
        type: Date,
        required: false,
    },
});

TodoSchema.virtual('todoId').get(function () {
    return this._id.toHexString();
});
TodoSchema.set('toJSON', {
    virtuals: true,
});

export default mongoose.model('Todo', TodoSchema);

✏️ Mongoose CRUD 구현하기

  • routes 폴더에 각 CRUD API 구현하면 됨

  • 코드 내용이 너무 길어서 코드는 생략


✏️ 미들웨어 (Middleware)

  • 미들웨어는 웹 서버에서 요청을 받을때, 모든 요청에 대한 공통적인 처리를 의미

  • 미들웨어(Middleware)는 서버의 요청(Request)-응답(Response) 과정에서 중간에 위치하여 특정 기능을 수행하는 함수라고 할 수도 있음

// app.js

...

// req.body의 json 형태의 데이터를 읽기 위해서 사용
app.use(express.json());  
// req.body의 form 데이터를 읽기 위해 사용
app.use(express.urlencoded({ extended: true }));  

...
  • Request 로그 남기는 미들웨어 작성
    • req: 요청(Request)에 대한 정보가 담겨있는 객체
      • HTTP Headers, Query Parameters, URL 등 브라우저가 서버로 보내는 정보들이 담겨있음
    • res: 응답(Response)을 위한 기능이 제공
      • 어떤 HTTP Status Code로 응답 할지, 어떤 데이터 형식으로 응답 할지, 헤더는 어떤 값을 넣어 응답 할지 다양한 기능을 제공
    • next: 다음 스택으로 정의된 미들웨어를 호출
app.use((req, res, next) => {
    console.log('Request URL:', req.originalUrl, ' - ', new Date());
    next();
});

✏️ 데이터 유효성 검증 라이브러리 Joi

  • Joi는 JavaScript 유효성 검증을 위한 라이브러리

  • Joi 설치하기

# yarn을 이용해 Joi를 설치
yarn add joi
  • 코드에 Joi 적용하기
// routes/todos.route.js

import express from 'express';
import joi from 'joi';
import Todo from '../schemas/todo.schema.js';

const router = express.Router();

const createdTodoSchema = joi.object({
    value: joi.string().min(1).max(50).required(),
});

// 할일 등록 API
router.post('/todos', async (req, res, next) => {
    try {
        // 클라이언트가 입력한 데이터 가져오기
        // req.body에서 데이터를 가져올 때 createdTodoSchema라는 유효성 검사를 마치고 값을 가져옴
        // 에러 발생 시 catch문에 의해서 에러 미들웨어로 전달
        const validation = await createdTodoSchema.validateAsync(req.body);

        const { value } = validation;

        // value 데이터가 비어있으면 에러를 반환
        if (!value) {
            return res.status(400).json({
                errorMessage: '해야할 일(value) 데이터가 비어있습니다.',
            });
        }

        // order값을 정하기 위해서 마지막 value의 order 값을 가져오기
        // 읽는 순서가 조금 다름
        // order 필드를 기준으로 내림차순 후 가장 앞에 있는 거 1개 문서(document) 반환
        const todoLastOrder = await Todo.findOne().sort('-order').exec();
        const order = todoLastOrder ? todoLastOrder.order + 1 : 1;

        // 해야할 일 등록하기
        const todo = new Todo({
            value,
            order,
        });
        await todo.save();

        // 클라이언트에게 등록한 할일 반환하기
        return res.status(201).json({ todo: todo });
    } catch (error) {
        // 라우터 다음에 있는 에러처리 미들웨어를 실행
        next(error);
    }
});

...

✏️ 에러 처리 미들웨어

  • 에러원하지 않았던 비즈니스 로직이 수행되지 않도록 하기 위해 사용

  • Express.js의 에러 처리 미들웨어

    • 에러 처리 미들웨어는 Express.js가 공식적으로 제공하는 기능
    • 에러를 인자로 전달받아 클라이언트에게 에러 응답을 반환하거나 다음 미들웨어로 에러를 전달하는 역할을 담당
    • err는 이전 미들웨어에서 발생한 에러를 전달받은 객체
    • req, res는 저희가 일반적으로 사용하는 HTTP 요청과 응답을 관리하는 객체
    • next는 다음 미들웨어를 실행하는 함수
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something broke!');
});
  • 에러 처리 미들웨어 등록하기
// app.js

import express from 'express';
import connect from './schemas/index.js';
import todosRouter from './routes/todos.route.js';
import errorHandlerMiddleware from './middlewares/error-handler.middleware.js';

const app = express();
const PORT = 3000;

connect();

app.use(express.json());  // req.body의 json 형태의 데이터를 읽기 위해서 사용
app.use(express.urlencoded({ extended: true }));  // req.body의 form 데이터를 읽기 위해 사용

// 정적 파일들을 가져오기 위한 미들웨어
// 기본 localhost:3000 으로 접속하면 assets에 있는 파일이 바로 출력됨
app.use(express.static('./assets'));

const router = express.Router();

router.get('/', (req, res) => {
    return res.json({ message: 'Hi!' });
});

// http://127.0.0.1:8080/api 경로로 접근하는 경우에만
// json 미들웨어를 거친 뒤, router로 연결되도록 하는것
app.use('/api', [router, todosRouter]);

// 에러 처리 미드웨어 등록
app.use(errorHandlerMiddleware);

app.listen(PORT, () => {
    console.log(PORT, '포트로 서버가 열렸어요!');
});
  • 에러 처리 미들웨어는 왜 Router 하단에 등록하는 이유는?
    • 미들웨어는 등록된 순서대로 실행
    • 라우터 이전에 에러 처리 미들웨어를 등록하면 라우터에서 발생한 에러를 처리할 수 없음
    • 라우터에서 발생한 에러는 라우터 이후에 등록된 미들웨어로 전달되기 때문에
    • 따라서, 항상 에러 처리 미들웨어는 라우터 설정 코드 하단에 위치해야 함


📌 Tomorrow's Goal

✏️ 개인 과제 진행하기

  • 기본적인 틀을 구현할 예정

  • 강의 내용을 기반으로 데이터베이스 연결과 API를 구현할 예정

  • 필수 구현 사항을 최우선으로 구현



📌 Today's Goal I Done

✔️ Node.js 입문 강의 시청

  • 2주차 나머지 내용을 모두 시청함

  • 이번 개인 과제는 사실 2주차 내용을 연습하는 과제 같음

  • 그렇기에 약간의 복습 후 코드 구현에 들어갈 예정

  • 사실 2주차에 제일 어려웠던 부분은 AWS E2C를 이해하는 것

  • 당장은 완벽히 이해하기 어려우니 설정하는 방법을 외우고 익히는 것을 우선해야 함


profile
조금씩 정리하자!!!

0개의 댓글