이전에는 주로 장고를 통해 API서버를 만들었다.
그러다가 요즘 프론트엔드로 React.js에 재미를 붙였는데, 이왕 할거
같은 스택으로 하자 싶어서 백엔드도 자바스크립트인 Node.js를 시작했다.
기존에는 Django의 Rest framework를 사용했다면 노드에서는 express서버에 올리는 방법으로 진행한다.
먼저 아래의 명령어로 필요한 모듈들을 설치한다.
npm install —save body-parser express jsonwebtoken mysql2 sequelize
모듈들마다 사용 목적은 다음과 같다
- body-parser : HTTP Body에서 값들을 빼올때 사용
- jsonwebtoken : JWT 토큰을 위해 사용
- sequelize : MySQL과 통신할 때 사용
- mysql2 : sequelize에서 MySQL과 통신할 때 사용
먼저 디렉토리의 구성은 다음과 같다.
(user폴더는 없다고 가정)
index.js가 메인이다. 먼저 index의 소스부터 확인해보자.
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
/* Routes */
app.get('/', (req, res) => {
res.send('Main');
});
app.use('/api', require('./api/auth'));
app.listen(3000, () => {
console.log('[*] Server start -> 3000');
require('./models').sequelize.sync({force: false})
.then(() => {
console.log('[*] Database sync');
});
});
/api 의 경로로 들어올 경우 /api/auth로 이동시킨다.
해당 파일의 소스는 다음과 같다.
const express = require('express');
const router = express.Router();
const controller = require('./auth.controller');
router.get('/', controller.index);
router.post('/login', controller.login);
router.post('/register', controller.register);
module.exports = router;
router를 통해 분기시켜준다.
관리하기 편하게 세분화를 시켜놨으며 /login으로 들어올 경우 controller에 정의되어있는 login으로 라우팅된다.
auth.controller.js 소스는 아래와 같다.
const passport = require('passport');
const jwt = require('jsonwebtoken');
const SECRET = 'SECRET';
const models = require('../../models');
exports.index = (req, res) => {
res.status(401).send('GET METHOD NOT ALLOWED');
}
exports.login = (req, res) => {
const { username, password } = req.body;
models.User.count({
where: {username: username, password: password}
})
.then(result => {
if(result === 1) { // 유저가 존재한다면
const token = jwt.sign({
username: username
}, SECRET, {
algorithm: 'HS256',
expiresIn: '10m'
})
res.status(200).json({status: true, token: token})
} else {
res.status(401).json({status: false, result: 'login fail'})
}
})
.catch(err => {
res.status(500).json({status: false, result: 'login fail'});
})
}
exports.register = (req, res) => {
console.log('register');
const { username, password } = req.body;
models.User.count({
where: {username: username}
})
.then(result => {
if(result > 0) { // 유저네임이 존재한다면
res.status(401).json({status: false, result: 'username already exist'});
} else { // 유저네임이 없다면
models.User.create({
username: username,
password: password
})
res.status(200).json({status: true, result: 'register success'})
}
})
.catch(err => {
res.status(500).json({status: false, result: 'register fail'})
})
}
login의 경우 유저가 존재하는지 판단한 이후 존재한다면 jwt sign() 함수를 통해 토큰을 생성, 리턴해준다.
물론 없다면 에러메시지를 보낸다.
생성도 비슷하다. 유저 아이디가 존재하는지 중복체크를 하고 없다면 생성한다.
여기서 사용하는 models.js의 소스를 보자.
const Sequelize = require('sequelize');
const Op = Sequelize.Op
const config = require('./config/config');
const sequelize = new Sequelize(
config.database,
config.username,
config.password,
{
host: '127.0.0.1',
dialect: 'mysql',
operatorsAliases: {
$and: Op.and,
$or: Op.or,
$eq: Op.eq,
$gt: Op.gt,
$lt: Op.lt,
$lte: Op.lte,
$like: Op.like
}
}
);
/* User Table */
const User = sequelize.define('user', {
username: {
type: Sequelize.STRING(30),
allowNull: false
},
password: {
type: Sequelize.STRING(30),
allowNull: false
},
isAdmin: {
type: Sequelize.BOOLEAN,
defaultValue: false,
allowNull: false
},
createdAt: {
type: Sequelize.DATE,
defaultValue: sequelize.fn('NOW')
},
updatedAt: {
type: Sequelize.DATE,
defaultValue: sequelize.fn('NOW')
}
})
module.exports = {
sequelize: sequelize,
User: User
}
sequelize 객체를 생성하고 옵션을 넣어줬고 define()을 통해 테이블도 생성해줬다.
참고로 설정에 이용하는 config.js는 다음과 같다.
const config = {
username: 'api',
password: 'api',
database: 'api',
secret_key: 'api'
}
module.exports = config;
Full Source : https://github.com/teamhide/nodeapi
+ 아래 코드는 Bcrypt로 암호화를 진행한 auth.controller.js 코드이다.
import jwt from 'jsonwebtoken';
import { User } from '../model/index';
import config from '../config/index';
import bcrypt from 'bcrypt-nodejs';
const SECRET = config.SECRET_KEY;
// Login and get JWT Token
export const login = (req, res) => {
try {
const { id, pw } = req.body;
User.find({
attributes: ['userpw', 'is_admin'],
where: {
userid: id,
}
}).then(queryResult => {
if(queryResult != null) { // 유저 아이디가 존재할 때
bcrypt.compare(pw, queryResult.userpw, (err, result) => {
if(result === true) {
const token = jwt.sign({
userid: id,
is_admin: queryResult.is_admin
}, SECRET, {
algorithm: 'HS256',
expiresIn: '10m'
})
res.status(200).json({status: true, token: token});
} else if(err) {
res.status(500).json({status: false, result: "Bcrypt error"});
} else {
res.status(401).json({status: false, result: "incorrect password"});
}
})
} else { // 유저아이디가 존재하지 않을 때
res.status(404).json({status: false, result: "user does not exist"});
}
});
} catch(e) {
console.log(e);
res.status(500).json({status: false, result: "login error"});
}
}
// Register
export const register = (req, res) => {
try {
const { id, pw } = req.body;
User.count({
where: {
userid: id
}
}).then(result => {
if(result === 1) {
res.status(304).json({status: false, result: "userid already exist"});
} else {
bcrypt.genSalt(10, (err, salt) => {
if(err) {
res.status(500).json({status: false, result: "Bcrypt genSalt error"});
} else {
bcrypt.hash(pw, salt, null, (err, hash) => {
if(err) {
res.status(500).json({status: false, result: "Bcrypt hashing error"});
}
else {
console.log(hash);
User.create({
userid: id,
userpw: bcrypt.hashSync(pw)
})
res.status(200).json({status: true, result: "register success"});
}
});
}
})
}
})
} catch(e) {
res.status(500).json({status: false, result: "register error"});
}
}