게시판 - Back End 개발

소스코드

이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.

이 게시물의 소스코드는 게시판 만들기 / 게시판 - 프로젝트 생성 및 navbar에서 이어집니다.

board.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.

git reset --hard
git pull
git reset --hard 79ba957
git reset --soft 1cd61b1
npm install
atom .

board.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 board.git 을 clone 합니다.

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard 79ba957
git reset --soft 1cd61b1
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/board/tree/79ba9572170952bd888d5a4de98c37875505fb07


이제 게시판 CRUD를 만들텐데 두 파트로 나누어서 이번 글에서 back end 코드를 작성하고, 다음 글에서 front end 코드를 작성하겠습니다.

게시판이라고 해도 주소록 만들기와 크게 다르지 않습니다. 게시판도 결국 데이터를 생성하고, 보여주고, 수정하고, 지우는 CRUD 기능이니까요.

이번 글에서는 Post(게시물) model을 만들고 back end CRUD를 구현해 봅시다.

폴더구조

코드

// models/Post.js

var mongoose = require('mongoose');

// schema
var postSchema = mongoose.Schema({ // 1
  title:{type:String, required:true},
  body:{type:String, required:true},
  createdAt:{type:Date, default:Date.now}, // 2
  updatedAt:{type:Date},
});

// model & export
var Post = mongoose.model('post', postSchema);
module.exports = Post;

1. Post의 schema는 title, body, createdAt, updatedAt으로 구성이 되어 있습니다.

2. default 항목으로 기본 값을 지정할 수 있습니다. 함수명을 넣으면 해당 함수의 return이 기본값이 됩니다.
(Date.now는 현재시간을 리턴하는 함수입니다)

// routes/posts.js

var express  = require('express');
var router = express.Router();
var Post = require('../models/Post');

// Index 
router.get('/', function(req, res){
  Post.find({})                  // 1
  .sort('-createdAt')            // 1
  .exec(function(err, posts){    // 1
    if(err) return res.json(err);
    res.render('posts/index', {posts:posts});
  });
});

// New
router.get('/new', function(req, res){
  res.render('posts/new');
});

// create
router.post('/', function(req, res){
  Post.create(req.body, function(err, post){
    if(err) return res.json(err);
    res.redirect('/posts');
  });
});

// show
router.get('/:id', function(req, res){
  Post.findOne({_id:req.params.id}, function(err, post){
    if(err) return res.json(err);
    res.render('posts/show', {post:post});
  });
});

// edit
router.get('/:id/edit', function(req, res){
  Post.findOne({_id:req.params.id}, function(err, post){
    if(err) return res.json(err);
    res.render('posts/edit', {post:post});
  });
});

// update
router.put('/:id', function(req, res){
  req.body.updatedAt = Date.now(); //2
  Post.findOneAndUpdate({_id:req.params.id}, req.body, function(err, post){
    if(err) return res.json(err);
    res.redirect("/posts/"+req.params.id);
  });
});

// destroy
router.delete('/:id', function(req, res){
  Post.deleteOne({_id:req.params.id}, function(err){
    if(err) return res.json(err);
    res.redirect('/posts');
  });
});

module.exports = router;

route은 1, 2번을 제외하면 주소록 만들기예제의 contacts route와 형태가 거의 같습니다. 앞으로도 CRUD 기능을 만들게 된다면 항상 거의 이 형태를 사용하게 됩니다.

1. 나중에 생성된 data가 위로 오도록 정렬합니다. find와 function 사이에 sort함수가 들어간 형태인데요,
원래 모양은

Post.find({}, function(err, posts){ ... })

이였는데

Post.find({})
  .sort('-createdAt')
  .exec(function(err, posts){ ... })

이렇게 바뀌었죠. 사실 원래모양 역시

Post.find({})
  .exec(function(err, posts){ ... })

를 줄인 표현인데요, .exec함수 앞에 DB에서 데이터를 어떻게 찾을지, 어떻게 정렬할지 등등을 함수로 표현하고, exec안의 함수에서 해당 data를 받아와서 할일을 정하는 구조입니다.

.sort()함수는 string이나 object를 받아서 데이터 정렬방법을 정의하는데요, 문자열로 표현하는 경우 정렬할 항목명을 문자열로 넣으면 오름차순으로 정렬하고, 내림차순인 경우 -를 앞에 붙여줍니다. 두가지 이상으로 정렬하는 경우 빈칸을 넣고 각각의 항목을 적어주면 됩니다. object를 넣는 경우 {createdAt:1}(오름차순), {createdAt:-1}(내림차순) 이런식으로 넣어주면 됩니다.

2. post를 수정하는 경우 수정된 날짜를 updateAt에 기록합니다.

// index.js

...

// Routes
app.use('/', require('./routes/home'));
app.use('/posts', require('./routes/posts')); // 1

...

1. 마지막으로 posts route를 index.js에 추가해 줍니다.

실행결과

아직 views가 없으니 생성한 route를 실행해 볼 수가 없죠. 실행결과는 다음 포스트에서..!

마치며...

model/ route주소록 만들기과 거의 똑같죠?
7 actions로 route를 한번 잘 만들어 놓으면 새로운 route을 만들때 필요한 부분만 수정해서 쓸 수 있습니다.
다음 포스트에서는 이번에 만든 back end를 사용하여 실제 page들을 만들어 보겠습니다.

댓글

박우림 2020.03.20
좋은 글 정말 감사합니다!! 여쭙고싶은게 있습니다!ㅎㅎ
index.js에서 라우터 설정을 보면 // Routes app.use('/', require('./routes/home')); app.use('/posts', require('./routes/posts'));
와 같이 "/", "/posts" 두개의 경로만 지정해줬는데  url에서 /about의 경로로 들어가면 views/home/about.ejs가 열리는 이유가 잘 이해되질 않네요 ㅜ
routes/home.js에서 /about의 경로를 설정해주긴했으나, 메인 js 파일인 index.js에서 /about 경로를 설정하지 않았는데도 이동되는게 납득이 안됩니다 ㅜ
I
Ian H 2020.03.20
@박우림,
app.use(express.static(__dirname+'/public'));를 통해 /public폴더가 static 폴더로 설정되었기 때문입니다.  https://www.a-mean-blog.com/ko/blog/Node-JS-첫걸음/Hello-World/Static-폴더-추가하기 글을 다시 한번 읽어주세요. 감사합니다^^
박우림 2020.03.25
@Ian H,
친절한 설명 감사드립니다.ㅎㅎ 말씀하신 게시글을 읽어보고 왔는데, 죄송하지만 저의 궁금증은 해소되질 않았습니다 ㅜ 한 시간 정도 혼자 고민해봤는데도 납득이 안되네요. 번거롭게 해드려 죄송합니다.
static 폴더로 지정하였기에 "/css" 경로로 들어갈 경우 "/public/css"로 들어가지는것은 이해가 잘 됩니다. 근데 그러하다면, "/"의 경로로 들어갈 때 "/public"의 경로와 "./routes/home"의 경로 두 개가 충돌해야 맞지 않나 싶기도합니다. 억지로 받아들여서 "/public"은 서버에서 호출했을 때의 경로, "./routes/home"은 클라이언트에서 호출했을 때의 경로라고 생각해보기도 했습니다.
이렇게 받아들였을 때 "/"로 링크 이동을 하면 "./routes/home"으로 이동하여 home.js 파일에서 "/"의 경로를 탐색해 "views/home/welcome"으로 최종 이동하는 것까지 이해됐습니다. 근데 문제는 "/about"으로 링크 이동을 하면 왜 "./routes/home"으로 이동되는지가 이해가 안됩니다. 애초에 "/"가 입력될 때 "./routes/home"으로 이동하도록 설정하였기에 "./routes/home"으로 이동한 후에 home.js 파일에서 "views/home/about"으로 최종 이동하는 것이라고 한다면, 추가로 궁금한 점이 생깁니다.
만약 index.js에서 app.use('/posts', require('./routes/posts')); app.use('/users', require('./routes/users')); 와 같은 소스를 추가하지 않는다면, "/posts"나 "/users"로 링크 이동을 하면 "./routes/home"으로 이동한 후에 home.js 파일에서 "/posts"와 "/users"의 경로를 추가 탐색하게 되는건가요? 이럴 경우 home.js에 "/posts"와 "/users"의 경로가 명시되어 있지 않으니 에러가 뜨게 되겠구요. 
제발 제가 생각한게 맞았으면 좋겠네요😢 다시 한번 좋은 강의 감사드리고, 정말 많이 배우고 있습니다. 감사합니다!
I
Ian H 2020.03.25
@박우림,
앗. 이전댓글에서 제가 잘못 설명드렸네요. about 페이지 내용이 static에 가깝다 보니 전 about 페이지가 public 폴더에 있는줄 알고 about 페이지가 보이는 이유가 static 설정 때문이라고 답글드렸어요. 죄송합니다!
정정합니다. about페이지는 static 설정이랑은 상관없구요 다시 설명드릴게요.
app.use('/', require('./routes/home')); 부분때문에 '/'으로 시작하는 route은 일단 무조건 ./route/home.js 파일로 전달이 됩니다. 두번째 댓글에서 생각하신 부분이 맞습니다. 위 코드에서 /users를 입력한다면 home.js에서 users route을 찾게 됩니다. 
home.js파일을 보면 '/'과 '/about' route에 대한 설정이 있죠 그렇기 때문에 /about은 home.js의 /about 라우트로 연결이 되는 겁니다.
몇가지 추가로 정리해 드릴게요. 1. ["/public"은 서버에서 호출했을 때의 경로, "./routes/home"은 클라이언트에서 호출했을 때의 경로] 라고 가정하신 부분은 잘못되었습니다. route을 통한 모든 경로탐색은 클라이언트가 서버로 request를 한 경우에 해당합니다. 2가지로 나누어 지지 않습니다.
2. request가 전달되면 index.js의 코드를 위에어 아래로 순서대로 진행합니다. 아래는 index.js에서 route에 관련된 코드부분입니다.
// Other settings ... app.use(express.static(__dirname+'/public')); ...
// Routes app.use('/', require('./routes/home')); app.use('/posts', require('./routes/posts'));
즉 어떠한 url을 받더라도 우선 static에 해당 url에 맞는 파일이 있는지를 먼저 살피고, 그다음에 route이 '/'로 시작한다면 home.js에 해당 url에 대응하는 route이 있는지 살핀 후 route이 '/posts'로 시작한다면 posts.js에서 route을 찾습니다.
그러므로 동일한 route이 한번 이상 설정된 경우 위의 흐름에 따라 가장 먼저 발견되는 route만 실행됩니다! 그러므로 route을 설정할때는 순서를 잘 생각하여 코드를 작성해야 합니다.
이해가 잘 되셨는지 모르겠어요 ㅠㅠ 이해 안되시면 또 댓글 남겨주세요!
댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP