pug 대신 hbs를 사용합니다. 개인취향입니다.
참고
- Node.js 교과서 조현영 길벗
책 좋습니다. 많이 구매하셔서 저자를 응원합시다!
$ express my-node-sns --view=hbs --css=sass
$ cd my-node-sns
$ npm iNodemon
개발 중 코드가 수정되면 자동으로 테스트 서버를 재기동해 주는 기술입니다. 특별한 설정은 필요하지 않고 실제 사용할 때 명령으로 적용합니다.
$ npm i -g nodemon
$ npm i --save-dev nodemon보안상 중요한 정보를 별도의 파일(.env)로 분리하고 이를 process.env 환경변수에 등록해 주는 기술입니다.
$ npm i dotenv.env
COOKIE_SECRET=secret seedapp.js
// 사용하기로 약속된 .env 파일을 로드하고
// 키=값 형태의 정보를 파싱하여 process.env에 추가한다.
require('dotenv').config();
app.use(cookieParser(process.env.COOKIE_SECRET));서버에 접속한 사용자 정보를 관리하는 세션 객체를 사용할 수 있게 해주는 기술입니다. 세션은 쿠키를 기반으로 작동하므로 쿠키 처리를 위한 미들웨어가 필요합니다. 이는 cookie-parser 패키지가 처리합니다. cookie-parser는 프로젝트 제너레이트 시 자동으로 설정됩니다. 세션 설정은 쿠키 설정 뒤에 해야 합니다.
$ npm i express-sessionapp.js
const session = require('express-session');
app.use(session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
},
}));1회성 메시지를 사용자에게 전달할 필요가 있을 때 사용하는 기술입니다. POST-REDIRECT-GET 패턴을 적용하기 위해서 필요한 기술입니다.
connect-flash
$ npm i connect-flashapp.js
const flash = require('connect-flash');
app.use(flash());자바스크립트에서 이용할 수 있는 ORM 기술입니다.
$ npm i -g sequelize-cli
$ npm i sequelize mysql2
$ sequelize init
Sequelize CLI [Node: 10.14.1, CLI: 5.4.0, ORM: 4.42.0]
Created "config\config.json"
Successfully created models folder at "C:\...\my-node-sns\models".
Successfully created migrations folder at "C:\...\my-node-sns\migrations".
Successfully created seeders folder at "C:\...\my-node-sns\seeders".app.js
const { sequelize } = require('./models');
if (app.get('env') === 'development') {
// If force is true, each Model will run DROP TABLE IF EXISTS,
// before it tries to create its own table
sequelize.sync({
force: true,
}).then(() => {
// Insert Dummy Data for Test
// require('./sql/dummy')();
});
} else {
sequelize.sync();
}config\config.json
{
"development": {
"username": "root",
"password": "1111",
"database": "node_sns_dev",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}다음 명령으로 디비를 새성할 수 있습니다.
$ sequelize db:createmodels\user.js
module.exports = (sequelize, DataTypes) => {
return sequelize.define('user', {
email: {
type: DataTypes.STRING(40),
allowNull: false,
unique: true,
},
nick: {
type: DataTypes.STRING(15),
allowNull: false,
},
password: {
type: DataTypes.STRING(100),
allowNull: true,
},
provider: {
type: DataTypes.STRING(10),
allowNull: false,
defaultValue: 'local',
},
snsId: {
type: DataTypes.STRING(30),
allowNull: true,
},
}, {
timestamps: true,
paranoid: true,
})
}
// CREATE TABLE IF NOT EXISTS `users` (
// `id` INTEGER NOT NULL auto_increment ,
// `email` VARCHAR(40) NOT NULL UNIQUE,
// `nick` VARCHAR(15) NOT NULL,
// `password` VARCHAR(100),
// `provider` VARCHAR(10) NOT NULL DEFAULT 'local',
// `snsId` VARCHAR(30),
// `createdAt` DATETIME NOT NULL,
// `updatedAt` DATETIME NOT NULL,
// `deletedAt` DATETIME,
// PRIMARY KEY (`id`)
// ) ENGINE=InnoDB;models\post.js
module.exports = (sequelize, DataTypes) => {
return sequelize.define('post', {
content: {
type: DataTypes.STRING(140),
allowNull: false,
},
img: {
type: DataTypes.STRING(200),
allowNull: true,
},
}, {
timestamps: true,
paranoid: true,
})
}
// CREATE TABLE IF NOT EXISTS `posts` (
// `id` INTEGER NOT NULL auto_increment ,
// `content` VARCHAR(140) NOT NULL,
// `img` VARCHAR(200),
// `createdAt` DATETIME NOT NULL,
// `updatedAt` DATETIME NOT NULL,
// `deletedAt` DATETIME,
// `userId` INTEGER,
// PRIMARY KEY (`id`),
// FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
// ) ENGINE=InnoDB;models\hashtag.js
module.exports = (sequelize, DataTypes) => {
return sequelize.define('hashtag', {
title: {
type: DataTypes.STRING(40),
allowNull: false,
unique: true,
},
}, {
timestamps: true,
paranoid: true,
})
}
// CREATE TABLE IF NOT EXISTS `hashtags` (
// `id` INTEGER NOT NULL auto_increment ,
// `title` VARCHAR(40) NOT NULL UNIQUE,
// `createdAt` DATETIME NOT NULL,
// `updatedAt` DATETIME NOT NULL,
// `deletedAt` DATETIME,
// PRIMARY KEY (`id`)
// ) ENGINE=InnoDB;models\index.js
const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize = new Sequelize(config.database, config.username, config.password, config);
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.User = require('./user')(sequelize, Sequelize);
db.Post = require('./post')(sequelize, Sequelize);
db.Hashtag = require('./hashtag')(sequelize, Sequelize);
// Post 테이블에 userId 칼럼을 추가한다.
// foreignKey: 'userId' 설정을 생략해도 userId 이름으로 칼럼이 추가된다.
db.User.hasMany(db.Post, {
// sourceKey: 'id',
// foreignKey: 'userId',
onDelete: 'cascade',
});
db.Post.belongsTo(db.User, {
// targetKey: 'id',
// foreignKey: 'userId',
});
// N:M 관계를 1:N 관계로 해소하기 위해서 PostHashtag라는 조인테이블을 생성한다.
// postId, hashtagId 칼럼을 추가한다.
// Post.getHashtags(), Post.addHashtags()
// Hashtag.getPosts(), Hashtag.addPosts() 메소드가 추가된다.
db.Post.belongsToMany(db.Hashtag, {through: 'PostHashtag'});
db.Hashtag.belongsToMany(db.Post, {through: 'PostHashtag'});
// CREATE TABLE IF NOT EXISTS `PostHashtag` (
// `createdAt` DATETIME NOT NULL,
// `updatedAt` DATETIME NOT NULL,
// `postId` INTEGER ,
// `hashtagId` INTEGER ,
// PRIMARY KEY (`postId`, `hashtagId`),
// FOREIGN KEY (`postId`) REFERENCES `posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
// FOREIGN KEY (`hashtagId`) REFERENCES `hashtags` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
// ) ENGINE=InnoDB;
// 하나의 테이블이 N:M 관계를 갖고 있다.
// Follow라는 조인테이블을 생성한다.
// as 옵션은 시퀄라이즈가 조인 시 사용하는 이름이다.
// as 옵션을 바탕으로
// User.getFollowings, User.getFollowers,
// User.addFollwing, User.addFollower 메소드가 추가된다.
db.User.belongsToMany(db.User, {
foreignKey: 'followingId',
as: 'Followers',
through: 'Follow',
});
db.User.belongsToMany(db.User, {
foreignKey: 'followerId',
as: 'Followings',
through: 'Follow',
});
// CREATE TABLE IF NOT EXISTS `Follow` (
// `createdAt` DATETIME NOT NULL,
// `updatedAt` DATETIME NOT NULL,
// `followingId` INTEGER ,
// `followerId` INTEGER ,
// PRIMARY KEY (`followingId`, `followerId`),
// FOREIGN KEY (`followingId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
// FOREIGN KEY (`followerId`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
// ) ENGINE=InnoDB;
module.exports = db;작업결과로써 테이블은 총 5개가 생성되며 테이블 사이에 관계는 다음과 같습니다.
User --1:N-- Follow --N:1-- User --1:N-- Post --1:N-- PostHashtag --N:1-- Hashtag
Follow는 조인 테이블로써 다음 N:M 관계를 해소합니다.
- User는 다수의 팔로워인 User를 가진다.
- User는 다수의 User를 팔로잉할 수 있다.
PostHashtag는 조인 테이블로써 다음 N:M 관계를 해소합니다.
- Post는 다수의 Hashtag를 가진다.
- Hashtag는 다수의 Post에 사용될 수 있다.
사용자 인증을 처리하는 기술입니다. 다양한 프로바이더와 인증을 연동하는 작업 시 매우 편리합니다. bcrypt는 패스워드를 암호화할 때 사용하는 기술입니다.
$ npm i passport passport-local passport-kakao bcrypt- 초기설정:
passport\index.js
- passport.serializeUser(): 무엇을 세션에 저장할 것인지 결정하는 메소드
- passport.deserializeUser(): 어떻게 회원인지 확인할 것인지 결정하는 메소드
- localStrategy, kakaoStrategy: 사용하는 로그인 전략을 등록
app.js
const passport = require('passport');
require('./passport')(passport);- 미들웨어 설정
- 패스포트 초기화 작업: passport.initialize()
- 패스포트 세션 활성: passport.session() ==> passport.deserializeUser()
app.js
app.use(passport.initialize());
app.use(passport.session());passport는 자동으로 req 객체에 login(), logout() 메소드를 추가한다.
- URL로 로그인을 요청한다.
routes\auth.js파일 내 관련 코드가 존재한다.
- passport.authenticate() 메소드를 호출한다.
routes\auth.js파일 내 관련 코드가 존재한다.
- 'local' 로그인 전략을 수행한다.
passport\localStrategy.js파일 내async (email, password, done){ ... }콜백함수가 호출된다.
- 로그인 성공 시 user 객체를 전달하면서 req.login() 메소드를 호출한다.
passport\localStrategy.js파일 내에서done(null, exUser);코드가 수행되면 로그인 성공으로 #4번으로 이어진다.
- req.login() 메소드 내부에서 passport.serializeUser() 메소드에게 전달 된 콜백함수를 호출된다.
passport\index.js파일 내 관련 코드가 존재한다.
- passport.serializeUser() 메소드의 콜백함수가 req.session 객체에 user.id만 저장하도록 요청한다.
passport\index.js파일 내 관련 코드가 존재한다.
- 로그인 성공 후 사용자에게 보여주고 싶은 곳으로 리다이렉트 한다.
routes\auth.js파일 내 관련 코드가 존재한다.
routes\auth.js
// 1: URL로 로그인을 요청한다.
router.post('/login', isNotLoggedIn, (req, res, next) => {
// 2: passport.authenticate() 메소드를 호출한다.
// 3: 'local' 로그인 전략을 수행한다.
// - 콜백함수는 LocalStrategy에서 done() 함수를 호출할 때 호출된다.
passport.authenticate('local', (authError, user, info) => {
// C: 서버 에러(장애 발생)
if (authError) {
console.error(authError);
return next(authError);
}
// B: 잘 못된 정보: 패스워드 or B: 잘 못된 정보: 이메일(아이디)
if (!user) {
req.flash('loginError', info.message);
return res.redirect('/');
}
// A: 성공
// 4: 로그인 성공 시 user 객체를 전달하면서 req.login() 메소드를 호출한다.
// 5: req.login() 메소드 내부에서 passport.serializeUser() 메소드를 호출한다.
// 6: passport.serializeUser() 메소드의 콜백함수가 req.session 객체에 user.id만 저장하도록 요청한다.
return req.login(user, (loginError) => {
if (loginError) {
console.error(loginError);
return next(loginError);
}
// 7: 로그인 성공 후 사용자에게 보여주고 싶은 곳으로 리다이렉트 한다.
return res.redirect('/');
})
})(req, res, next);
// 미들웨어 안에서 미들웨어를 사용할 수 있다.
// 지금 여기서, 라우팅 처리 미들웨어 안에서 인증처리 미들웨어를 사용하고 있다.
// 이 경우, 내부 미들웨어에서 (req, res, next)를 인자로 제공하면서 호출하면 된다.
});세션에 user.id 정보가 저장되었습니다. 이는 로그인 처리가 완료되었음을 의미합니다. 사용자와 관련한 다양한 정보를 라우팅 및 뷰에서 사용할 수 있도록 조치하기 위해서 다음 단계에 처리가 필요합니다.
- 모든 요청에 대해서 app.js 파일 내 설정된 passport.session() 미들웨어가 passport.deserializeUser 메소드를 호출한다.
passport\index.js파일 내 관련 코드가 존재한다.
- req.session에 저장된 user.id로 데이터에비스에서 사용자 정보를 조회한다.
passport\index.js파일 내 관련 코드가 존재한다.
- 조회된 사용자 정보를 req.user 형태로 저장한다.
passport\index.js파일 내 관련 코드가 존재한다.
- 여러 라우터 함수에서 req.user 객체를 사용할 수 있다. 더불어서 뷰에게 req.user 객체를 전달할 수 있다.
app.js
const passport = require('passport');
const passportConfig = require('./passport');
passportConfig(passport);
app.use(passport.initialize());
app.use(passport.session());passport\index.js
const local = require('./localStrategy');
const kakao = require('./kakaoStrategy');
const { User } = require('../models');
// 1. 디비에 회원여부 조회: localStrategy
// 2. 세션에 저장할 정보를 선택: passport.serializeUser
// 3. 뷰가 필요한 인증된 회원정보를 조회: passport.deserializeUser
module.exports = (passport) => {
// req.session 객체에 저장할 데이터를 결정한다.
// user.id만 session에 저장한다.
passport.serializeUser((user, done) => {
done(null, user.id);
});
// passport.session() 미들웨어가 이 메소드를 호출한다.
// passport.serializeUser() 함수가 저장한 id를 받아서
// 데이터베이스에서 정보를 조회한다.
passport.deserializeUser((id, done) => {
User.findOne({
where: {
id
}
})
.then((user) => {
// req.user에 저장한다. ==> 라우터에서 req.user 객체를 사용한다.
done(null, user);
})
.catch((err) => {
done(err);
});
})
// localStrategy 로그인 전략을 등록한다.
local(passport);
// kakaoStrategy 로그인 전략을 등록한다.
kakao(passport);
};passport\localStrategy.js
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
const { User } = require('../models');
module.exports = (passport) => {
passport.use(new LocalStrategy({
// req.body가 갖고 있는 파라미터 중
// 로그인 처리 시 필요한 파라미터의 키를 알려준다.
usernameField: 'email',
passwordField: 'password'
}, async (email, password, done) => {
// 세 번째 파라미터 done은 passport.authenticate() 메소드의 콜백함수를 호출하는 것이다.
// done(authError, user, info)
try {
// email(아이디)로 회원정보를 조회한다.
const exUser = await User.find({
where: {
email
}
});
if (exUser) {
// 패스워드를 암호화 한 다음 데이터베이스에 저장된 exUser.password 값과 비교한다.
const result = await bcrypt.compare(password, exUser.password);
if (result) {
// A: 성공
done(null, exUser);
} else {
// B: 잘 못된 정보: 패스워드
done(null, false, {
message: 'Password is not correct.'
})
}
} else {
// B: 잘 못된 정보: 이메일(아이디)
done(null, false, {
message: 'No matching member information.'
})
}
} catch (err) {
console.error(err);
// C: 서버 에러(장애 발생)
done(err);
}
}));
};passport\kakaoStrategy.js
const KakaoStrategy = require('passport-kakao').Strategy;
const { User } = require('../models');
module.exports = (passport) => {
passport.use(new KakaoStrategy({
// clientID는 카카오가 발급하는 컨슈머 아이디다.
clientID: process.env.KAKAO_ID,
// callbackURL은 카카오로부터 인증 결과를 받을 라우터 주소다.
callbackURL: '/auth/kakao/callback'
}, async (accessToken, refreshToken, profile, done) => {
console.log('accessToken: ' + accessToken);
console.log('refreshToken: ' + refreshToken);
console.log('profile: ' + profile);
// done은 passport.authenticate() 메소드의 콜백함수를 호출한다.
try {
const exUser = await User.find({
where: {
snsId: profile.id,
provider: 'kakao'
}
});
if (exUser) {
// 이미 등록 된 회원이면 바로 passport.authenticate() 메소드의 콜백함수를 호출한다.
done(null, exUser);
} else {
// 신규 회원이면 등록하고 난 후, passport.authenticate() 메소드의 콜백함수를 호출한다.
const newUser = await User.create({
email: profile._json && profile._json.kaccount_email,
nick: profile.displayName,
snsId: profile.id,
provider: 'kakao'
});
done(null, newUser);
}
} catch (err) {
console.error(err);
done(err);
}
}));
};.env 파일 내 KAKAO_ID=카카오로부터 발급받은 컨슈머 아이디 정보를 설정해야 합니다.
routes\middlewares.js
exports.isLoggedIn = (req, res, next) => {
// passport는 req 객체에 isAuthenticated() 메소드를 추가한다.
if (req.isAuthenticated()) {
next();
} else {
res.status(403).send('Login is required');
}
};
exports.isNotLoggedIn = (req, res, next) => {
if (!req.isAuthenticated()) {
next();
} else {
res.redirect('/');
}
};routes\page.js
var express = require('express');
var router = express.Router();
const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
// 로그인 한 사용자만 프로파일 정보를 조회할 수 있다.
router.get('/profile', isLoggedIn, function(req, res, next) {
// 로그인 한 사용자라면 passport 처리에 의해서 req 객체에 user 정보가 존재한다.
res.json({
title: '회원정보',
user: req.user,
});
});
// 로그인 하지 않은 상태에 사용자만 신규 회원가입을 할 수 있다.
router.get('/join', isNotLoggedIn, function(req, res, next) {
res.json({
title: '회원가입',
user: null,
joinError: req.flash('Signup Error'),
});
});
router.get('/', function(req, res, next) {
res.json({
title: '메인화면',
twits: [],
user: req.user,
loginError: req.flash('Login Error'),
});
});
module.exports = router;routes\auth.js
const express = require('express');
const passport = require('passport');
const bcrypt = require('bcrypt');
const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
const { User } = require('../models');
const router = express.Router();
router.post('/join', isNotLoggedIn, async (req, res, next) => {
const { email, nick, password } = req.body;
try {
const exUser = await User.findOne({
where: {
email
}
});
// 이미 존재하는 이메일로 신규 회원가입을 할 수 없다.
if (exUser) {
req.flash('joinError', 'Email is duplicated.');
return res.redirect('/join');
}
// 패스워드는 암호화한 후 신규 회원정보를 디비에 등록한다.
const hash = await bcrypt.hash(password, 12);
await User.create({
email,
nick,
password: hash
});
// 신규 회원등록 후 사용자에게 보여줄 곳으로 리다이렉트 한다.
return res.redirect('/');
} catch (err) {
console.error(err);
return next(err);
}
});
// 1: URL로 로그인을 요청한다.
router.post('/login', isNotLoggedIn, (req, res, next) => {
// 2: passport.authenticate() 메소드를 호출한다.
// 3: 'local' 로그인 전략을 수행한다.
// - 콜백함수는 LocalStrategy에서 done() 함수를 사용할 때 호출된다.
passport.authenticate('local', (authError, user, info) => {
// C: 서버 에러(장애 발생)
if (authError) {
console.error(authError);
return next(authError);
}
// B: 잘 못된 정보: 패스워드
// or
// B: 잘 못된 정보: 이메일(아이디)
if (!user) {
req.flash('loginError', info.message);
return res.redirect('/');
}
// A: 성공
// 4: 로그인 성공 시 user 객체를 전달하면서 req.login() 메소드를 호출한다.
// 5: req.login() 메소드 내부에서 passport.serializeUser() 메소드에게 전달 된 콜백함수를 호출된다.
// 6: passport.serializeUser() 메소드의 콜백함수가 req.session 객체에 user.id만 저장하도록 요청한다.
return req.login(user, (loginError) => {
if (loginError) {
console.error(loginError);
return next(loginError);
}
// 7: 로그인 성공 후 사용자에게 보여주고 싶은 곳으로 리다이렉트 한다.
return res.redirect('/');
})
})(req, res, next);
// 미들웨어 안에서 미들웨어를 사용할 수 있다.
// 지금 여기서, 라우팅 처리 미들웨어 안에서 인증처리 미들웨어를 사용하고 있다.
// 이 경우, 내부 미들웨어에서 (req, res, next)를 인자로 제공하면서 호출하면 된다.
});
router.get('/logout', isLoggedIn, (req, res) => {
// passport가 추가한 req.logout() 메소드는 req.user 객체를 제거하는 임무를 수행한다.
req.logout();
// 세션에 저장된 user.id를 제거한다.
req.session.destroy();
// 로그아웃 작업 후 사용자에게 보여줄 곳으로 리다이렉트 한다.
res.redirect('/');
});
// 로그인 화면에서 이 라우팅 함수와 연동하는 링크를 제공해야 한다.
// 카카로 로그인 과정이 시작된다. 즉, passport\kakaoStrategy.js 파일내 코드가 수행된다.
router.get('/kakao', passport.authenticate('kakao'));
// 카카오 개발자로 등록하고 앱을 등록할 때 URL을 지정하는 작업을 먼저 수행해야 한다.
// 카카오에서 인증을 수행한 후 이 URL로 리다이트를 해 준다.
router.get('/kakao/callback', passport.authenticate('kakao', {
failureRedirect: '/'
}), (req, res) => {
// 로그인 처리 성공 후 사용자에게 보여줄 곳으로 리다이렉트 한다.
res.redirect('/');
})
module.exports = router;지금까지 작업한 app.js 파일의 내용은 다음과 같습니다.
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var sassMiddleware = require('node-sass-middleware');
// ## 1
// 사용하기로 약속된 .env 파일을 로드하고
// 키=값 형태의 정보를 파싱하여 process.env에 추가한다.
require('dotenv').config();
// ## 2
const session = require('express-session');
// ## 3
const flash = require('connect-flash');
// ## 4
const { sequelize } = require('./models');
// ## 5
const passport = require('passport');
require('./passport')(passport);
// var indexRouter = require('./routes/index');
// var usersRouter = require('./routes/users');
var pageRouter = require('./routes/page');
var authRouter = require('./routes/auth');
var app = express();
// app.get('env') returns 'development' if process.env.NODE_ENV is not defined.
console.log('=======================');
console.log('MODE: ' + app.get('env'));
console.log('=======================');
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(sassMiddleware({
src: path.join(__dirname, 'public'),
dest: path.join(__dirname, 'public'),
indentedSyntax: true, // true = .sass and false = .scss
sourceMap: true
}));
// ## 1
app.use(cookieParser(process.env.COOKIE_SECRET));
// ## 2
app.use(session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
},
}));
// ## 3
app.use(flash());
// ## 4
if (app.get('env') === 'development') {
// If force is true, each Model will run DROP TABLE IF EXISTS,
// before it tries to create its own table
sequelize.sync({
force: true,
}).then(() => {
// Insert Dummy Data for Test
// require('./sql/dummy')();
});
} else {
sequelize.sync();
}
// ## 5
app.use(passport.initialize());
app.use(passport.session());
// 라우팅
// app.use('/', indexRouter);
// app.use('/users', usersRouter);
app.use('/', pageRouter);
app.use('/auth', authRouter);
// 404 Not Found 에러 핸들러
app.use(function(req, res, next) {
next(createError(404));
});
// Fall-back 에러 핸들러
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;프로젝트를 기동하고 Postman을 이용하여 테스트 합니다.
콘텐츠 배포자의 권리를 보호하기 위해서 도메인이 다르면 브라우저가 자바스크립트 코드의 요청을 거부할 수 있습니다. 자세한 사항은 다음 사이트를 참고하세요.
https://developer.mozilla.org/ko/docs/Web/HTTP/Access_control_CORS
$ npm i corsroutes\index.js
var express = require('express');
var router = express.Router();
const cors = require('cors');
router.use(cors());
// router.get('/', function(req, res, next) {
// res.render('index', { title: 'Express' });
// });
module.exports = router;추가로 app.js 파일에서 routes\index.js 파일을 사용하도록 변경하십시오.
모든 URL은 index.js 파일의 router로 전달되므로 간단하게 router.use(cors())라는 코드로 CORS를 적용할 수 있습니다. 아무러 설정정보 없이 사용하므로 모든 요청에 대해서 응답하는 것이 됩니다. 요청자를 필터링하기 위해서는 추가적으로 설정을 해야한다는 뜻 입니다.
다음 사이트를 참고하세요.
https://www.npmjs.com/package/cors
노드 서버 측에서 HTML Engine 기술인 Pug, Handlebars, EJS 등을 사용하지 않고 뷰를 처리하는 기술로써 Angular를 사용하겠습니다. 앵귤러 프로젝트가 완료되면 빌드 후 결과물을 노드의 public 폴더에 배치하면 됩니다. 그에 따라서 앞서서 작업한 Express의 라우팅 처리 로직은 조금 수정되어야 합니다. 같이 살펴보도록 하겠습니다.
$ ng new my-ng-sns
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS$ cd my-ng-sns
$ npm i jquery popper.js bootstrap
$ npm i @types/jquery --save-dev
$ npm i font-awesome
$ npm i axiosangular.json
"styles": [
"./node_modules/font-awesome/css/font-awesome.css",
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.scss"
],
"scripts": [
"./node_modules/jquery/dist/jquery.slim.min.js",
"./node_modules/popper.js/dist/umd/popper.min.js",
"./node_modules/bootstrap/dist/js/bootstrap.min.js"
]src\styles.scss
body,
html {
width: 100%;
height: 100%;
}
.container {
margin-top: 2em;
}$ ng g c layout/header
$ ng g c page/home
$ ng g c page/sns
$ ng g c page/login
$ ng g c page/join
$ ng g c page/joinReport
$ ng g c page/profile- header: 상단 네비게이션 UI를 제공한다.
- home: 첫 화면이다.
- sns: Posts 테이블의 정보를 표시한다.
- login: 로그인 화면을 제공한다.
- join: 회원가입 화면을 제공한다.
- joinReport: 회원가입 완료정보를 제공한다.
- profile: Users 테이블의 정보를 표시한다.
$ ng g s http/snsHttp
$ ng g s http/loginHttp
$ ng g s http/joinHttp
$ ng g s http/profileHttp- snsHttp: Posts 테이블의 정보를 구한다.
- loginHttp: 로그인 처리를 한다.
- joinHttp: 회원가입 처리를 한다.
- profileHttp: Users 테이블의 정보를 구한다.
src\app\app.component.html
<app-header></app-header>
<div class="container">
<router-outlet></router-outlet>
</div>src\app\layout\header\header.component.html
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" routerLink="/">Angular Love</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/home">
Home <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/sns">SNS</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/login">Login</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/join">Join</a>
</li>
</ul>
</div>
</nav>