
인프런 ==> 따라하며-배우는-노드-리액트-기본
강의 자료 : https://braverokmc79.github.io/react-series/BoilerPlate%20Korean.html
소스 : https://github.com/braverokmc79/react-series
섹션 0. Node JS
강의
1.소개
2.NODE JS 와 EXPRESS JS 다운로드 하기
3.몽고 DB 연결
4.MongoDB Model & Schema
5.GIT 설치
6.SSH를 이용해 GITHUB 연결
7.BodyParser & PostMan & 회원 가입 기능
8.Nodemon 설치
9.비밀 설정 정보 관리
10.Bcrypt로 비밀번호 암호화 하기
11.로그인 기능 with Bcrypt (1)
12.토큰 생성 with jsonwebtoken
13.Auth 기능 만들기
14.로그아웃 기능
섹션 1. React JS
20.강의
15.리액트란 ?
16.Create-React-App
17.npm npx
18.구조 설명
19.CRA to Our Boilerplate
20.React Router Dom
21.데이터 Flow & Axios
22.CORS 이슈, Proxy 설정
23.Proxy Server ?
24.Concurrently
25.Antd CSS Framwork 26.Redux 기초
27.Redux UP !!!!!
28.React Hooks
29.로그인 페이지(1)
30.로그인 페이지(2)
31.회원 가입 페이지
32.로그아웃
33.인증 체크 (1)
34.인증 체크 (2) 강의 마무리.
2. Diagram 자료
2 강의
Diagram HTML 자료
Diagram XML 자료
1.소개
2.NODE JS 와 EXPRESS JS 다운로드 하기
expressjs 공식 문서 : https://expressjs.com/
설치 중
이미 Node.js 를 설치했다고 가정 하고 애플리케이션을 저장할 디렉토리를 만들고 이 디렉토리를 작업 디렉토리로 만듭니다.
$ mkdir myapp $ cd myapp
npm init명령을 사용하여 응용 프로그램에 대한 파일 package.json을 만듭니다. package.json작동 방식 에 대한 자세한 내용 은 npm의 package.json 처리 세부 사항을 참조하세요 .
$ npm init
이 명령은 응용 프로그램의 이름 및 버전과 같은 여러 항목을 묻는 메시지를 표시합니다. 지금은 RETURN을 눌러 다음 예외를 제외하고 대부분의 기본값을 수락할 수 있습니다.
entry point: (index.js)
를 입력 app.js하거나 기본 파일 이름을 원하는 대로 입력합니다. 원하는 경우 index.jsRETURN을 눌러 제안된 기본 파일 이름을 수락하십시오.
이제 myapp디렉토리에 Express를 설치하고 종속성 목록에 저장하십시오. 예를 들어:
$ npm install express
Express를 임시로 설치하고 종속성 목록에 추가하지 않으려면:
$ npm install express --no-save
#biler-plate 디렉토리 설치
$ mkdir  biler-plate
$ cd  biler-plate
$ npm init
#express 설치
$ yarn add express
index.js 파일 생성 후 작성
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
    res.send('Hello World! 안녕하세요.')
})
app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})
package.json
{
  "name": "biler-plate",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "macaronics",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.1"
  }
}
실행 :
$ node index.js
또는
$ npm start
3.몽고 DB 연결
몽고 DB 윈도우 설치 및 실행
2)[MongoDB] 몽고디비 GUI 개발/관리도구 Studio 3T 설치 (Robo 3T)
몽고 DB 클라우드 가입
몽고 DB 툴로 연결 할 경우
로컬일 경우 : mongodb://localhost:27017
클라우드일 경우 : mongodb+srv://macaronics:<password>@mongo-macaronics.y37mjuf.mongodb.net/test

몽고DB 연결 참조 : https://bokki.org/nodejs/nodejs-mongoose로-mongodb-연결하기/
nodejs 몽고 DB 연결 라이브러리 설치
$ npm install mongoose

mongodbURI.js
const mongodbURI = "mongodb+srv://macaronics:<password>@mongo-macaronics.y37mjuf.mongodb.net/test"; module.exports = mongodbURI;
index.js
const express = require('express')
const app = express()
const port = 5000
const mongodbURI = require('./mongodbURI')
const mongoose = require("mongoose");
// MongoDB 연결 1
// '단' 하나의 데이터베이스 연결 시 유효. 
mongoose.connect(
    mongodbURI,
    // MongoDB url
    // {
    //     useNewUrlParser: true,
    //     useUnifiedTopology: true,
    //     useCreateIndex: true,
    //     useFindAndModify: false,
    // }
    // options
    // MongoDB 5 버전부터 useNewUrlParser 옵션을 사용해주지 않으면 에러가 뜹니다.
    //Mongoose 6 사용으로 에러 발생시.
    //useNewUrlParser, useUnifiedTopology,
    //useFindAndModify 및 useCreateIndex는 더 이상 지원되지 않는 옵션입니다.
    //Mongoose 6은 항상 useNewUrlParser, useUnifiedTopology 및 useCreateIndex가 true이고
    // useFindAndModify가 false인 것처럼 작동합니다.코드에서 이러한 옵션을 제거하십시오.
).then(() => console.log("MongoDB Connected...")).catch(err => console.error("에러 :", err));
app.get('/', (req, res) => {
    res.send('Hello World! 안녕하세요.')
})
app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})
실행 :
npm start
4.MongoDB Model & Schema
몽고 DB Scema 예
const Schema = mongoose.Schema;
const productSchema = mongoose.Schema({
    writer: {
        type: Schema.Types.ObjectId,
        ref: "User"
    },
    title: {
        type: String,
        maxlength: 50
    },
    descrip: {
        type: String
    }
}, { timestamps: true })
const Product = mongoose.model('Product', productSchema);
module.exports = { Product };
models/User.js 에 User 스키마 생성
const mongoose = require('mongoose');
const userSchema = mongoose.Schema({
    name: {
        type: String,
        maxlength: 50
    },
    email: {
        type: String,
        trim: true,
        unique: 1
    },
    password: {
        type: String,
        minlength: 5
    },
    lastname: {
        type: String,
        maxlength: 50
    },
    role: {
        type: Number,
        default: 0
    },
    image: String,
    token: {
        type: String
    },
    tokenExp: {
        type: Number
    }
})
const User = mongoose.model('User', userSchema);
module.exports = { User };
테스트 : index.js
const express = require('express')
const app = express()
const port = 5000
const mongodbURI = require('./mongodbURI')
const mongoose = require("mongoose");
const User = require("./models/User");
console.log("User : ", User);
//Mongoose 6은 항상 useNewUrlParser, useUnifiedTopology 및 useCreateIndex가 true이고 useFindAndModify가 false인 것처럼 작동
// mongoose.connect(mongodbURI,
// ).then(() => console.log("MongoDB Connected...")).catch(err => console.error("에러 :", err));
app.get('/', (req, res) => {
    res.send('Hello World! 안녕하세요.')
})
app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})
$npm start
실행 결과=>
> biler-plate@1.0.0 start
> node index.js
User :  { User: Model { User } }
Example app listening on port 500
5.GIT 설치
깃 다운로드 : https://git-scm.com/downloads
상황을 들어보자.
작업이 완료되어 저장소에 push를 날렸다.
하지만 모르고 작업에 필요했지만 올리지 않아도 되는 private이라는 폴더를 올려버렸다.
그래서 폴더를 삭제하고, 다시 push를 날렸다.
하지만 Github에서는 삭제되지 않았다.
폴더를 리팩토링하고 난 후에도 마찬가지이다.
삭제 및 이동을 하고 push를 할 시 Github에 있는 폴더는 유지된 채 바뀐 폴더가 새로 생성된다.
원격 저장소에 이미 파일은 저장되어있다.
로컬에서 삭제만 한다고 해서 원격 저장소에서 삭제가 이루어지지 않는다.
이 경우 git 명령어를 통한 파일 삭제 후 push를 해줘야한다.
$ git rm <fileName>
$ git rm --cached <fileName>
위의 git 명령어를 통해 폴더나 파일을 삭제하면 된다.
--cached 는 rm 명령어의 옵션이다.
--cached의 유무에 따라 차이점을 알아보자.
정의는 아래와 같다.
git rm => 원격 저장소와 로컬 저장소에 있는 파일을 삭제한다.
git rm --cached => 원격 저장소에 있는 파일을 삭제한다. 로컬 저장소에 있는 파일은 삭제하지 않는다.
정의 그대로 --cached 옵션을 넣을 경우에는 로컬 저장소에 있는 건 삭제되지 않는다.
즉, 로컬 저장소에는 존재하지만, 원격 저장소에 올라가진 않는다.
생각해보면 어떤 경우 쓰는지 모르겠고, 필요없는 옵션이라고 볼 수 있다.
예를 들어 작업시에만 쓰는 파일이나 로그 등 작업할 땐 필요하지만 Github에 안 올려도 되는 것들이 있다고 가정하자.
그렇다는 건, 작업할 때마다 올리지 않아도 되는 것들을 항상 고려해야한다.
즉 한가지 예로, Github에는 반영되지 않기 위해 git rm 명령어를 통해 삭제 해주는 작업을 말한다.
이런 경우를 위해 --cached 옵션을 통해 이런 불필요한 과정이 필요하지 않게 할 수 있다는 것이다.
그렇기에 일반적으로 git rm 명령어를 쓸 때에는 --cached 옵션을 사용한다.
아무튼 git rm 명령어를 통해 Github 저장소에 있는 private 폴더를 삭제할 수 있게 되었다.
출처: https://mygumi.tistory.com/103 [마이구미의 HelloWorld:티스토리]
$ git rm --cached node_modules -r
$ git status
$ git commit -m "처음 저장소에 올림"
$ git status
6.SSH를 이용해 GITHUB 연결
.ssh 설치 확인
$ ls -a ~/.ssh
./  ../  id_rsa  id_rsa.pub  known_hosts  known_hosts.old
ssh 가 없을 경우
git hub 공식문서 = > 새 SSH 키 생성 및 ssh-agent에 추가
7.BodyParser & PostMan & 회원 가입 기능
body-parser는 미들웨어이다. 즉, 요청(request)과 응답(response) 사이에서 공통적인 기능을 수행하는 소프트웨어이다.
그렇다면 역할은 무엇일까? 바로 요청의 본문을 지정한 형태로 파싱해주는 미들웨어이다.
body-parser는 HTTP 의 post, put 요청시 HTTP 의 본문(body)를 parsing 하여 나온 결과값을
req.body 에 넣어 body 프로퍼티를 사용할 수 있도록 하는 미들웨어입니다.
body-parser 는 4가지의 parser 를 지원합니다.
1. JSON body parser
2. Raw body parser
3. Text body Parser
4. URL-encoded from body parser
body-parser 설치
$ yarn add body-parser
또는
$npm install body-parser
postman 설치 : https://www.postman.com/
index.js
const express = require('express')
const app = express()
const port = 5000
const mongodbURI = require('./mongodbURI')
const mongoose = require("mongoose");
const { User } = require("./models/User");
const bodyParser = require("body-parser");
//aplication/json
app.use(bodyParser.json());
//application/x-www-form-unlencoded
// body-parser deprecated undefined extended
//app.use(bodyParser.urlencoded({ extends: true }));
//Mongoose 6은 항상 useNewUrlParser, useUnifiedTopology 및 useCreateIndex가 true이고 useFindAndModify가 false인 것처럼 작동
mongoose.connect(mongodbURI,
).then(() => console.log("MongoDB Connected...")).catch(err => console.error("에러 :", err));
app.get('/', (req, res) => { res.send('Hello World! 안녕하세요.') })
app.post('/register', (req, res) => {
    //회원 가입 할때 필요한 정보들을  client 에서 가져오면
    //그것들을 데이터 베이스에 넣어준다.
    console.log("req", req.body.name);
    const user = new User(req.body);
    //몽고 DB 에 설정된 save  사용
    user.save((err, doc) => {
        if (err) {
            return res.json({
                success: false,
                err
            })
        }
        return res.status(200).json({ success: true });
    });
});
app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
postman 테스트

Studio 3D for MongoDB 툴로 DB 저장 확인

cloud.mongodb.com 사이트에서 DB 저장 확인

8.Nodemon 설치
* 실시간 프로젝트 변경 감지 nodemon 설치
yarn 설치 오류시
$ npm i nodemon --save-dev
 
package.json
실행 스크립트 추가
  "scripts": {
    "start": "node index.js",
    "backend":"nodemon index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
9.비밀 설정 정보 관리
package.json
다음과 같이
production 실행 환경 변수는 : NODE_ENV=production,
development 실행 환경 변수는 NODE_ENV=development 로 선언한다.
"scripts": {
    "start": "NODE_ENV=production node index.js",
    "backend":"NODE_ENV=development nodemon index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
$ production 환경 실행 : npm start 또는 npm run start
$ development 환경 실행 : npm run backend

dev.js
module.exports = {
    mongoURI: "mongodb+srv://macaronics:<password>@mongo-macaronics.y37mjuf.mongodb.net/test"
}
prod.js
module.exports = {
    mongoURI: process.env.MONGO_URI
}
key.js
if (process.env.NODE_ENV === 'production') {
    module.exports = require("./prod");
} else {
    module.exports = require("./dev");
}
index.js
const config = require("./config/key");
~
`
mongoose.connect(config.mongoURI,
).then(() => console.log("MongoDB Connected...")).catch(err => console.error("에러 :", err));
10.Bcrypt로 비밀번호 암호화 하기
bcrypt 라이브러리 설치
$ yarn add bcrypt
bcrypt npm 문서 = > https://www.npmjs.com/package/bcrypt
async (recommended)
const bcrypt = require('bcrypt');
const saltRounds = 10;
const myPlaintextPassword = 's0/\/\P4$$w0rD';
const someOtherPlaintextPassword = 'not_bacon'
bcrypt.genSalt(saltRounds, function(err, salt) {
    bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
        // Store hash in your password DB.
    });
});
User.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const userSchema = mongoose.Schema({
    name: {
        type: String,
        maxlength: 50
    },
    email: {
        type: String,
        trim: true,
        unique: 1
    },
    password: {
        type: String,
        minlength: 5
    },
    lastname: {
        type: String,
        maxlength: 50
    },
    role: {
        type: Number,
        default: 0
    },
    image: String,
    token: {
        type: String
    },
    tokenExp: {
        type: Number
    }
})
//몽고DB 에 저장하기 전에 실행하는 함수
userSchema.pre('save', function (next) {
    const user = this;
    //비밀번호가 변환될때면 다음을 실행한다.
    if (user.isModified('password')) {
        //비밀번호를 암호와 시킨다.
        bcrypt.genSalt(saltRounds, function (err, salt) {
            if (err) return next(err);
            bcrypt.hash(user.password, salt, function (err, hash) {
                if (err) return next(err);
                user.password = hash;
                next();
            });
        });
    } else {
        next();
    }
})
const User = mongoose.model('User', userSchema);
module.exports = { User };
DB 확인

11.로그인 기능 with Bcrypt (1)
models/User.js 에 다음을 추가
//userSchema.methods  => 커스텀 함수 생성
userSchema.methods.comparePassword = function (plainPassword, cb) {
    //plainPassword 비밀번호가 12345 일때,   this.password 는 암호화된 비밀번호 $2b$10$LK86g2vaPNMHVLkj69hO7uzodTXATNMezdKnWymKi8QoTX9pE3bey
    bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
        if (err) return cb(err);
        else return cb(null, isMatch);
    });
}
전체
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const userSchema = mongoose.Schema({
    name: {
        type: String,
        maxlength: 50
    },
    email: {
        type: String,
        trim: true,
        unique: 1
    },
    password: {
        type: String,
        minlength: 5
    },
    lastname: {
        type: String,
        maxlength: 50
    },
    role: {
        type: Number,
        default: 0
    },
    image: String,
    token: {
        type: String
    },
    tokenExp: {
        type: Number
    }
})
//몽고DB 에 저장하기 전에 실행하는 함수
userSchema.pre('save', function (next) {
    const user = this;
    //비밀번호가 변환될때면 다음을 실행한다.
    if (user.isModified('password')) {
        //비밀번호를 암호와 시킨다.
        bcrypt.genSalt(saltRounds, function (err, salt) {
            if (err) return next(err);
            bcrypt.hash(user.password, salt, function (err, hash) {
                if (err) return next(err);
                user.password = hash;
                next();
            });
        });
    } else {
        next();
    }
})
//userSchema.methods  => 커스텀 함수 생성
userSchema.methods.comparePassword = function (plainPassword, cb) {
    //plainPassword 비밀번호가 12345 일때,   this.password 는 암호화된 비밀번호 $2b$10$LK86g2vaPNMHVLkj69hO7uzodTXATNMezdKnWymKi8QoTX9pE3bey
    bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
        if (err) return cb(err);
        else return cb(null, isMatch);
    });
}
const User = mongoose.model('User', userSchema);
module.exports = { User };
index.js
app.post('/register', (req, res) => {
    //회원 가입 할때 필요한 정보들을  client 에서 가져오면
    //그것들을 데이터 베이스에 넣어준다.
    const user = new User(req.body);
    //몽고 DB 에 설정된 save  사용
    user.save((err, doc) => {
        if (err) {
            return res.json({
                success: false,
                err
            })
        }
        return res.status(200).json({ success: true });
    });
});
12.토큰 생성 with jsonwebtoken
jsonwebtoken 토큰 라이브러리 등록
$ yarn add jsonwebtoken
npm 공식 문서 : https://www.npmjs.com/package/jsonwebtoken
cookie-parser 쿠키 라이브러리 등록
$ yarn add cookie-parser
사용등록
const cookieParser = require("cookie-parser");
app.use(cookieParser());
models/User.js
~
userSchema.methods.generateToken = function (cb) {
    //몽고DB 에 저장된 형태
    /*  _id:ObjectId("630edadb1f06e2b0be7adeea")
        name:"홍길동"
        email:"test1@gmail.com"
        password: "$2b$10$LK86g2vaPNMHVLkj69hO7uzodTXATNMezdKnWymKi8QoTX9pE3bey"
        role: 0
        __v:0
    */
    //jwt.sign({ foo: 'bar' }, 'shhhhh');  shhhhh 는 임이 문자
    //jsonwebtoken 을 이용해서 token 을 생성하기
    const user = this;
    const token = jwt.sign(user._id.toHexString(), "abcd!!!333");
    user.token = token;
    user.save(function (err, user) {
        if (err) return cb(err)
        cb(null, user)
    });
}
~
index.js
app.post('/login', (req, res) => {
    //1.요청된 이메일을 데이터베이스에서 찾는다.
    User.findOne({ email: req.body.email }, (err, user) => {
        if (!user) {
            return res.json({
                loginSuccess: false,
                message: "제공된 이메일에 해당하는 유저가 없습니다."
            })
        }
        //2.요청된 이메일이 데이터 베이스에 있다면 비밀번호가 맞는 비밀번호 인지 확인
        user.comparePassword(req.body.password, (err, isMatch) => {
            if (!isMatch) return res.json({ loginSuccess: false, message: "비밀번호가 틀렸습니다." });
            //3.비밀 번호까지 같다면 Token을 생성
            user.generateToken((err, user) => {
                if (err) return res.status(400).send(err);
                //토큰을 저장한다. 어디에? 쿠키, 로컬스토리지
                res.cookie("x_auth", user.token).status(200).json({
                    loginSuccess: true,
                    userId: user._id,
                   // token: user.token
                })
            });
        })
    });
});

13.Auth 기능 만들기


middleware 디렉토리 생성후 auth.js 파일 생성.
토큰을 복호화 한후 유저를 찾는 임의 함수인 findByToken 은 User.js 에서 만들어 처리해 준다.
const { User } = require("../models/User");
/** 인증 처리를 하는 곳 **/
let auth = (req, res, next) => {
    //1.클라이언트 쿠키에서 토큰을 가져온다.
    let token = req.cookies.x_auth;
    // //2.토큰을 복호화 한후 유저를 찾는다.
    User.findByToken(token, (err, user) => {
        //3.유저가 없으면 인증 No !
        if (err) throw err;
        if (!user) return res.json({ isAuth: false, error: true })
        //4.유저가 있으면 인증 Okey  
        req.token = token;
        req.user = user;
        next();
    });
}
module.exports = auth;
클라이언트 쿠키에서 토큰 가지고 와서 미들웨어 auth 통해 인증 처리
index.js
const auth = require("./middleware/auth");
~
~
//role 1 어드민  role 2 특정 부서 어드민
//rele 0 -> 일반유저 ,  role 0 이 아니면 관리자.
app.get('/api/users/auth', auth, (req, res) => {
    //여기 까지 미들웨어를 통과해 왔다는 얘기는 Authentication이 True 라는 말
    res.status(200).json({
        _id: req.user._id,
        isAdmin: req.user.role === 0 ? false : true,
        isAuth: true,
        email: req.user.email,
        name: req.user.name,
        lastname: req.user.lastname,
        role: req.user.role,
        image: req.user.image
    });
});
~
~
User.js
1) 라이언트에서 가져온 토큰과 SECRET_KEY 값과 조합을 해서 decoded 값을 생성한다.
여기서 decoded 는 user._id 가 된다. 만약 SECRET_KEY 불일치로 조합에 실패할 경우
decoded 값인 user._id 는 undefined 가 된다.
2)decoded 하연 생성된 유저아이디와 토큰값을 이용해서 DB에서 정보를 가져온다.
3)DB에 가져온 데이터가 없으면 에러, 있으면 유저 정보값을 콜백으로 반환시켜 미들웨어 auth 에서 처리 시킨다.
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const jwt = require("jsonwebtoken");
const SECRET_KEY = "abcd!!!333";
~
userSchema.statics.findByToken = function (token, cb) {
    const user = this;
    /***  토큰을 decode 한다.***/
    //user._id+''=token
    //const SECRET_KEY: "abcd!!!333"
    jwt.verify(token, SECRET_KEY, function (err, decoded) {
        console.log("클라이언트에서 가져온 토큰값 :", token);
        console.log("decoded 는 user._id  :", decoded);
        //예
        //클라이언트에서 가져온 토큰값: eyJhbGciOiJIUzI1NiJ9.NjMwZWRhZGIxZjA2ZTJiMGJlN2FkZWVh.venwg5 - aVAvLA72IDSa1VNkls2MYwK6zXp3wJKSrn6k
        //ecoded 는 user._id  : 630edadb1f06e2b0be7adeea
        //findOne() 은 몽고DB함수
        user.findOne({ "_id": decoded, "token": token }, function (err, user) {
            if (err) return cb(err)
            cb(null, user);
        });
    });
}
~

14.로그아웃 기능

1) 미들웨어를 통해 토큰값으로 아이디값을 가져온다.  
   가져온 아이디 예: 630edadb1f06e2b0be7adeea  
2) 몽고 DB 내장 함수인 findOneAndUpdate 통해 몽고 DB 토큰값을 지운다.
app.get("/api/users/logout", auth, (req, res) => {
    User.findOneAndUpdate({ _id: req.user._id },
        { token: "" },
        (err, user) => {
            if (err) return res.json({ success: false, err });
            return res.status(200).send({ success: true });
        }
    );
});

소스 : https://github.com/braverokmc79/react-series
동영상 34개













댓글 ( 4)  
댓글 남기기