티스토리 뷰

다음은 회원가입 프로젝트를 이용한 라우트 분리와 게시글의 추가, 수정, 삭제 기능을 물리적으로 합치는 것에 대해서 진행하겠습니다.


소스코드 : https://github.com/swk3169/nodejs-membership



라우트 : 길을 내다


라우트를 분리하는 이유 : 코드의 가독성을 높이기 위해서 코드를 필요에 따라서 길을 나누는 것



기존의 facebook인증을 통한 회원가입 프로젝트 코드


app_passport_facebook_mysql.js


var express = require('express');
var session = require('express-session');
var MySQLStore = require('express-mysql-session')(session);
var bodyParser = require('body-parser');
var bkfd2Password = require("pbkdf2-password");
var passport = require('passport')
var LocalStrategy = require('passport-local').Strategy;
var FacebookStrategy = require('passport-facebook').Strategy;
var hasher = bkfd2Password();
var mysql = require('mysql');
var conn = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '7101',
database: 'o2'
});
conn.connect();
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ // session 미들웨어
secret: '1234DSFs@adf1234!@#&asd',
resave: false,
saveUninitialized: true,
store: new MySQLStore({
host: 'localhost',
port: 3306,
user: '',
password: '',
database: ''
})
}));
app.use(passport.initialize()); // passport 초기화
app.use(passport.session()); // session을 사용
app.get('/count', function (req, res) {
if (req.session.count) {
req.session.count++;
} else {
req.session.count = 1;
}
res.send('count : ' + req.session.count);
});
app.get('/auth/logout', function (req, res) {
req.logout();
req.session.save(function () {
res.redirect('/welcome');
});
});
app.get('/welcome', function (req, res) {
if (req.user && req.user.displayName) { // passport로 부터 받은 유저 객체가 사용자 정보와 같으면
res.send(`
<h1>Hello, ${req.user.displayName}</h1>
<a href="/auth/logout">logout</a>
`);
} else {
res.send(`
<h1>Welcome</h1>
<ul>
<li><a href="/auth/login">Login</a></li>
<li><a href="/auth/register">Register</a></li>
</ul>
`);
}
});
passport.serializeUser(function (user, done) {
console.log('serializeUser', user);
done(null, user.authId);
});

passport.deserializeUser(function (id, done) {
console.log('deserializeUser', id);
var sql = 'SELECT * FROM users WHERE authId=?';
conn.query(sql, [id], function (err, results) {
if (err) {
console.log(err);
done('There is no user.');
} else {
done(null, results[0]);
}
});
// for (var i = 0; i < users.length; i++) {
// var user = users[i];
// if (user.authId === id) {
// return done(null, user);
// }
// }
// done('There is no user');
});
passport.use(new LocalStrategy( // 로컬 전략
function (username, password, done) { // 콜백함수
var uname = username;
var pwd = password;
var sql = 'SELECT * FROM users WHERE authId=?';
conn.query(sql, ['local:' + uname], function (err, results) {
console.log(results);
if (err) {
return done('There is no user.');
}
var user = results[0];
return hasher({ password: pwd, salt: user.salt }, function (err, pass, salt, hash) {
if (hash === user.password) { // 사용자가 있다면
console.log('LocalStrategy', user);
done(null, user); // 로그인 성공
} else { // 사용자가 없다면
done(null, false); // 로그인 실패
}
});
});
}
));
passport.use(new FacebookStrategy({
clientID: '327387394504728',
clientSecret: '7da8b7d304a5c49afba0dd3cea02a60a',
callbackURL: "/auth/facebook/callback",
profileFields: ['id', 'email', 'gender', 'link', 'locale',
'name', 'timezone', 'updated_time', 'verified', 'displayName']
},
function (accessToken, refreshToken, profile, done) {
console.log(profile);
var authId = 'facebook:' + profile.id;
var sql = 'SELECT * FROM users WHERE authId=?';
conn.query(sql, [authId], function(err, results){
if(results.length>0){
done(null, results[0]);
} else {
var newuser = {
'authId': authId,
'displayName': profile.displayName,
'email': profile.emails[0].value
}
var sql = 'INSERT INTO users SET ?';
conn.query(sql, newuser, function(err, results){
if(err){
console.log(err);
done('Error');
} else {
done(null, newuser);
}
})
}
});
}
));
app.post(
'/auth/login',
passport.authenticate( // 미들웨어를 받아서 인증작업을 처리
'local', // 로컬 방식
{
successRedirect: '/welcome', // 로그인 성공
failureRedirect: '/auth/login', // 로그인 실패
failureFlash: false
}
)
);
app.get('/auth/login', function (req, res) {
var output = `
<h1>Login</h1>
<form action="/auth/login" method="post">
<p>
<input type="text" name="username" placeholder="username">
</p>
<p>
<input type="password" name="password" placeholder="password">
</p>
<p>
<input type="submit">
</p>
</form>
<a href="/auth/facebook">facebook</a>
`;

res.send(output);
});
app.get(
'/auth/facebook',
passport.authenticate(
'facebook',
{ scope: 'email' }
)
);
app.get(
'/auth/facebook/callback',
passport.authenticate(
'facebook',
{
successRedirect: '/welcome',
failureRedirect: '/auth/login'
}
)
);

app.post('/auth/register', function (req, res) {
hasher({ password: req.body.password }, function (err, pass, salt, hash) {
var user = {
authId: 'local:' + req.body.username,
username: req.body.username,
password: hash,
salt: salt,
displayName: req.body.displayName
};
var sql = 'INSERT INTO users SET ?';
conn.query(sql, user, function (err, results) {
if (err) {
console.log(err);
res.status(500);
} else {
req.login(user, function (err) {
req.session.save(function () {
res.redirect('/welcome');
});
});
}
});
});
});
app.get('/auth/register', function (req, res) {
var output = `
<h1>Register</h1>
<form action="/auth/register" method="post">
<p>
<input type="text" name="username" placeholder="username">
</p>
<p>
<input type="password" name="password" placeholder="password">
</p>
<p>
<input type="text" name="displayName" placeholder="displayName">
</p>
<p>
<input type="submit">
</p>
</form>
`;
res.send(output);
});
app.listen(3003, function () {
console.log('Connected 3003 port!!!');
});



기존의 게시글 프로젝트 코드


app_mysql.js


var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var multer = require('multer');
var _storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/');
},
filename: function (req, file, cb) {
cb(null, file.originalname);
}
})

var upload = multer({ storage: _storage })
var fs = require('fs');
var mysql = require('mysql');
var conn = mysql.createConnection({
host: 'localhost',
user: '',
password: '',
database: ''
});
conn.connect();
app.use('/user', express.static('uploads'));
app.use(bodyParser.urlencoded({ extended: false }));
app.locals.pretty = true;
app.set('views', './views/mysql');
app.set('view engine', 'pug');
app.get('/upload', function (req, res) {
res.render('upload');
});
app.post('/upload', upload.single('userfile'), function (req, res) {
console.log(req.file);
res.send('Uploaded : ' + req.file.filename);
});
app.get('/topic/add', function (req, res) {
var sql = 'SELECT id,title FROM topic';
conn.query(sql, function (err, topics, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
}
res.render('add', { topics: topics });
});
})
app.post('/topic/add', function (req, res) {
var title = req.body.title;
var description = req.body.description;
var author = req.body.author;
var sql = 'INSERT INTO topic (title, description, author) VALUES(?, ?, ?)';
conn.query(sql, [title, description, author], function (err, result, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
res.redirect('/topic/' + result.insertId);
}
});
})
app.get(['/topic/:id/edit'], function (req, res) {
var sql = 'SELECT id,title FROM topic';
conn.query(sql, function (err, topics, fields) {
var id = req.params.id;
if (id) {
var sql = 'SELECT * FROM topic WHERE id=?';
conn.query(sql, [id], function (err, topic, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
res.render('edit', { topics: topics, topic: topic[0] });
}
});
} else {
console.log('There is no id.');
res.status(500).send('Internal Server Error');
}
});
})
app.post(['/topic/:id/edit'], function (req, res) {
var title = req.body.title;
var description = req.body.description;
var author = req.body.author;
var id = req.params.id;
var sql = 'UPDATE topic SET title=?, description=?, author=? WHERE id=?';
conn.query(sql, [title, description, author, id], function (err, result, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
res.redirect('/topic/' + id);
}
});
});

app.get(['/topic/:id/delete'], function (req, res) {
var sql = 'SELECT id,title FROM topic';
var id = req.params.id;
conn.query(sql, function (err, topics, fields) {
var sql = 'SELECT * FROM topic WHERE id=?';
conn.query(sql, [id], function (err, topic) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
if (topic.length === 0) {
console.log('There is no record.');
res.status(500).send('Internal Server Error');
} else {
res.render('delete', { topics: topics, topic: topic[0] });
}
res.send(topic);
}
});
});
});

app.post(['/topic/:id/delete'], function (req, res) {
var id = req.params.id;
var sql = 'DELETE FROM topic WHERE id=?';
conn.query(sql, [id], function(err, result){
res.redirect('/topic/');
});
});

app.get(['/topic', '/topic/:id'], function (req, res) {
var sql = 'SELECT id,title FROM topic';
conn.query(sql, function (err, topics, fields) {
var id = req.params.id;
if (id) {
var sql = 'SELECT * FROM topic WHERE id=?';
conn.query(sql, [id], function (err, topic, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
res.render('view', { topics: topics, topic: topic[0] });
}
});
} else {
res.render('view', { topics: topics });
}
});
});
app.listen(3000, function () {
console.log('Connected, 3000 port!');
})



이 둘의 코드를 합치기 위해서는 어떻게 해야할까요?


우선 모든 호출된 모듈들을 공통된 기능들로 이루어진 모듈로 합쳐줘야 합니다.


크게 사용된 모듈들은


mysql, express, passport가 있고


기능 별로는 회원가입의 주 기능, 게시글 기능을 라우트로 분리시켜야 합니다.



설정과 관련된 모듈입니다.


환경을 설정하기 위한 모듈들이기 때문에 폴더 이름을 config로 하였습니다.



기능과 관련된 모듈입니다.


길을 찾아서 호출하여 기능들을 실행시켜야 하기 때문에 폴더 이름을 routes로 하였습니다.


다음과 같이 routes라는 폴더를 새롭게 만들고


mysql을 기준으로 코드를 작성하였기 때문에 하위 폴더 이름을 mysql로 하였습니다.


모듈을 담는 공통적인 방법은 


module.exports = function (app) {
return app;
}


다음과 같은 코드로 하나의 함수로 묶을 수 있습니다.


자세한 설명은 코드와 함께 아래에서 설명하겠습니다.



mysql db에 접속하기 위한 코드


db.js


module.exports = function () {
var mysql = require('mysql');
var conn = mysql.createConnection({
host: 'localhost',
user: '',
password: '',
database: ''
});
conn.connect();

return conn;
}


express 모듈을 셋팅하여 웹 사이트를 열기 위한 코드


express.js


module.exports = function () {

var express = require('express');
var session = require('express-session');
var MySQLStore = require('express-mysql-session')(session);
var bodyParser = require('body-parser');

var app = express();
app.set('views', './views/mysql');
app.set('view engine', 'pug');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ // session 미들웨어
secret: '1234DSFs@adf1234!@#&asd',
resave: false,
saveUninitialized: true,
store: new MySQLStore({
host: 'localhost',
port: 3306,
user: '',
password: '',
database: ''
})
}));

return app;
}


passport 모듈을 사용하여 인증과 관련된 작업을 하기위한 코드


passport.js


module.exports = function (app) {
var conn = require('./db')();
var bkfd2Password = require("pbkdf2-password");
var passport = require('passport')
var LocalStrategy = require('passport-local').Strategy;
var FacebookStrategy = require('passport-facebook').Strategy;
var hasher = bkfd2Password();
app.use(passport.initialize()); // passport 초기화
app.use(passport.session()); // session을 사용

passport.serializeUser(function (user, done) {
console.log('serializeUser', user);
done(null, user.authId);
});
passport.deserializeUser(function (id, done) {
console.log('deserializeUser', id);
var sql = 'SELECT * FROM users WHERE authId=?';
conn.query(sql, [id], function (err, results) {
if (err) {
console.log(err);
done('There is no user.');
} else {
done(null, results[0]);
}
});
});
passport.use(new LocalStrategy( // 로컬 전략
function (username, password, done) { // 콜백함수
var uname = username;
var pwd = password;
var sql = 'SELECT * FROM users WHERE authId=?';
conn.query(sql, ['local:' + uname], function (err, results) {
console.log(results);
if (err) {
return done('There is no user.');
}
var user = results[0];
return hasher({ password: pwd, salt: user.salt }, function (err, pass, salt, hash) {
if (hash === user.password) { // 사용자가 있다면
console.log('LocalStrategy', user);
done(null, user); // 로그인 성공
} else { // 사용자가 없다면
done(null, false); // 로그인 실패
}
});
});
}
));
passport.use(new FacebookStrategy({
clientID: '',
clientSecret: '',
callbackURL: "/auth/facebook/callback",
profileFields: ['id', 'email', 'gender', 'link', 'locale',
'name', 'timezone', 'updated_time', 'verified', 'displayName']
},
function (accessToken, refreshToken, profile, done) {
console.log(profile);
var authId = 'facebook:' + profile.id;
var sql = 'SELECT * FROM users WHERE authId=?';
conn.query(sql, [authId], function (err, results) {
if (results.length > 0) {
done(null, results[0]);
} else {
var newuser = {
'authId': authId,
'displayName': profile.displayName,
'email': profile.emails[0].value
}
var sql = 'INSERT INTO users SET ?';
conn.query(sql, newuser, function (err, results) {
if (err) {
console.log(err);
done('Error');
} else {
done(null, newuser);
}
})
}
});
}
));
return passport;
}


새로운 facebook인증을 통한 회원가입 프로젝트 코드


auth.js


module.exports = function (passport) {
var bkfd2Password = require("pbkdf2-password");
var hasher = bkfd2Password();
var conn = require('../../config/mysql/db')();
var route = require('express').Router();
route.post(
'/login',
passport.authenticate( // 미들웨어를 받아서 인증작업을 처리
'local', // 로컬 방식
{
successRedirect: '/topic', // 로그인 성공
failureRedirect: '/auth/login', // 로그인 실패
failureFlash: false
}
)
);
route.get(
'/facebook',
passport.authenticate(
'facebook',
{ scope: 'email' }
)
);
route.get(
'/facebook/callback',
passport.authenticate(
'facebook',
{
successRedirect: '/topic',
failureRedirect: '/auth/login'
}
)
);
route.post('/register', function (req, res) {
hasher({ password: req.body.password }, function (err, pass, salt, hash) {
var user = {
authId: 'local:' + req.body.username,
username: req.body.username,
password: hash,
salt: salt,
displayName: req.body.displayName
};
var sql = 'INSERT INTO users SET ?';
conn.query(sql, user, function (err, results) {
if (err) {
console.log(err);
res.status(500);
} else {
req.login(user, function (err) {
req.session.save(function () {
res.redirect('/welcome');
});
});
}
});
});
});
route.get('/register', function (req, res) {
var sql = 'SELECT id,title FROM topic';
conn.query(sql, function (err, topics, fields) {
res.render('auth/register', { topics: topics });
});
});
route.get('/login', function (req, res) {
var sql = 'SELECT id,title FROM topic';
conn.query(sql, function (err, topics, fields) {
res.render('auth/login', { topics: topics });
});
});
route.get('/logout', function (req, res) {
req.logout();
req.session.save(function () {
res.redirect('/topic');
});
});
return route;
};


기존의 코드와 차이점이 있다면 하나의 예를 들어서 각 경로가


/auth/login -> /login


으로 바뀐 점을 볼 수 있습니다. 이유는 나중에 실행 시킬 코드에서 경로를 직접 요청할 것이기 때문입니다.


var route = require('express').Router();


또한 express를 라우터로 생성함으로써


인증과 관련된 모든 코드가 


아래의 기존 방식과 다르게


app.get('/auth/login', function (req, res)


다음과 같은 방식으로 수정되었습니다.


app -> route


route.get('/login', function (req, res)


왜냐하면 express를 라우터로 생성하여 route에 할당하였기 때문입니다.


module.exports = function (passport) {
return route;
};


그리고 함수형 프로그래밍을 통하여 route가 생성된 코드를 함수화 하고 


passport 인증 코드를 인자로 받아서 인증 처리를 할 수 있습니다.


app_athentic_mysql.js


var app = require('./config/mysql/express')();    // express를 요청
var passport = require('./config/mysql/passport')(app);    // passport를 요청, 생성된 express를 인자로 받음

var auth = require('./routes/mysql/auth')(passport);    // auth를 요청, 생성된 passport를 인자로 받음
app.use('/auth/', auth);    // auth의 경로를 지정

app.listen(3003, function () {
console.log('Connected 3003 port!!!');
});


라우트로 분리된 모듈들을 main 모듈에 모두 호출합니다.


그리고 app_athentic_mysql.js 모듈을 실행하면 라우트된 모듈들이 따라서 실행이 됩니다.


새로운 게시글 프로젝트


다음은 위와 모두 중복되는 내용이므로 설명은 생략하겠습니다.


topic.js


module.exports = function () {
var route = require('express').Router();
var conn = require('../../config/mysql/db')();
route.get('/add', function (req, res) {
var sql = 'SELECT id,title FROM topic';
conn.query(sql, function (err, topics, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
}
res.render('topic/add', { topics: topics, user: req.user });
});
})
route.post('/add', function (req, res) {
var title = req.body.title;
var description = req.body.description;
var author = req.body.author;
var sql = 'INSERT INTO topic (title, description, author) VALUES(?, ?, ?)';
conn.query(sql, [title, description, author], function (err, result, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
res.redirect('/topic/' + result.insertId);
}
});
})
route.get(['/:id/edit'], function (req, res) {
var sql = 'SELECT id,title FROM topic';
conn.query(sql, function (err, topics, fields) {
var id = req.params.id;
if (id) {
var sql = 'SELECT * FROM topic WHERE id=?';
conn.query(sql, [id], function (err, topic, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
res.render('topic/edit', { topics: topics, topic: topic[0], user:req.user });
}
});
} else {
console.log('There is no id.');
res.status(500).send('Internal Server Error');
}
});
})
route.post(['/:id/edit'], function (req, res) {
var title = req.body.title;
var description = req.body.description;
var author = req.body.author;
var id = req.params.id;
var sql = 'UPDATE topic SET title=?, description=?, author=? WHERE id=?';
conn.query(sql, [title, description, author, id], function (err, result, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
res.redirect('/topic/' + id);
}
});
});

route.get(['/:id/delete'], function (req, res) {
var sql = 'SELECT id,title FROM topic';
var id = req.params.id;
conn.query(sql, function (err, topics, fields) {
var sql = 'SELECT * FROM topic WHERE id=?';
conn.query(sql, [id], function (err, topic) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
if (topic.length === 0) {
console.log('There is no record.');
res.status(500).send('Internal Server Error');
} else {
res.render('topic/delete', { topics: topics, topic: topic[0], user:req.user });
}
res.send(topic);
}
});
});
});

route.post(['/:id/delete'], function (req, res) {
var id = req.params.id;
var sql = 'DELETE FROM topic WHERE id=?';
conn.query(sql, [id], function (err, result) {
res.redirect('/topic/');
});
});

route.get(['/', '/:id'], function (req, res) {
var sql = 'SELECT id,title FROM topic';
conn.query(sql, function (err, topics, fields) {
var id = req.params.id;
if (id) {
var sql = 'SELECT * FROM topic WHERE id=?';
conn.query(sql, [id], function (err, topic, fields) {
if (err) {
console.log(err);
res.status(500).send('Internal Server Error');
} else {
res.render('topic/view', { topics: topics, topic: topic[0], user:req.user });
}
});
} else {
res.render('topic/view', { topics: topics, user:req.user });
}
});
});
return route;
}


app_crud_mysql.js


var app = require('./config/mysql/express')(); // express를 요청
var passport = require('./config/mysql/passport')(app); // passport를 요청, 생성된 express를 인자로 받음
var auth = require('./routes/mysql/auth')(passport); // auth를 요청, 생성된 passport를 인자로 받음
app.use('/auth/', auth); // auth의 경로를 지정

var topic = require('./routes/mysql/topic')(); // topic을 요청
app.use('/topic', topic); // topic의 경로를 지정

app.listen(3003, function () {
console.log('Connected, 3003 port!');
})



해당 코드는 app_athentic_mysql.js 에서 호출되었던 모듈들을 모두 따라서 호출하였기 때문에 합쳐진 코드입니다.


실행 시 app_crud_mysql.js 만 실행을 하여도 인증과 게시글 기능이 모두 실행됩니다.


하지만 백엔드에서 구현한 코드들을 화면으로 보여주기 위해서는 프론트엔드에서도 코드를 구현해줘야 합니다.



html의 코드를 pug라는 템플릿으로 확장하여 더욱 편리하게 코드를 사용할 수 있게 하였습니다.


클라이언트에게 보여주기 위한 UI를 만들기 위하여


views라는 폴더를 만들었습니다.


그리고 인증과 관련된 UI인 auth


게시글 기능과 관련된 UI인 topic으로 나누었습니다.





기존의 html의 코드는


https://html2pug.herokuapp.com/


다음의 사이트에서 쉽게 pug로 변환할 수 있습니다.



공통된 골격을 가진 코드들은 layout.pug에 묶어서 처리하였습니다.


layout.pug


doctype html
html
head
meta(charset='utf-8')
body
ul#account
if user
li
a(href='/auth/logout') Logout
else
li
a(href='/auth/register') Register
li
a(href='/auth/login') Login
h1
a(href='/topic') Server Side JavaScript
ul
each topic in topics
li
a(href='/topic/'+topic.id)= topic.title
block content


다음은 auth와 관련된 코드입니다.


login.pug


extends ../layout
block content
form(action='/auth/login', method='post')
p
input(type='text', name='username', placeholder='username')
p
input(type='password', name='password', placeholder='password')
p
input(type='submit')
a(href='/auth/facebook') facebook


register.pug


extends ../layout
block content
form(action='/auth/register', method='post')
p
input(type='text', name='username', placeholder='username')
p
input(type='password', name='password', placeholder='password')
p
input(type='text', name='displayName', placeholder='displayName')
p
input(type='submit')


다음은 topic과 관련된 코드입니다.


add.pug


extends ../layout
block content
article
form(action='/topic/add' method='post')
p
input(type='text' name='title' placeholder='title')
p
textarea(name='description' placeholder='description')
p
input(type='text' name='author' placeholder='author')
p
input(type='submit')


delete.pug


extends ../layout
block content
article
h1= 'Delete? '+topic.title
form(action='/topic/'+topic.id+'/delete' method='post')
p
input(type='submit' value='YES')
a(href='/topic/'+topic.id) NO


edit.pug


extends ../layout
block content
article
form(action='/topic/'+topic.id+'/edit' method='post')
p
input(type='text' name='title' value=topic.title placeholder='title')
p
textarea(name='description' placeholder='description')
=topic.description
p
input(type='text' name='author' value=topic.author placeholder='author')
p
input(type='submit')
ul
li
a(href='/topic/add') add


upload.pug


doctype html
html
head
meta(charset='utf-8')
body
form(action='upload' method='post' enctype="multipart/form-data")
input(type='file' name='userfile')
input(type='submit')


view.pug


extends ../layout
block content
article
if topic
h2= topic.title
= topic.description
div= 'by '+topic.author
else
h2 Welcome
| This is server side javascript tutorial.
ul
li
a(href='/topic/add') add
if topic
li
a(href='/topic/'+topic.id+'/edit') edit
li
a(href='/topic/'+topic.id+'/delete') delete




코드들의 공통점이 있다면

extends ../layout
block content


layout.pug를 제외한 모든 코드들이


다음과 같이 extends와 block을 선언하고 있습니다.


공통된 코드들을 layout에 담았기 때문에


하위 파일들은 extends를 통하여 layout의 코드를 사용할 수 있고,


block을 통하여 html 내에 들어갈 수 있는 코드를 만들 수 있습니다.


app을 실행한 화면입니다.


인증과 기능들이 합쳐진 모습을 확인할 수 있습니다.



이로써 회원가입 프로젝트를 이용한 라우트 분리와 게시글 기능 추가에 대한 설명을 마치겠습니다.


댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크