23.1 JWT의 이해

세션 기반 인증과 토큰 기반 인증의 차이

  • 세션 기반 인증 시스템
    • 서버가 사용자가 로그인 중임을 기억하고 있음. 로그인하면, 서버는 세션 저장소에 사용자의 정보를 조회하고 세션 id를 발급함. 발급된 id는 주로 브라우저의 쿠키에 저장.
    • 서버를 확장하기가 번거로움. (서버 여러개 되면 세션 전용 데이터베이스 만들어야 함.)
  • 토큰 기반 인증 시스템
    • 토큰은 로그인 이후 서버가 만들어 주는 문자열. 이 안에 사용자의 로그인 정보가 들어 있고, 해당 정보가 서버에서 발급되었음을 증명하는 서명이 들어 있음. (무결성 보장)
    • 로그인하면, 서버에서 사용자에게 해당 사용자의 정보를 지니고 있는 토큰을 발급해 주고, 추후 사용자가 다른 API를 요청하게 될 때 발급받은 토큰과 함께 요청하게 됨.
    • 서버 리소스 절약. 서버의 확장성 높음 

23.2 User 스키마/모델 만들기

  • 모델 메서드 만들기
  • 스태틱 메서드 만들기

23.3 회원 인증 API 만들기

  • 회원가입 구현하기
  • 로그인 구현하기

23.4 토큰 발급 및 검증하기

yarn add jsonwebtoken

23.4.1 비밀키 설정하기

openssl rand -hex 64

23.4.2 토큰 발급하기

  • localStorage, sessionStorage : 구현과 사용이 편리하지만, XSS에 취약.
  • 쿠키 : httpOnly 속성을 활성화해서  XSS방지, CSRF는 CSRF 토큰 사용 및 Referer 검증 등의 방식으로 제대로 막을 수 있음.
    const token = jwt.sign(
        {
            _id: this.id, username: this.username //첫 번째 파라미터 -> 토큰 안에 넣고 싶은 데이터
        },
        process.env.JWT_SECRET, //두 번째 파라미터 -> JWT 암호
        {
            expiresIn: '7d', //세 번째 파라미터 -> 유효기간
        }
    );

23.4.3 토큰 검증하기

const decoded = jwt.verify(token, process.env.JWT_SECRET);

23.4.4 토큰 재발급하기

23.4.5 로그아웃 기능 구현하기

23.5 posts API에 회원 인증 시스템 도입하기

23.6 username/tags로 포스트 필터링하기

 

22.1 소개하기

  • MongoDB는 관계형 데이터베이스의 스키마, 확장성에서의 한계를 극복한 문서 지향적 NoSQL 데이터베이스.
  • 데이터의 구조가 자주 바뀐다면 MongoDB가 유리, 까다로운 조건으로 데이터를 필터링해야 하거나, ACID 특성을 지켜야 한다면 RDBMS가 더 유리할 수 있음.

22.1.1 문서란?
22.1.2 MongoDB 구조
22.1.3 스키마 디자인 

22.2 MongoDB 서버 준비

22.2.1 설치

//macOS, Homebrew를 이용하여 설치
brew tap mongodb/brew
brew install mongodb-community
brew services start mongodb-community

22.2.2 MongoDB 작동 확인

//mongoDB 실행 확인
mongo

//버전 확인
>version()

22.3 mongoose의 설치 및 적용

MongoDB 기반 ODM(Object Data Modelling) 라이브러리. 데이터베이스 문서들을 자바스크립트 객체처럼 사용할 수 있게 해 줌

//mongoose, dotenv 설치
yarn add mongoose dotenv

//dotenv로 환경변수 호출
require('dotenv').config();
const {PORT, MONGO_URI} = process.env;

//mongoose로 서버와 데이터베이스 연결
mongoose.connect(MONGO_URI, {useNewUrlParser: true, useFindAndModify: false}).then(() =>
    console.log('connected to MongoDB')).catch(e => console.log(e));

22.4 ems으로 ES 모듈 import/export 문법 사용하기

//Node.js v12를 사용할 경우, package.json에 다음 줄을 추가하면 ES Module을 바로 사용할 수 있음
"scripts" : {
	...
},
"type" : "module"

//esm 추가
yarn add esm

// 기존 src/index.js 이름을 main.js로 변경하고, index.js를 아래와 같이 새로 생성
require = require('esm')(module);
module.exports = require('./mains.js');

//package.json 스크립트 변경
"scripts" : {
	"start" : "node -r esm src",
    "start:dev" : "nodemon --watch src -r esm src/indx.js"
}


//jsconfig.json 파일 추가하면 자동 완성을 통해 모듈을 불러올 수 있음
{
  "compilerOptions": {
    "target": "es6",
    "module": "es2015"
  },
  "include": ["src/**/*"]
}

22.5 데이터베이스의 스키마와 모델

  • 스키마 - 컬렉션에 들어가는 문서 내부의 각 필드가 어떤 형식으로 되어 있는지 정의하는 객체
  • 모델 - 스키마를 사용하여 만드는 인스턴스, 데이터베이스에 실제 작업을 처리할 수 있는 함수들을 지니고 있는 객체

22.6 MongoDB Compass의 설치 및 사용

MongoDB를 위한 GUI 프로그램
(www.mongodb.com/try/download/compass)

22.7 데이터 생성과 조회

  • 생성 : 모델로 인스턴스를 생성하고 save함수 호출
  • 데이터 리스트 조회 : 모델 인스턴스의 find함수를 사용 (exec()를 붙여줘야 서버에 쿼리 요청)
  • 단일 데이터 조회 : 모델 인스턴스의 findById 함수를 사용

22.8 데이터 삭제와 수정

  • 삭제 : remove - 특정 조건을 만족하는 데이터를 모두 지움, findByIdAndRemove - id를 찾아서 지움 , findOneAndRemove -  특정 조건을 만족하는 데이터 하나를 찾아서 제거
  • 수정 : findByIdAndUpdate 함수를 사용. id, 업데이트 내용, 업데이트 옵션을 차례로 파라미터로 받음

22.9 요청 검증

22.9.1 ObjectId 검증

//mongoDB에 적합한 object id인지 검증
import mongoose from 'mongoose';

const { ObjectId } = mongoose.Types;
ObjectId.isValid(id);

//미들웨어를 사용해서 검증이 필요한 컨트롤러에 삽입
export const checkObjectId = (ctx, next) => {
    const {id} = ctx.params;
    if (!ObjectId.isValid(id)) {
        ctx.status = 400;
        return;
    }
    return next();
}

posts.get('/:id', postCtrl.checkObjectId, postCtrl.remove);

22.9.2 Request Body 검증

//Joi 설치
yarn add joi

22.10 페이지네이션 구현

22.10.1 가짜 데이터 생성하기
22.10.2 포스트를 역순으로 불러오기
22.10.3 보이는 개수 제한
22.10.4 페이지 기능 구현
22.10.5 마지막 페이지 번호 알려 주기
22.10.6 내용 길이 제한

 

21.1 소개하기

21.2 작업 환경 준비

// Node 설치 확인
node --version

// koa 설치
yarn add koa

//eslint 설치
yarn add --dev eslint
yarn run eslint --init

//prettier 설정
{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80
}

21.3 Koa 기본 사용법

21.3.1 서버 띄우기

const Koa = require('koa');

const app = new Koa();

app.use(ctx => {
	ctx.body = 'hello world';
});

app.listen(3000, () => {
	console.log('Listening to port 3000');
});

21.3.2 미들웨어

  • next 함수는 Promise를 반환
  • Koa는 async/await을 지원

21.4 nodemon 사용하기

//nodemon 설치
yarn add --dev nodemon

//package.json
//yarn start:dev로 시작하면 nodemon은 src 디렉터리를 주시하고 있다고 해당 폴더 안의 변화를 감지하여 src/index.js를 재시작
"scripts" : {
	"start" : "node src",
    "start:dev" : "nodemon --watch src/ src/index.js"
}

21.5 koa-router 사용하기

20.1 서버 사이드 렌더링의 이해

20.1.1 서버 사이드 렌더링의 장점

  • 검색 엔진이 페이지를 원활하게 수집
  • 초기 랜더링 성능 개선

20.1.2 서버 사이드 렌더링의 단점

  • 서버 리소스를 사용함. (캐싱과 로드밸런싱을 통해 성능 최적화 필요)
  • 프로젝트 구조가 복잡해질 수 있음.

20.13 서버 사이드 렌더링과 코드 스플리팅 충돌

20.2 프로젝트 준비하기

//index.js 프로젝트에 라우터 적용
import {BrowserRouter} from 'react-router-dom';

ReactDOM.render(
    <BrowserRouter>
        <App/>
    </BrowserRouter>,
    document.getElementById('root')
);


//app.js 컴포넌트에 라우트 설정(설정된 경로의 컴포넌트가 나타남)
import {Route} from 'react-router-dom';

function App() {
    return (
        <div>
            <Menu/>
            <hr/>
            <Route path="/red" component={RedPage}/>
            <Route path="/blue" component={BluePage}/>
        </div>
    );
}


//라우트 링크 설정
import {Link} from "react-router-dom";

const Menu = () => {
    return (
        <ul>
            <li>
                <Link to="/red">Red</Link>
            </li>
            <li>
                <Link to="/blue">Blue</Link>
            </li>
        </ul>
    );
};

20.3 서버 사이드 렌더링 구현하기

//webpack 설정 보이게 하기
git add .
git commit -m 'commit before ejcet'
yarn eject

20.3.1 서버 사이드 렌더링용 엔트리 만들기
20.3.2 서버 사이드 렌더링 전용 웹팩 환경 설정 작성하기
20.3.3 빌드 스크립트 작성하기
20.3.4 서버 코드 작성하기
20.3.5 정적 파일 제공하기

20.4 데이터 로딩

20.4.1 redux-thunk 코드 준비하기
20.4.2 Users, UsersContainer 컴포넌트 준비하기
20.4.3 PreloadContext 만들기
20.4.4 서버에서 리덕스 설정 및 PreloadContext 사용하기
20.4.5 스크립트로 스토어 초기 상태 주입하기
20.4.6 redux-saga 코드 준비하기
20.4.7 User, UserContainer 컴포넌트 준비하기
20.4.8 redux-saga를 위한 서버 사이드 렌더링 작업
20.4.9 usePreloader Hook 만들어서 사용하기

20.5 서버 사이드 렌더링과 코드 스플리팅

20.5.1 라우트 컴포넌트 스플리팅하기
20.5.2 웹팩과 babel 플러그인 적용
20.5.3 필요한 청크 파일 경로 추출하기
20.5.4 loadableReady와 hydrate

20.6 서버 사이드 렌더링의 환경 구축을 위한 대안

19.1 자바스크립트 함수 비동기 로딩

    const onClick = () => {
        import('./notify').then(result => result.default());
    }

19.2 React.lazy와 Suspense를 통한 컴포넌트 코드 스플리팅

19.2.1 state를 사용한 코드 스플리팅
19.2.2 React.lazy와 Suspense 사용하기
19.2.3 Loadable Components를 통한 코드 스플리팅

18.1 작업 환경 준비

18.2 미들웨어란?

18.2.1 미들웨어 만들기

18.2.2 redux-logger 사용하기

//미들웨어 생성
const loggerMiddleWare = store => next => action => {
    console.group(action && action.type);
    console.log('이전 상태', store.getState());
    console.log('액션', action);
    next(action);
    console.log('다음 상태', store.getState());
    console.groupEnd();
};

export default loggerMiddleWare;


//미들웨어 적용
import loggerMiddleware from "./lib/loggerMiddleware";
import {createLogger} from 'redux-logger'; //redux-logger 사용하기

const logger = createLogger();
const store = createStore(rootReducer, applyMiddleware(logger));
//const store = createStore(rootReducer, applyMiddleware(loggerMiddleware));

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root')
);

18.3 비동기 작업을 처리하는 미들웨어 사용

  • redux-thunk : 가장 많이 사용하는 비동기 처리 미들웨어. 객체가 아닌 함수 형태의 액션을 디스패치 할 수 있게 해 줌.
  • redux-saga : redux-thunk 다음으로 많이 사용되는 비동기 처리 미들웨어. 특정 액션 디스패치 후, 다른 액션을 디스패치시키는 규칙을 적용하여 비동기 작업 처리.

18.3.1. redux-thunk

18.3.1.1 Thunk란 ?
- 특정 작업을 나중에 할 수 있도록 미루기 위해 함수 형태로 감싼 것
18.3.1.2 미들웨어 적용하기

//추가
yarn add redux-thunk

//스토어를 만들때 적용
import ReduxThunk from 'redux-thunk';
const logger = createLogger();

const store = createStore(rootReducer, applyMiddleware(logger, ReduxThunk));

18.3.1.3 Thunk 생성 함수 만들기
18.3.1.4 웹 요청 비동기 작업 처리하기
18.3.1.5 리팩토링

18.3.2 redux-saga

18.3.2.1 제너레이터 함수 이해하기
18.3.2.2 비동기 카운터 만들기

//추가
yarn add redux-saga

//개발자 도구 라이브러리 추가, composWithDevTools를 리덕스 미들웨어와 함께 사용할 때는 applyMiddleware부분을 감싸주면 됨
yarn add redux-devtools-extension

//all 함수는 여러 사가를 합쳐 주는 역할
export function* rootSaga(){
    yield all([counterSaga()]);
}

//사가 미들웨어 등록
import createSagaMiddleWare from 'redux-saga';
import {composeWithDevTools} from 'redux-devtools-extension'; 

const logger = createLogger();
const sagaMiddleware = createSagaMiddleWare();
const store = createStore(
    rootReducer,
    composeWithDevTools(applyMiddleware(logger, ReduxThunk, sagaMiddleware))
);
sagaMiddleware.run(rootSaga);

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root')
);

18.3.2.3 API 요청 상태 관리하기
18.3.2.4 리팩토링
18.3.2.5 알아 두면 유용한 기능들

17.1 작업 환경 설정

//redux, react-redux라이브러리 설치
yarn add redux react-redux

//.prettierrc
{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80
}

17.2 UI 준비하기

17.3 리덕스 관련 코드 작성하기

액션 타입, 액션 생성 함수, 리듀서 코드를 작성. 작성 방법은 아래 두 가지로 나뉨.

  • actions, constants, reducers로 각각 디렉터리를 구분해서 작성하는 방법
  • Ducks패턴 : 액션 타입, 액션 생성함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 다 작성하는 방식

  1. 액션 타입 정의하기
  2. 액션 생성 함수 만들기
  3. 초기 상태 및 리듀서 함수 만들기
  4. 루트 리듀서 만들기

17.4 리액트 애플리케이션에 리덕스 적용하기

17.4.1 스토어 만들기

17.4.2 Provider 컴포넌트를 사용하여 프로젝트에 리덕스 적용하기

17.4.3 Redux DevTools의 설치 및 적용 

//크롬 확장 프로그램 설치(Redux DevTools)
1.
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

2. 
패키지를 설치하여 적용(코드 훨씬 깔끔해짐)
yarn add redux-devtools-extension

import {composeWithDevTools} from "redux-devtools-extension";
const store = createStore(rootReducer, composeWithDevTools());

//index.js
import rootReducer from "./modules";
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import {composeWithDevTools} from "redux-devtools-extension";

const store = createStore(rootReducer, composeWithDevTools());

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root')
);

17.5 컨테이너 컴포넌트 만들기

const makeContainer = connect(mapStateToProps, mapDispatchToProps);
makeContainer(타깃 컴포넌트);

//connect 함수사용
const mapStateToProps = state => ({
    number: state.data.number
});

const mapDispatchToProps = dispatch => ({
    increase: () => dispatch(increase()),
    decrease: () => dispatch(decrease())
});

export default connect(mapStateToProps, mapDispatchToProps)(Container);


//bindActionCreators함수 사용
export default connect(
    state => ({
        number: state.data.number
    }),
    dispatch => bindActionCreators(
        {
            increase,
            decrease,
        }, dispatch)
)(Container);


//mapDispatchToProps 파라미터에 액션 생성함수 객체 삽입 (connect 함수가 내부적으로 bindActionCreators 작업 대신 해줌)
export default connect(
    state => ({
        number: state.data.number
    }),
    {
        increase,
        decrease
    }
)(Container);

17.6 리덕스 더 편하게 사용하기

17.6.1 redux-actions

//라이브러리 설치
yarn add redux-actions


//createAction, handleActions를 사용해서 가독성 향상
import {createAction, handleActions} from "redux-actions";

const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

const initialState = {
    number: 0
};

const counter = handleActions(
    {
        [INCREASE]: (state, action) => ({number: state.number + 1}),
        [DECREASE]: (state, action) => ({number: state.number - 1}),
    },
    initialState
)

export default counter;

17.6.2 immer

//spread, 배열 내장 함수 대신 immer를 사용한 불변성 유지 업데이트

//전
({...state, input: data}),

//후
produce(state, draft => {
	draft.input = data
}),

17.7 Hooks를 사용하여 컨테이너 컴포넌트 만들기

17.7.1 useSelector로 상태 조회하기

const result = useSelector(state => state.data.num);

17.7.2 useDispatch를 사용하여 액션 디스패치하기

const dispatch = useDispatch();
dispatch({type : 'TEST_ACTION'});
useDispatch를 사용할 때는 성능 최적화를 위해 useCallback을 함께 사용하는 것을 권장.

17.7.3 useStore를 사용하여 리덕스 스토어 사용하기

const store = useStore();
store.dispatch({type : 'TEST_ACTION'});
store.getState();

17.7.5 useActions 유틸 Hook을 만들어서 사용하기

17.7.6 connect 함수와의 주요 차이점

useSelector를 사용할 때는 성능 최적화를 위해 React.memo를 컨테이너 컴포넌트에 사용해 주어야 함.

16.1 개념 미리 정리하기

16.1.1 액션

상태에 어떠한 변화가 필요하면 액션이란 것이 발생함. 

{
	type : 'TOGGLE_VALUE'
}

16.1.2 액션 생성 함수

액션 객체를 만들어 주는 함수

//일반함수 형태
function addTest(data) {
	return {
    	type : 'ADD_TEST',
        data
    }
}

//화살표함수 형태
const addTest = text => ({
	type : 'ADD_TEST',
    text
});

16.1.3 리듀서

리듀서는 변화를 일으키는 함수. 액션을 만들어서 발생시켜면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아 옴. 그리고 두 값을 참고하여 새로운 상태를 반환해줌.

const initialState = {
	data : 1
};

function reducer(state = initialState, action) {
	switch (action.type) {
    	case ADD : 
        	return {
            	data : state.data + 1
            };
        default :
        	return state;
    }
}

16.1.4 스토어

리덕스를 적용하기 위해 스토어를 만듬. 한 개의 프로젝트는 단 하나의 스토어만 가질 수 있음. 스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에 몇 가지 중요한 내장 함수를 지님.

16.1.5 디스패치

스토어의 내장 함수 중 하나. 액션을 발생시키는 것. dispatch(action)과 같은 형태로 액션 객체를 파라미터로 넣어서 호출함. 이는 리듀서 함수를 실행시켜서 새로운 상태를 만들어 줌.

16.1.6 구독

스토어의 내장 함수 중 하나. subscribe 함수 안에 리스터 함수를 파라미터로 넣어서 호출해 주면 이 함수가 액션이 디스패치되어 상태가   업데이트될 때마다 호출됨.

16.2 리액트 없이 쓰는 리덕스

16.3 리덕스의 세 가지 규칙

16.3.1 단일 스토어

하나의 애플리케이션 안에는 하나의 스토어가 들어 있음.

16.3.2 읽기 전용 상태

상태를 업데이트할 때 기존 객체를 건드리지 않고 새로운 객체를 생성해 주어야 함. (리덕스가 내부적으로 데이터 변경 감지를 위해 얕은 비교 검사를 하기 때문)

16.3.3 리듀서는 순수한 함수

순수한 함수는 다음 조건을 만족해야함.

  • 리듀서 함수는 이전 상태와 액션 객체를 파라미터로 받음.
  • 파라미터 외의 값에는 의존하면 안 됨.
  • 이전 상태는 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어서 반환함.
  • 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과 값을 반환해야 함.

15.1 Context API를 사용한 전역 상태 관리 흐름 이해하기

15.2 Context API 사용법 익히기

15.2.1 새 Context 만들기

import {createContext} from 'react';

const TestContext = createContext({data: 'black'});

export default TestContext;

 

15.2.2 Consumer 사용하기

import React from "react";
import TestContext from "../context/test";

const TestBox = () => {
    return (
        <TestContext.Consumer>
            {value =>(
            	<div>value.data</div>
            )}
        </TestContext.Consumer>
    );
};

export default TestBox;

15.2.3 Provider

Provider를 사용하여 Context value 변경

import React from 'react';
import TestContext from "./context/Test";

function App() {
    return (
        <TestContext.Provider value={{data:'red'}}>
			...
        </TestContext.Provider>
    );
}

export default App;

15.3 동적 Context 사용하기

15.4 Consumer 대신 Hook 또는 static contextType 사용하기

15.4.1 useContext Hook 사용하기

children에 함수를 전달하는 Render Props 패턴이 불편하다면, useContext Hook을 사용하여 훨씬 편하게 Context 값을 조회할 수 있음.

15.4.2 static contextType 사용하기

클래스형 컴포넌트에서 Context를 좀더 쉽게 사용하고 싶다면 static contextType을 정의하는 방법이 있음.

14.1 비동기 작업의 이해

14.1.1 콜백 함수
14.1.2 Promise
14.1.3 asnyc/await

화살표 함수에 async/await을 적용할 때는 async () => {}와 같은 형식으로 적용함.

const onClick = async () => {
	try {
    	const response = await axios.get(...);
        setData(response.data);
    } catch(e) {
    ...
    }
};

14.2 axios로 API 호출해서 데이터 받아 오기

14.3 newsapi API 키 발급받기

14.4 뉴스 뷰어 UI 만들기

14.5 데이터 연동하기

userEffect 내부에서 async/await을 사용하고 싶다면, 함수 내부에 async 키워드가 붙은 다른 함수를 반들어 사용해야함.

useEffect(() => {
	const fetchData = async () => {
      setLoading(true);
      try {
        ...
        const response = await axios.get(...);
        setData(response.data.xxx);
      } catch (e) {
      	console.log(e);
      }
      setLoading(false);
    }
    fetchData();
}, [category]);

14.6 카테고리 기능 구현하기

14.7 리액트 라우터 구현하기

리액트 라우터의 Router, NavLink등을 사용하여 라우터 구현

//리액트 라우터 설치
yarn add react-router-dom

14.8 usePromise 커스텀 Hook 만들기

+ Recent posts