백엔드 기능들을 MVC 패턴으로 분리하기
이전에 작업했던 최신 영화 정보 웹 사이트 구현 프로젝트를 MVC 패턴으로 리팩토링하는 작업을 진행했다.
아래 코드는 root 주소에 접근 시 MongoDB Atlas에서 영화 정보를 가져와 프론트에 보내주는 API이다.
express 기반으로 백엔드를 구현하고, ejs로 웹사이트를 꾸며준다.
// server.js
require('dotenv').config();
const express = require('express');
const app = express();
const path = require('path');
const { MongoClient } = require('mongodb');
const PORT = 3000;
const HOST = '0.0.0.0';
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.json());
app.use('/static', express.static(path.join(__dirname, 'public')));
// MongoDB
const client = new MongoClient(process.env.MONGODB_URL);
// movies get (여기 부분을 수정합니다.)
app.get('/', async (req, res) => {
try {
await client.connect();
const db = client.db('mydatabase');
const collection = db.collection('mycollection');
console.log('Connected to MongoDB');
const query = {};
const movies = await collection.find(query).toArray();
res.render('index', {
movies: movies,
});
} catch (e) {
throw new Error('Connection to MongoDB Atlas failed');
} finally {
await client.close();
console.log('Disconnected from MongoDB Atlas');
}
});
MVC 패턴에서 Controllers를 주로 API 요청이 들어왔을 때 response를 위한 함수를 담는다.
위 코드에서는 app.get() 함수 작동 부분을 Controller.js로 이동이 가능하다.
아래는 그 결과이다.
// movies.controllers.js
const { MongoClient } = require('mongodb');
const client = new MongoClient(process.env.MONGODB_URL);
const getMovies = async (req, res) => {
try {
await client.connect();
const db = client.db('mydatabase');
const collection = db.collection('mycollection');
console.log('Connected to MongoDB');
const query = {};
const movies = await collection.find(query).toArray();
res.render('index', {
movies: movies,
});
} catch (e) {
throw new Error('Connection to MongoDB Atlas failed');
} finally {
await client.close();
console.log('Disconnected from MongoDB Atlas');
}
};
module.exports = {
getMovies,
};
실제로 함수가 작동되는 부분을 드러내 controllers 폴더 내 movies.controllers.js파일에 getMovies라는 변수에 담았다.
그리고 나서 getMovies 함수를 exports하여 외부에 공개한다. 이렇게 해서 server.js와 연결하기 위해서다.
또한 mongoDB Atlas 접속에 필요한 모듈과 클라이언트 접속 관련 코드도 server.js에서 옮겨왔다.
// server.js
require('dotenv').config();
const path = require('path');
const express = require('express');
// 수정된 부분
const movieController = require('./controllers/movies.controllers');
const PORT = 3000;
const HOST = '0.0.0.0';
const app = express();
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.json());
app.use('/static', express.static(path.join(__dirname, 'public')));
// 수정된 부분
app.get('/', movieController.getMovies);
server.js에서는 controllers로 옮겨간 getMovies 코드 모듈을 require하여 movieController라는 변수에 담았다.
root 페이지를 위한 app.get 메서드는 root 페이지 요청이 들어올 경우 즉시 movies.controllers.js 파일에 있는 getMovies가 실행되도록 하였다. 이렇게 server.js 파일에는 request와 response 컨트롤을 위한 기능을 분리하였다.
MongoDB 접속 기능과 API 기능의 분리
이전에 본 getMovies 함수를 다시 살펴보면 현재 get 요청 시 mongoDB에 접속하고, 데이터를 조회하고, 접속종료를 하는 순서로 진행되고 있음을 볼 수 있다. 여기서 MongoDB 접속 부분을 생각해보면, 데이터베이스를 조회해야하는 모든 API는 '데이터베이스 접속', '데이터베이스 접속 종료' 이 과정이 항상 따라다니게 될 것이다. 더 나아가 다른 데이터베이스나 컬렉션에 접근해야하는 경우도 있겠지만 그렇다해도 접속 - 접속 종료 패턴은 변하지 않는다.
그래서 MongoDB 접속과 접속 종료 코드를 매 API 구현 시 복붙하지 않도록 모듈화 하기로 했다.
// (기존) movies.controllers.js
const getMovies = async (req, res) => {
try {
await client.connect();
const db = client.db('mydatabase');
const collection = db.collection('mycollection');
console.log('Connected to MongoDB');
// 해당 부분까지가 mongoDB 접속 관련 코드이다.
const query = {};
const movies = await collection.find(query).toArray();
res.render('index', {
movies: movies,
});
// 여기서부터는 접속 실패시 에러 코드 발동 및 접속 종료 코드이다.
} catch (e) {
throw new Error('Connection to MongoDB Atlas failed');
} finally {
await client.close();
console.log('Disconnected from MongoDB Atlas');
}
};
위 코드를 아래와 같이 수정했다.
// mongodb.js
const { MongoClient } = require('mongodb');
const client = new MongoClient(process.env.MONGODB_URL);
const connect = async (databaseName, collectionName) => {
try {
await client.connect();
const db = client.db(databaseName);
const collection = db.collection(collectionName);
console.log(`Connected to MongoDB Atlas: ${databaseName}, ${collectionName}`);
return collection; // 컬렉션 정보를 리턴합니다.
} catch (e) {
throw new Error('Connection to MongoDB Atlas failed');
}
};
const disconnect = async () => {
await client.close();
console.log('Disconnected from MongoDB Atlas');
};
module.exports = {
connect,
disconnect,
};
먼저 mongodb.js 파일을 만들어 접속 - 종료에 해당하는 코드를 옮겨왔고, 접속하고자 하는 데이터베이스 또는 컬렉션을 선택할 수 있도록 connect 함수의 변수로 전달받게 했다. 데이터 조회 부분에서는 연결된 컬렉션 정보만 필요하므로 이를 리턴하여 데이터 조회를 위한 함수에 전달해주는 코드를 구상했다.
// movies.controllers.js
const mongoDB = require('../mongodb');
const getMovies = async (req, res) => {
const collection = await mongoDB.connect('mydatabase', 'mycollection');
const query = {};
const movies = await collection.find(query).toArray();
await mongoDB.disconnect();
res.render('index', {
movies: movies,
});
movies.controllers.js 파일에 mongoDB.connect와 disconnect메소드를 가져와서 코드를 간결화하였다.
이후 reviews에 대한 데이터를 조회하거나, post, put, delete 등의 API를 작성할 때도 유용하게 사용될 것으로 보인다.
EJS로 동적 결과를 HTML에 담기
아래 코드를 보면 데이터 조회 결과를 res.render 메서드로 어딘가에 전달하는 것을 확인할 수 있다.
// server.js
const query = {};
const movies = await collection.find(query).toArray();
await mongoDB.disconnect();
res.render('index', {
movies: movies,
});
res.render에서 'index'는 index.ejs 파일을 랜더링함을 의미하고, 추가적으로 데이터 조회 결과를 담은 movies 배열을 함께 전달한다. 이 때 movies 파일은 response를 위한 json 변환 과정이 따로 필요하지 않다.
우선 사전에 ejs파일을 생성해둬야 하는데 이는 html과 동일한 문법을 사용하면서 자바스크립트 코드를 함께 동작시킬 수 있다.
<!-- index.ejs -->
<div class="cards">
<div class="row row-cols-1 row-cols-md-4 g-4" id="cards-box">
<!-- <h1 style="border: solid 2px red;">여기에 영화소개 카드가 들어갑니다.</h1> -->
<% movies.forEach((movie, idx)=> { %>
<div class="col">
<div class="card h-100">
<img src=<%= movie.poster_url %> class="card-img-top">
<div class="card-body">
<a href="/reviews/<%= movie.movie_id %>?page=1&limit=10" class="card-title">
<%= idx + 1 %>. <%= movie.title %>
</a>
<p class="card-text">
<%= '⭐' .repeat(movie.grade) %> (<%= movie.grade %>)
</p>
<p class="card-text">
<%= movie.content %>
</p>
<p class="card-text" style="margin-bottom: 0px;">개봉일: <%= movie.open_date %>
</p>
<p class="card-text">예매율: <%= movie.reserve_ratio %>
</p>
</div>
</div>
</div>
<% }) %>
</div>
</div>
이 프로젝트의 경우 영화 소개를 아래 예시처럼 카드 형태로 보여지도록 하였다. 각 카드의 레이아웃은 "cards" 클래스 내 "cards-box" 클래스가 반복적으로 추가되면 되는 방식이므로 controllers에서 가져온 movies 데이터를 forEach를 통해 각각의 영화 정보를 담은 "cards-box" 클래스 태그를 쌓아나가는 것을 확인할 수 있다.
결과적으로 위와 같은 결과를 출력할 수 있다.
'백엔드 개발자(node.js)가 되는 과정' 카테고리의 다른 글
라디오 버튼을 활용하여 선택한 값 가져오기 (0) | 2023.06.01 |
---|---|
자바스크립트 정규표현식 간단 활용 (0) | 2023.06.01 |
[WIL] 2023. 05. 22~26 회고 (0) | 2023.05.30 |
Express로 MVC 패턴에 대한 간략 정리 (0) | 2023.05.26 |
자바스크립트에서의 클래스 구현과 클로저 (0) | 2023.05.25 |