본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.
mongoose는 MongoDB에 데이터를 쉽게 읽고 쓰게 해주는 JavaScript 라이브러리
mongoose를 ODM(Object Document Mapper)이라고도 부름
ODM(Object Document Mapper)이란, JavaScript의 객체(Object)와 MongoDB의 문서(Document) 사이에서 ‘매핑’을 수행하는 도구
즉, 객체(Object)를 MongoDB 데이터베이스의 문서(Document)로 쉽게 변환하거나, 반대로 문서를 객체로 변환해주는 작업을 수행
MongoDB에서 가지고 있는 각 데이터 하나하나를 문서(Document)라고 정의
1개 이상의 Key-Value 쌍 형태, JSON 형식으로 구성
{
"_id": ObjectId("6682192a1c155bd2f27881"),
"name": "lyw",
}
여러개의 문서(Document)를 보유할 수 있는 MongoDB의 구성요소
JSON 형식의 여러가지 문서(Document)를 보유할 수 있
관계형 데이터베이스(RDB)의 Table과 동일한 역할
컬렉션(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 값과 존재하지 않는 필드null
String
: 문자열"mongoDB"
Number
: 숫자3.14
Date
: 날짜new Date()
Buffer
: 파일을 담을 수 있는 버퍼, UTF-8이 아닌 문자열을 저장0x65
Boolean
: true
or false
true
ObjectId
(Schema.Types.ObjectId) : 객체 ID, 주로 다른 객체를 참조할 때 넣음ObjectId()
Array
: 배열 형태의 값["a", "b", "c"]
// 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;
# 프로젝트를 초기화합니다.
yarn init -y
# express와 mongoose를 yarn을 이용해 설치합니다.
yarn add express mongoose
// 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"
}
}
// 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, '포트로 서버가 열렸어요!');
});
// 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, '포트로 서버가 열렸어요!');
});
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 설계하기
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);
routes
폴더에 각 CRUD API 구현하면 됨
코드 내용이 너무 길어서 코드는 생략
미들웨어는 웹 서버에서 요청을 받을때, 모든 요청에 대한 공통적인 처리를 의미
미들웨어(Middleware)는 서버의 요청(Request)-응답(Response) 과정에서 중간에 위치하여 특정 기능을 수행하는 함수라고 할 수도 있음
// app.js
...
// req.body의 json 형태의 데이터를 읽기 위해서 사용
app.use(express.json());
// req.body의 form 데이터를 읽기 위해 사용
app.use(express.urlencoded({ extended: true }));
...
req
: 요청(Request)에 대한 정보가 담겨있는 객체res
: 응답(Response)을 위한 기능이 제공next
: 다음 스택으로 정의된 미들웨어를 호출app.use((req, res, next) => {
console.log('Request URL:', req.originalUrl, ' - ', new Date());
next();
});
Joi는 JavaScript 유효성 검증을 위한 라이브러리
Joi 설치하기
# yarn을 이용해 Joi를 설치
yarn add 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의 에러 처리 미들웨어
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, '포트로 서버가 열렸어요!');
});
기본적인 틀을 구현할 예정
강의 내용을 기반으로 데이터베이스 연결과 API를 구현할 예정
필수 구현 사항을 최우선으로 구현
2주차 나머지 내용을 모두 시청함
이번 개인 과제는 사실 2주차 내용을 연습하는 과제 같음
그렇기에 약간의 복습 후 코드 구현에 들어갈 예정
사실 2주차에 제일 어려웠던 부분은 AWS E2C를 이해하는 것
당장은 완벽히 이해하기 어려우니 설정하는 방법을 외우고 익히는 것을 우선해야 함