본 자료는 Elice 플랫폼의 자료를 사용하여 정리하였습니다.
html
head
title = title
body
h1#greeting 안녕하세요
a.link(href="/") 홈으로
=
를 이용해 전달받은 변수를 사용할 수 있다.each item in arr
if item.name == 'new'
h1 New Document
else
h1 = `${item.name}`
// layout.pug
html
head
title = title
body
block content // layout은 block을 포함한 템플릿
// main.pug
extends layout // extends로 아래 부분에 작성한 HTML 태그가 block부분에 포함된다.
block content
h1 Main Page
// title.pug
h1 = title
// main.pug
extends layout
block content
include title // 자주 반복되는 구문에 사용
div.content
안녕하세요
pre
include. article.txt // 텍스트 파일도 가능하다.
// listItem.pug
mixin listItem(title, name)
tr
td title
td name
// main.pug
include listItem
table
tbody
listItem('제목', '이름') // 파라미터로 값을 넘겨받아 사용한다.
// app.js
app.set('views', path.join(__dirname, 'views')); // 템플릿이 저장되는 디렉토리 지정
app.set('view engine', 'pug'); // 템플릿 엔진 지정
// request handler
res.render('main', { // res.render는 화면을 그리는 기능이다.
title: 'Hello Express', // res.render(템플릿 이름, 템플릿에 전달되는 값)
});
// app.js
app.locals.appName = 'Express' // render함수에 전달되지 않은 값이나 함수를 사용(템플릿에 전역으로 사용될 값을 지정)
// main.pug
h1 = appName
$ express --view=pug myapp
const { nanoid } = require('nanoid'); // 중복없는 문자열을 생성해주는 패키지
const shortId = {
type: String,
default: () => {
return nanoid(); // ObjectId를 대체할 아이디 생성
},
required: true,
index: true,
}
module.exports = shortId;
/posts?write=true
로 작성페이지 접근router.post
이용하여 post 요청 처리res.redirect
이용하여 post 완료 처리// ./routes/posts.js
const { Router } = require('express');
const router = Router();
router.get('/', (req, res, next) => {
if (req.query.write) {
res.render('posts/edit');
return;
}
...
});
...
module.exports = router;
// ./views/posts/edit.pug
...
form(action="/posts", method="post")
table
tbody
tr
th 제목
td: input(type="text" name="title")
tr
th 내용
td: textarea(name="content")
td
td(colspan="2")
input(type="submit" value="등록")
const { Post } = require('./models');
...
router.post('/', async (req, res, next) => {
const { title, content } = req.body;
try {
await Post.create({
title,
content,
});
res.redirect('/');
} catch (err) {
next(err);
}
});
/posts
로 목록페이지 접근<a href='/posts/:shortId'>
이용하여 상세 URL Linkrouter.get('/:shortId')
path parameter 이용하여 요청 처// ./routes/posts.js
router.get('/', async (req, res, next) => {
const posts = await Post.find({});
res.render('posts/list', { posts });
});
// ./views/posts/list.pug
...
table
tbody
each post in posts
tr
td
a(href=`/posts/${post.shortId}`)
= post.title
td= post.author
td= formatDate(post.createdAt)
tfoot
tr
td(colspan="3")
a(href="/posts?write=true")
등록하기
// app.js
const dayjs = require('dayjs');
app.locals.formatDate = (date) => {
return dayjs(date).format('YYYY-MM-DD HH:mm:ss');
}
// ./routes/posts.js
router.get('/:shortId', async (req, res, next) => {
const { shortId } = req.params;
const post = await Post.findOne({ shortId });
if (!post) {
next(new Error('Post NotFound'));
return;
}
...
res.render('posts/view', { post });
});
// ./views/posts/view.pug
...
table
tbody
tr
td(colspan="2")= post.title
tr
td= post.author
td= formatDate(post.createdAt)
tr
td(colspan="2"): pre= post.content
tr
td: a(href=`/posts/${post.shortId}?edit=true`)
수정
td
button(onclick=`deletePost("${post.shortId}")`)
삭제
/posts/{shortId}?edit=true
로 수정페이지 접근<form action="/posts/:shortId" method="post">
로 post 요청 전송// ./routes/posts.js
router.get('/:shortId', async (req, res, next) => {
...
if (req.query.edit) {
res.render('posts/edir', { post });
}
...
});
// ./views/posts/edit.pug
...
- var action = post ? `/posts/${post.shortId}` : "/posts"
form(action=action, method="post")
table
tbody
tr
th 제목
td: input(type="text" name="title" value=post&&post.title)
tr
th 내용
td: textarea(name="content")= post&&post.content
td
td(colspan="2")
- var value = post ? "수정" : "등록"
input(type="submit" value=value)
// ./routes/posts.js
...
router.post('/:shortId', async (req, res, next) => {
const { shortId } = req.params;
const { title, content } = req.body;
const post = await Post.findOneAndUpdate({ shortId }, { title, content });
if (!post) {
next(new Error('Post NotFound'));
return;
}
res.redirect(`/posts/${shortId}`);
}
// posts/view.pug
td
button.delete(
onclick='deletePost("${post.shortId}")'
) 삭제
...
script(type="text/javascript").
function deletePost(shortId) {
fetch('/posts/' + shortId, { method: 'delete' })
.then((res) => {
if (res.ok) {
alert('삭제되었습니다.');
window.location.href = '/posts';
} else {
alert('오류가 발생했습니다.');
console.log(res.statusText);
}
})
.catch((err) => {
console.log(err);
alert('오류가 발생했습니다.');
});
}
// ./routes/posts.js
const { Post } = require('./models');
...
router.delete('/:shortId', async (req, res, next) => {
const { shortId } = req.params;
try {
await Post.delete({ shortId });
res.send('OK');
} catch (err) {
nexst(err);
}
});
promise().catch(next)
async function
, try ~ catch
, next
try ~ catch
, next
를 자동으로 할 수 있도록 구성asyncHandler
는 requestHandler를 매개변수로 갖는 함수형 미들웨어이다.const asyncHandler = (requestHandler) => {
return async (req, res, next) => {
try {
await requestHandler(req, res);
} catch (err) {
next(err);
}
}
}
---
router.get('/', asyncHandler(async (req, res) => {
const posts = await Posts.find({});
if (posts.length < 1) {
throw new Error('Not Found');
}
res.render('posts/list', { posts });
});
router.get(... => {
const page = Number(req.query.page || 1); // 현재 페이지
const perPage = Number(req.query.perPage || 10); // 페이지 당 게시글 수
/posts?page=1&perPate=10
일반적으로 url query를 사용해 전달한다.router.get(... => {
...
const total = await Post.countDocument({});
const posts = await Post.find({});
.sort({ createdAt: -1 })
.skip(perPage * (page -1)) // 검색 시 포함하지 않을 데이터 수
.limit(perPage); // 검색 결과 수 제한
const totalPage = Math.ceil(total / perPage);
mixin pagination(path)
p
- for(let i = 1; i <= totalPage; i++)
a(href=`${path}?page=${i}&perPage=${perPage}`)
if i == page
b= i
else
= i
= " "
---
include pagination
tr
td
+pagination('/posts')
$ pm2 init simple
혹은 $ pm2 init
명령어를 이용하여 pm2 설정파일 예제를 만들 수 있다.$ pm2 start
명령어를 실행하면 어플리케이션을 pm2 데몬으로 실행해준다.module.exports = {
apps : [{
name: 'simple-board',
script: './bin/www',
watch: '.',
ignore_watch: 'views',
}],
};
---
$ pm2 start