[React+Node.js Express] 회원가입, 로그인, 회원 정보 수정, 게시판 기능을 구현해 놓고 필요할 때 필요한 기능만 추가해 사용할 수 있는 나만의 보일러 플레이트 토이 프로젝트를 진행하고 있다. 프론트엔드는 React로, 백엔드는 Express로, DB는 몽구스를 사용해 구현할 것이다. 특히 회원 가입이나 로그인에 대한 정보는 많은데, 회원 정보 수정, 또는 게시판 기능에 대한 글들이 생각보다 없어 헤메는 분들에게 도움이 되었으면 한다. 최종 전체 코드는 맨 아래에 있으니 필요하신 분들은 스크롤 쭉 내려서 확인하시길..!
Frontend
이번 글에서는 회원 가입 기능을 만들 것이다. 피그마로 간단히 디자인 해 놓은 와이어프레임을 따라 프론트엔드를 구성해준다. 디자인은 편리하게 antd(https://ant.design/), Material(https://mui.com/core/) 등 리액트 전용 부트스트랩을 사용해 꾸몄고, 아래와 같은 창을 만들어 줄 것이다. 각 항목을 작성하지 않으면 빨간색 작은 문구로 각 Input 밑에 HelpText를 표기한다. 백엔드의 기능을 완벽히 만들어 놓는 것이 목표이기 때문에 프론트엔드 디자인은 아직 완벽하지 못한 상태...
사용자가 각 Input 항목에 데이터를 입력하면 그것을 useState에 담은 뒤, axios 라이브러리를 이용해 백엔드에 작성한 회원가입 처리 로직 api로 데이터들을 전송할 것이다. 데이터를 전달받은 백엔드는 로직에 따라 몽고db에 데이터를 저장하고, 완료된 결과를 프론트엔드로 전송한다. 즉, 사용자 입력 -> useState -> axios -> 백엔드 -> 몽고DB -> 백엔드 -> 프론트엔드 의 형태로 데이터가 흐르게 된다.
Register.js
먼저 HelpText 컴포넌트를 만들고, 변수로 내용을 저장해 하나의 컴포넌트를 여러번 재사용할 수 있게 해준다. HelpText는 인풋에 값을 입력하지 않거나, 또는 잘못된 내용을 입력하였을 때 보이는 붉은 글씨이다. HelpText를 불러올 때 파라미터로 String이 저장된 변수를 전달하여 재사용한다.
function HelpText(Helptext) {
return(
<FormHelperText key="ckPw" style={{color:'red'}}>{Helptext}</FormHelperText>
)
}
const Register = (props) => {
...
let Helptext = {
IName : '이름을 입력해 주세요.',
INik: '닉네임을 입력해 주세요.',
IEmail: '이메일을 입력해주세요.',
IAuth: '인증번호를 입력해주세요.',
IPwd: '비밀번호를 입력해주세요.',
IckPwd:'비밀번호가 서로 다릅니다.'
}
}
값은 아래와 같이 입력받는다. useState를 선언해 변수를 만들어 놓고, get 형식으로 메소드를 만든 후 Input의 onChange에 연결하여 Input의 값이 변할 때 마다 변수에 사용자가 입력한 내용을 저장한다. 만일 Input에 아무것도 입력되지 않은 상태, 즉 State에 아무런 값이 저장되어 있지 않은 상태라면 위에서 작성한 HelpText가 보이도록 삼항연산자를 이용해 주었다.
const Register = (props) => {
...
// < ----------------------------입력된 값 받아오기 ---------------------------->
const [username, setUserName] = useState()
const [userNikname, setUserNikname] = useState()
const [userEmail, setUserEmail] = useState()
const [userAut, setAut] = useState()
const [userPassword, setPassword] = useState()
const [Checkpassword,setCheckPsw] = useState()
function getName(e) {
setUserName(e.target.value)
console.log(username)
}
function getNikname(e) {
setUserNikname(e.target.value)
console.log(userNikname)
}
...
// <-------------------------UI----------------------------->
return (
...
<Form.Item
name="userName">
<Input style={{fontSize:'15px'}} prefix={<UserOutlined className="site-form-item-icon" />} placeholder="이름"
onChange={(e)=>{getName(e)}}
/>
{(!username) ? HelpText(Helptext.IName) : <></> }
</Form.Item>
...
)
입력 폼과 HelpText를 모두 작성하였으니, axios 라이브러리를 이용해 백엔드로 데이터를 전달해 줄 axios.post를 작성한다. axios.post('api주소', 변수) 를 하면 변수에 저장된 정보를 응답할 api로 전달해줄 수 있다. api를 요청하기 전에 각 state의 NULL 여부를 검사하여, 만일 작성하지 않은 항목이 있다면 axios 요청을 하지 않도록 제어해준다.
const Register = (props) => {
...
// < ---------------------------- 백엔드 api 호출 ---------------------------->
function onRegister() {
let body = {
name:username,
nikname:userNikname,
email:userEmail,
asonumber:userAut,
password:userPassword,
}
if( !username || !userNikname || !userEmail || !userAut || !userPassword ) {
alert('작성하지 않은 항목이 있습니다.')
} else if (Checkpassword!=userPassword) {
alert('비밀번호가 같지 않습니다.')
} else {
axios.post('/api/user/register', body)
.then(response=>{console.log('회원가입 완료',response)
navigate('/')
// eslint-disable-next-line no-restricted-globals
location.reload()})
}
}
const onFinish = (values) => {
console.log('Received values of form: ', values);
};
...
}
navigate('/')
// eslint-disable-next-line no-restricted-globals
location.reload()})
|
위 코드에서 이 부분은 let navigate = useNavigate() 훅을 사용해 홈 화면으로 포워딩 해 주는 문장이다. 회원가입을 완료하면 홈 화면으로 돌아가되, 새로고침을 한번 해 준다. 새로고침을 한 이유는 개발 단계에서 주소를 직접 변경해 이동했을 경우 useState가 props로 제대로 전달되지 않는 이슈가 있기 때문인데, 이때 새로고침을 해 주면 컴포넌트를 전부 다시 마운트하므로 State를 올바르게 불러올 수 있게 된다. // eslint-disable-next-line no-restricted-globals 주석을 반드시 작성해야 오류 없이 로드되기 때문에 주석을 빼먹어서는 안된다. (나만의 꿀팁,,)
Backend
프론트 단에서 api 요청을 했을 때 응답할 로직을 작성한다. 먼저 몽구스 라이브러리를 install하고, 몽고DB 클러스터를 생성해 서버에 연결한다. 깃허브로 배포할 때를 대비해 gitignore하기 위해서, 몽고 DB의 mongoURI는 따로 빼 작성했다.
server.js
models 폴더를 만들고, 그곳에 몽구스 스키마를 작성할 User.js 파일을 생성한다.
또한 모든 api를 한개의 파일에 작성하면 관리가 어렵기 때문에 Exrpess에 포함된 Router를 이용할 것이다. 회원 가입, 로그인, 회원 정보 수정 등 회원 전용 api를 따로 빼기 위해 routes 폴더를 만든 후 users.js를 생성한다. /api/user/하위경로 로 요청되는 api들은 routes 폴더 안의 users.js에서 처리하게 된다.
모든 파일과 모듈을 모아주는 server.js에 require(경로)를 사용해 import 해주면 성공적으로 사용할 수 있다. router 관련 선언은 가장 하단부에 있어야 작동이 잘 되므로 코드의 하단부에 작성해준다. 그 밑에 있는 코드는 '만일 server.js에서 인식할 수 없는 요청이 들어오면, 리액트가 route를 처리하도록 하라' 는 요청이다. 프론트인 React에서 Rotuer를 사용해 페이지를 나누었기 때문에 해당 코드를 넣어주어야 정상적으로 작동된다.
const express = require('express')
const app = express()
const path = require('path')
app.use(express.json());
const cors = require('cors');
app.use(cors());
app.listen(5000,function(){
console.log('서버를 열었습니다.')
})
// ======== Router ========
const { User } = require('./models/User');
// ======== Mongoose Setting ========
const mongoose = require("mongoose");
const config = require('./config/key')
mongoose.connect(config.mongoURI)
.then( ()=>console.log('몽고DB Connected...'))
.catch(err=>console.log('몽고디비 에러',err))
// ------------------------------ user Routes ------------------------------
app.use('/api/user', require('./routes/users'));
// Serve static assets if in production
if (process.env.NODE_ENV === "production") {
// Set static folder
// All the javascript and css files will be read and served from this folder
app.use(express.static("client/build"));
// index.html for all page routes html or routing and naviagtion
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "../client", "build", "index.html"));
});
}
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, '../client/build/index.html'));
});
server/models/User.js
mongoose.Schema를 사용해 사이트를 이용할 유저의 스키마를 만든다. 몽구스는 이렇게 개발자가 작성한 스키마를 기준으로 데이터를 DB에 삽입하기 전, 데이터가 형식에 맞는지를 검사한다. 만일 데이터가 어긋난다면 에러를 발생시킨다. SQL의 테이블과 같은 역할을 한다고 보면 된다. JSON과 유사한 형태로 선언하는데, 이름: { 속성:속성설정 } 의 형태로 선언하며 type, maxlength, trim, unique 등 여러 속성을 설정해 줄 수 있다.
https://mongoosejs.com/docs/api/model.html
유저 정보로는 이름, 닉네임, 이메일, 비밀번호를 받아오며 필요할 경우 프로젝트에 [관리자] 권한을 추가해주기 위하여 role(유저 권한)을 기본값(default) 0으로 설정해 주었다. 또한 각 속성에 타입과 trim(공백 제거), unique(중복 불가능), maxlength(최대 길이) 등 필요한 속성을 입한다. 아직은 설정하지 않았지만 후일 이메일 인증 기능을 추가할 때 인증번호를 받아오기위해 asonumber 을 넣어주었다.
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const saltRounds = 10;
const jwt = require('jsonwebtoken');
const moment = require("moment");
const userSchema = mongoose.Schema({
// 이름, 이메일, 비밀번호, 닉네임, 유저 권한, 토큰(로그인 상태 관리)
name: {
type:String,
maxlength:50
},
nikname: {
type:String,
maxlength: 50
},
email: {
type:String,
trim:true,
unique: 1
},
asonumber: {
type:String,
trim:true
},
password: {
type: String,
minglength: 5
},
role : {
type:Number,
default: 0
},
})
api를 본격적으로 작성하기 전, 회원가입 로직을 잘 생각해보자. 프론트에서 유저에게 입력받은 값을 axios를 통해 server.js로 받아오면, server.js의 router가 /api/user/하위 경로로 전달받은 요청들을 모두 routes의 users.js로 전송한다. users.js는 받은 요청에 담긴 데이터를 방금 작성한 몽구스 스키마 User.js 와 일치하는지 살펴본다. 만일 데이터들이 스키에 맞는 형식이면 몽고DB에 데이터를 저장하고, 프론트엔드로 완료된 결과를 전송한다. 따라서 데이터의 흐름은 다음과 같다.
axios.post -> server.js -> router로 /api/user/ 하위 경로에 해당하는 요청들을 users.js로 전달 -> users.js 에서 User.js의 스키마와 비교 -> 비밀번호 암호화하여 Mongo DB에 저장 -> users.js에서 Fontend로 처리 결과 res(응답) |
이제 api를 작성하기 위해 routes폴더의 users.js로 이동한다. 위에서 ' users.js 에서 User.js의 스키마와 비교 -> 비밀번호 암호화하여 Mongo DB에 저장 ' 에 해당하는 부분을 작성해 줄 것이다. 몽구스를 이용해 db에 데이터를 저장 할 때는 create(), save()를 사용해 줄 수 있는데, 나는 save()를 사용해 줄 것이다. Create는 Model에서 호출되는 반면, save()는 인스턴스 메서드이다. 즉 인스턴스를 사용할 나에게는 save()가 더 맞는 선택이다. 게다가 save()는 멋진 옵션이 존재한다. 바로 기존 db와 비교하여 만일 이미 _id값의 데이터가 존재하는 경우라면 바뀐 데이터들만 업데이트해준다. 추후 '회원 정보 수정' 로직을 작성할 때 비밀번호를 변경하게 될 경우 '비밀번호 암호화' 과정을 회원가입때와 똑같이 거쳐야 하기 때문에, 코드가 중복되지 않도록 굳이 업데이트문을 새로 작성하지 않고, 기존의 함수를 재사용할 수 있도록 해주기 위하여 save를 사용할 것이다.
save를 사용해 데이터를 업데이트 하는 방법 : https://mongoosejs.com/docs/documents.html#updating-using-save
Model.prototype.save()
Parameters:
- [options] «Object» options optional options
- [fn] «Function» optional callback
Returns:
- «Promise,undefined,void» Returns undefined if used with callback or a Promise otherwise.
See:
Saves this document by inserting a new document into the database if document.isNew is true, or sends an updateOne operation with just the modified paths if isNew is false.
Example:
product.sold = Date.now();
product = await product.save();
If save is successful, the returned promise will fulfill with the document saved.
Example:
const newProduct = await product.save();
newProduct === product; // true
server/routes/users.js
프론트에서 보낸 api 요청 주소는 /api/user/register 이었다. server.js에서 /api/user에 해당하는 요청들을 모두 users.js로 보내달라고 작성했기 때문에, users.js에서는 router를 임포트 한 후 /register에 해당하는 응답문을 작성해주면 된다.
const express = require('express')
const router = express.Router()
// -------------------------------------------------------
const { auth } = require('../middleware/auth')
const { User } = require('../models/User');
// -------------------------------------------------------
router.post('/register', function(req,res){
// 받아온 정보를 user에 저장한다.
const user = new User(req.body)
// 여기서 userSchema.pre('save', function( next ) { 를 실행시킨다.
user.save((err, userInfo) => {
if(err) return res.json({success:false, err})
return res.status(200).json({
success:true
})
})
})
req에는 '요청한 당사자가 보낸 데이터들' 이 들어있다. 옛날에는 body-parser를 따로 설치했지만, 지금은 Express에 내장 모듈로 포함되어 있기 때문에 별도로 install해 줄 필요가 없다.
const user에 req.body를 사용하여, 회원가입할 유저의 정보를 저장해 새로운 스키마를 생성해준다. 그 다음 해당 유저 정보를 mongoose에 포함된 save() 메소드를 사용하여 몽고DB에 저장해준다. err에는 에러 정보가, userInfo에는 생성된 몽고DB 정보가 담겨있다.
그런데 이대로만 하면 비밀번호가 암호화되지 않은 채 그대로 저장된다. 따라서 다시 models/User.js로 이동해 'save함수가 실행되기 전에 비밀번호를 암호화 해주세요~' 라는 메소드를 작성해 주어야 한다. (User.js에 데이터 스키마가 있기 때문)
이러한 메소드는 스키마.pre()라는 몽구스의 함수를 사용해 작성할 수 있다. 아래와 같은 경우, 첫번째 인자인 'save' 가 실행되기 전에 function에 담긴 내용이 실행된다. 그 뒤 next()를 통하여 'save'를 작성한 곳으로 돌아가 save()를 실행한다.
toySchema.pre('save', function(next) {
if (!this.created) this.created = new Date;
next();
});
https://mongoosejs.com/docs/api.html#schema_Schema-pre
server/models/User.js
회원가입을 할 때 비밀번호를 암호화하지 않고 그대로 저장하면 위험에 노출될 가능성이 크다. 따라서 bcrypt 라이브러리를 이용하여 비밀번호를 암호화 해 db에 저장해 준다.
bcrypt : https://www.npmjs.com/package/bcrypt
아래와 같이 설치한 bcrypt를 불러온다.
const bcrypt = require('bcryptjs');
const saltRounds = 10;
userSchema.pre에서 save()가 실행되기 전에 bcrypt를 이용해 비밀번호가 암호화되도록 설정해준다. bcrypt함수는 공식 문서에서 제공해준 대로 작성했다.
// bcrypt를 이용한 비밀번호 암호화
// 'save'라는 api를 실행하기 전에 function를 실행하고, next로 save로 이동시킨다.
userSchema.pre('save', function( next ) {
var user = this;
if(user.isModified('password')){
console.log('비밀번호 변경중 ...')
bcrypt.genSalt(saltRounds, function(err, salt){
if(err) return next(err);
// hash화된 비밀번호 저장
bcrypt.hash(user.password, salt, function(err, hash){
if(err) return next(err);
console.log('여기까지 성공 ...')
user.password = hash
next()
})
})
} else {
next()
}
});
isModified는 몽구스에서 제공해주는 함수이다. if(user.isModified('password')) 는 '만일 이미 user라는 스키마의 password 항목이 변경되지 않은 상태라면 false, 변경된 상태라면 true를 반환해 주세요~' 라는 내용을 수행한다. 추후 회원정보 수정 시 비밀번호를 변경할 때 해당 메소드를 불러와 실행할 것이기 때문에 이 문장을 추가해주었다. 이렇게 하면 비밀번호가 수정될 경우에만 전달받은 새로운 비밀번호를 다시 암호화해준다. hash에는 해쉬화가 완료된 비밀번호가 들어 있다. user.password에 hash를 저장해주면 저장할 데이터들이 완성된다.
반드시 next()를 호출해야 save()로 돌아갈 수 있음을 잊어서는 안된다.
여기까지 하면 회원가입 기능이 완료된다.
'유저에게 정보 입력받기, 해당 정보 백엔드로 전송하기, 비밀번호 암호화하여 몽구스를 이용해 저장하기' 가 모두 완료된 것이다. 아직 '이메일 인증, 인증번호 발송' 기능은 구현하지 않았다. 따라서 단순한 회원가입이 가능한지만 테스트해본다.
다음은 로그인 기능을 구현해보겠다. 오늘은 여기까지 ...
전체 코드
Frontend
client/src/Register.js
import React, { useState } from 'react';
import { LockOutlined, UserOutlined, CheckOutlined } from '@ant-design/icons';
import { Button, Checkbox, Form, Input } from 'antd';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import { FormHelperText } from '@mui/material';
function HelpText(Helptext) {
return(
<FormHelperText key="ckPw" style={{color:'red'}}>{Helptext}</FormHelperText>
)
}
const Register = (props) => {
let navigate = useNavigate()
// < ----------------------------입력된 값 받아오기 ---------------------------->
const [username, setUserName] = useState()
const [userNikname, setUserNikname] = useState()
const [userEmail, setUserEmail] = useState()
const [userAut, setAut] = useState()
const [userPassword, setPassword] = useState()
const [Checkpassword,setCheckPsw] = useState()
function getName(e) {
setUserName(e.target.value)
console.log(username)
}
function getNikname(e) {
setUserNikname(e.target.value)
console.log(userNikname)
}
function getUserEmail(e) {
setUserEmail(e.target.value)
console.log(userEmail)
}
function getAut(e) {
setAut(e.target.value)
console.log(userAut)
}
function getPassword(e) {
setPassword(e.target.value)
console.log(userPassword)
}
function checkPsw(e) {
setCheckPsw(e.target.value)
console.log(Checkpassword)
}
let Helptext = {
IName : '이름을 입력해 주세요.',
INik: '닉네임을 입력해 주세요.',
IEmail: '이메일을 입력해주세요.',
IAuth: '인증번호를 입력해주세요.',
IPwd: '비밀번호를 입력해주세요.',
IckPwd:'비밀번호가 서로 다릅니다.'
}
// < ---------------------------- 백엔드 api 호출 ---------------------------->
function onRegister() {
let body = {
name:username,
nikname:userNikname,
email:userEmail,
asonumber:userAut,
password:userPassword,
}
if( !username || !userNikname || !userEmail || !userAut || !userPassword ) {
alert('작성하지 않은 항목이 있습니다.')
} else if (Checkpassword!=userPassword) {
alert('비밀번호가 같지 않습니다.')
} else {
axios.post('/api/user/register', body)
.then(response=>{console.log('회원가입 완료',response)
navigate('/')
// eslint-disable-next-line no-restricted-globals
location.reload()})
}
}
const onFinish = (values) => {
console.log('Received values of form: ', values);
};
// < ----------------------------로그인 되어있을 경우 navigate('/') ---------------------------->
if (props.isAuth) {
navigate('/')
// < ---------------------------- 아닐 경우 회원가입 UI VIEW ---------------------------->
} else {
return (
<div style={{width:'100%',height:'100vh',display:'flex', marginTop:'40px', marginBottom:'40px'}}>
<div style={{ display:'flex',width:'900px', height:'100vh', margin:'auto', border:'1px solid gray', borderRadius:'19px'}}>
<Form style={{ margin: 'auto', width:'280px'}}
name="normal_Register"
className="register-form"
size='large'
initialValues={{
remember: false,
}}
onFinish={onFinish}
>
<h2 style={{textAlign:'center', fontWeight:'bold', paddingBottom:'20px' }}>회원가입</h2>
<Form.Item
name="userName">
<Input style={{fontSize:'15px'}} prefix={<UserOutlined className="site-form-item-icon" />} placeholder="이름"
onChange={(e)=>{getName(e)}}
/>
{(!username) ? HelpText(Helptext.IName) : <></> }
</Form.Item>
<Form.Item
name="userNikname">
<Input style={{fontSize:'15px'}} prefix={<UserOutlined className="site-form-item-icon" />} placeholder="닉네임"
onChange={(e)=>{getNikname(e)}}/>
{(!userNikname) ? HelpText(Helptext.INik) : <></> }
</Form.Item>
<Form.Item
name="userId">
<Input style={{fontSize:'15px', float:'left'}} prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Email"
onChange={(e)=>{getUserEmail(e)}}/>
{(!userEmail) ? HelpText(Helptext.IEmail) : <></> }
<Button disabled="" size="middle" type="primary" htmlType="submit" className="login-form-button" style={{backgroundColor:'gray', width:'100%', marginTop:'10px'}}
>
인증번호 발송
</Button>
</Form.Item>
<Form.Item
name="EmailCheck">
<Input style={{fontSize:'15px'}} prefix={<UserOutlined className="site-form-item-icon" />} placeholder="인증번호"
onChange={(e)=>{getAut(e)}}/>
{(!userAut) ? HelpText(Helptext.IAuth) : <></> }
</Form.Item>
<Form.Item
name="password">
<Input
style={{fontSize:'15px'}}
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="비밀번호"
onChange={(e)=>{checkPsw(e)}}
/>
{(!userPassword) ? HelpText(Helptext.IPwd) : <></> }
</Form.Item>
<Form.Item
name="Checkpassword">
<Input
style={{fontSize:'15px'}}
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="비밀번호"
onChange={(e)=>{getPassword(e)}
}
/>
{(userPassword!=Checkpassword) ? HelpText(Helptext.IckPwd) : <></> }
</Form.Item>
<Form.Item>
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox style={{marginTop:'10px'}}>전체 동의하기</Checkbox>
<div> <span><CheckOutlined style={{color:'gray'}}/> </span> 서비스 이용 약관 동의 (필수)</div>
<div> <span><CheckOutlined style={{color:'gray'}}/> </span> 개인정보 수집 및 이용 동의 (필수)</div>
<div> <span><CheckOutlined style={{color:'gray'}}/> </span> 마케팅 수신 동의 (선택)</div>
</Form.Item>
</Form.Item>
<Form.Item>
<Button size="large" type="primary" htmlType="submit" className="login-form-button"
style={{width:'100%', backgroundColor:'gray'}}
onClick={()=>{
onRegister()
}}>
회원가입
</Button>
</Form.Item>
</Form>
</div>
</div>
);
}
};
export default Register;
Backend
/server/models/server.js
const express = require('express')
const app = express()
const path = require('path')
app.use(express.json());
const cors = require('cors');
app.use(cors());
app.listen(5000,function(){
console.log('서버를 열었습니다.')
})
const config = require('./config/key')
const mongoose = require("mongoose");
const { User } = require('./models/User');
mongoose.connect(config.mongoURI)
.then( ()=>console.log('몽고DB Connected...'))
.catch(err=>console.log('몽고디비 에러',err))
// ------------------------------ user Routes ------------------------------
app.use('/api/user', require('./routes/users'));
// Serve static assets if in production
if (process.env.NODE_ENV === "production") {
// Set static folder
// All the javascript and css files will be read and served from this folder
app.use(express.static("client/build"));
// index.html for all page routes html or routing and naviagtion
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "../client", "build", "index.html"));
});
}
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, '../client/build/index.html'));
});
/server/models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const saltRounds = 10;
const userSchema = mongoose.Schema({
// 이름, 이메일, 비밀번호, 닉네임, 유저 권한, 토큰(로그인 상태 관리)
name: {
type:String,
maxlength:50
},
nikname: {
type:String,
maxlength: 50
},
email: {
type:String,
trim:true,
unique: 1
},
asonumber: {
type:String,
trim:true
},
password: {
type: String,
minglength: 5
},
role : {
type:Number,
default: 0
}
})
// bcrypt를 이용한 비밀번호 암호화
// 'save'라는 api를 실행하기 전에 function를 실행하고, next로 save로 이동시킨다.
userSchema.pre('save', function( next ) {
var user = this;
if(user.isModified('password')){
console.log('비밀번호 변경중 ...')
bcrypt.genSalt(saltRounds, function(err, salt){
if(err) return next(err);
// hash화된 비밀번호 저장
bcrypt.hash(user.password, salt, function(err, hash){
if(err) return next(err);
console.log('여기까지 성공 ...')
user.password = hash
next()
})
})
} else {
next()
}
});
const User = mongoose.model('User', userSchema);
module.exports = { User }
/server/routes/users.js
const express = require('express')
const router = express.Router()
// -------------------------------------------------------
const { User } = require('../models/User');
// -------------------------------------------------------
router.post('/register', function(req,res){
// 받아온 정보를 user에 저장한다.
const user = new User(req.body)
// 여기서 userSchema.pre('save', function( next ) { 를 실행시킨다.
user.save((err, userInfo) => {
if(err) return res.json({success:false, err})
return res.status(200).json({
success:true
})
})
})
module.exports = router
'프로젝트 > 풀스택 프로젝트' 카테고리의 다른 글
4-4. [React + Node.js Express] 로그아웃 기능 (0) | 2022.12.05 |
---|---|
4-3. [React + Node.js Express] 유저 인증 기능 (1) | 2022.12.05 |
4-2. [React + Node.js Express] 로그인 기능 (0) | 2022.12.03 |
3. [ React + Node.js Exrpess ] 리액트와 익스프레스 연결하기 (0) | 2022.11.19 |
1. 무비 API 이용 풀스택 웹 프로젝트 ( React, Node.js express, MongoDB ) (0) | 2022.11.13 |
댓글