Dwitter – 서버구조 MVC 패턴으로 변경 (2)

MVC 이란?

어플리케이션을 Model, View, Controller 로 나눠서 가독성 / 유지보수 / 확장성을 늘리는 소프트웨어 디자인 패턴

  • Model
    • 어플리케이션에서 필요한 데이터를 담는 영역
  • View
    • 사용자에게 보여지는 순수 어플리케이션의 UI
  • Controller
    • View 와 Model을 연결하는 비즈니스 로직을 담는 영역
어플리케이션에서의 MVC 패턴
서버에서의 MVC 패턴

Dwitter 서버에 Data층 만들기 ✏️

Router (/router/tweets.js)

import express from 'express';
import 'express-async-errors';
import * as tweetRepository from '../data/tweet.js';

const router = express.Router();


// GET /tweets
// GET /tweets?username=:username
router.get('/',(req,res,next) => {
    const username =  req.query.username;
    const data = username
    ? tweetRepository.getAllByUsername(username)
    : tweetRepository.getAll();
    res.status(200).json(data);
});

// GET /tweets/:id
router.get('/:id',(req,res,next) => {
    const id = req.params.id;
    const tweet = tweetRepository.getById(id);
    if(tweet){
        res.status(200).json(tweet);
    }else {
        res.status(404).json({message : `Tweet id(${id}) not found`});
    }
});

// POST /tweets
router.post('/',(req,res,next) => {
    const {text, name, username} = req.body;
    const tweet = tweetRepository.create(text, name, username);
    res.status(201).json(tweet);
});

// PUT /tweets/:id
router.put('/:id',(req,res,next) => {
    const id = req.params.id;
    const text = req.body.text;
    const tweet = tweetRepository.update(id, text);
    if(tweet){
        res.status(200).json(tweet);
    }else {
        res.status(404).json({message : `Tweet id(${id}) not found`});
    }
});

// DELETE /tweets/:id
router.delete('/:id',(req,res,next) => {
    const id = req.params.id;
    tweetRepository.remove(id);
    res.sendStatus(204);
});

export default router;

데이터 처리 (/data/tweet.js)

let tweets = [
    {
        id: '1',
        text: 'Hello world!',
        createdAt: Date.now().toString(),
        name: 'Dylan',
        username: 'dylan',
        url: 'https://cdn-icons-png.flaticon.com/512/3576/3576887.png'
    },
    {
        id: '2',
        text: 'New message :)',
        createdAt: Date.now().toString(),
        name: 'Bob',
        username: 'bob',
        url: 'https://cdn-icons-png.flaticon.com/512/3576/3576887.png'
    },    
];

export function getAll(){
    return tweets;
}

export function getAllByUsername(username){
    return tweets.filter((tweet)=> tweet.username === username);
}

export function getById(id){
    return tweets.find(tweet=> tweet.id === id);
}

export function create(text, name, username){
    const tweet = {
        id : Date.now().toString(),
        text,
        createdAt : new Date(),
        name,
        username,
    };
    tweets = [tweet, ...tweets];
    return tweets;
}

export function update(id, text){
    const tweet = tweets.find(tweet => tweet.id === id);
    if(tweet){
        tweet.text= text;
    }
    return tweet;
}

export function remove(id){
    tweets = tweets.filter(tweet=> tweet.id !== id);
}

Dwitter 서버에 Controller 층 만들기 ✏️

Router(/router/tweets.js)

import express from 'express';
import 'express-async-errors';
import * as tweetController from '../controller/tweet.js';

const router = express.Router();


// GET /tweets
// GET /tweets?username=:username
router.get('/', tweetController.getTweets);

// GET /tweets/:id
router.get('/:id',tweetController.getTweet);

// POST /tweets
router.post('/',tweetController.createTweet);

// PUT /tweets/:id
router.put('/:id',tweetController.updateTweet);

// DELETE /tweets/:id
router.delete('/:id',tweetController.deleteTweet);

export default router;

Controller (/controller/tweet.js)

import * as tweetRepository from '../data/tweet.js';

export function getTweets(req,res) {
    const username =  req.query.username;
    const data = username
    ? tweetRepository.getAllByUsername(username)
    : tweetRepository.getAll();
    res.status(200).json(data);
};

export function getTweet(req,res){
    const id =  req.params.id;
    const tweet = tweetRepository.getById(id);
    if(tweet){
        res.status(200).json(tweet);
    } else {
        res.status(404).json({ message : `Tweet id (${id}) not found`});
    }
}

export function createTweet(req,res) {
    const {text, name, username} = req.body;
    const tweet = tweetRepository.create(text, name, username);
    res.status(201).json(tweet);
}

export function updateTweet(req,res){
    const id = req.params.id;
    const text = req.body.text;
    const tweet = tweetRepository.update(id, text);
    if(tweet){
        res.status(200).json(tweet);
    }else {
        res.status(404).json({message : `Tweet id(${id}) not found`});
    }
}

export function deleteTweet(req,res){
    const id = req.params.id;
    tweetRepository.remove(id);
    res.sendStatus(204);
}

Async /Await 로 변경

보통 컨트롤러에서는 DB 혹은 파일에서 데이터를 읽어오기때문에 async 로 모두 변경처리해야한다.

데이터 처리 부분 (/data/tweet.js)

let tweets = [
    {
        id: '1',
        text: 'Hello world!',
        createdAt: Date.now().toString(),
        name: 'Dylan',
        username: 'dylan',
        url: 'https://cdn-icons-png.flaticon.com/512/3576/3576887.png'
    },
    {
        id: '2',
        text: 'New message :)',
        createdAt: Date.now().toString(),
        name: 'Bob',
        username: 'bob',
        url: 'https://cdn-icons-png.flaticon.com/512/3576/3576887.png'
    },    
];

export async function getAll(){
    return tweets; // async 가 붙으면 모두 promise로 리턴
}

export async function getAllByUsername(username){
    return tweets.filter((tweet)=> tweet.username === username); // async 가 붙으면 모두 promise로 리턴
}

export async function getById(id){
    return tweets.find(tweet=> tweet.id === id); // async 가 붙으면 모두 promise로 리턴
}

export async function create(text, name, username){
    const tweet = {
        id : Date.now().toString(),
        text,
        createdAt : new Date(),
        name,
        username,
    };
    tweets = [tweet, ...tweets];
    return tweets; // async 가 붙으면 모두 promise로 리턴
}

export async function update(id, text){
    const tweet = tweets.find(tweet => tweet.id === id);
    if(tweet){
        tweet.text= text;
    }
    return tweet; // async 가 붙으면 모두 promise로 리턴
}

export async function remove(id){
    tweets = tweets.filter(tweet=> tweet.id !== id);
}

컨트롤러 부분 (/controller/tweet.js)

import * as tweetRepository from '../data/tweet.js';

export async function getTweets(req,res) {
    const username =  req.query.username;
    const data = await (username
    ? tweetRepository.getAllByUsername(username)
    : tweetRepository.getAll());
    res.status(200).json(data);
};

export async function getTweet(req,res){
    const id =  req.params.id;
    const tweet = await tweetRepository.getById(id);
    if(tweet){
        res.status(200).json(tweet);
    } else {
        res.status(404).json({ message : `Tweet id (${id}) not found`});
    }
}

export async function createTweet(req,res) {
    const {text, name, username} = req.body;
    const tweet = await tweetRepository.create(text, name, username);
    res.status(201).json(tweet);
}

export async function updateTweet(req,res){
    const id = req.params.id;
    const text = req.body.text;
    const tweet = await tweetRepository.update(id, text);
    if(tweet){
        res.status(200).json(tweet);
    }else {
        res.status(404).json({message : `Tweet id(${id}) not found`});
    }
}

export async function deleteTweet(req,res){
    const id = req.params.id;
    await tweetRepository.remove(id);
    res.sendStatus(204);
}

마지막 점검

구조 / 패턴 변경후 RUN 테스트 🙂 잘 동작하는 모습 👏👏