<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>맹수개발</title>
    <link>https://myeongsu0257.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 13:48:21 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>최맹수</managingEditor>
    <image>
      <title>맹수개발</title>
      <url>https://tistory1.daumcdn.net/tistory/6245892/attach/8d7965c30a714c0f8bcd3c25c6729082</url>
      <link>https://myeongsu0257.tistory.com</link>
    </image>
    <item>
      <title>02. 클린코드 리액트(React) - State</title>
      <link>https://myeongsu0257.tistory.com/240</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 올바른 초기값 설정&lt;/h2&gt;
&lt;pre id=&quot;code_1711027383835&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [count, setCount] = useState();
const [list, setList] = useState();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 초기값설정은 아래처럼 변경되어야 올바른 초기값 설정입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711027430115&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [ count, setCount ] = useState(0);
const [ list, setList ] = useState([]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이렇게 값을 설정해야하는지 한번 알아보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기값 ?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 먼저 렌더링될대 순간적으로 보여질 수 있는 값이기도 하다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;당연히 초기에 렌더링 되는 값&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기값 지키지 않을 경우 ?&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;렌더링 이슈, 무한루프, 타입 불일치로 의도치 않은 동작 발생&amp;nbsp; -&amp;gt; 런타임 에러&amp;nbsp;&lt;/li&gt;
&lt;li&gt;초기값을 넣지 않으면 ? : undefined&amp;nbsp;&lt;/li&gt;
&lt;li&gt;상태를 CRUD =&amp;gt; 상태를 지울때도 초기값을 잘 기억해놔야 원상태로 돌아간다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기값을 잘 설정해주면 빈값이나 NULL 처리를 할때 불필요한 방어코드도 줄여준다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시코드를 보면 아래코드에서는 list라는 상태값에 아무런 초기값도 할당하지 않은 상태입니다. 그 상태에서 useEffect로 api의 데이터를 fetch해서 list 상태값에 받아옵니다. 그리고 return문에서 해당 상태값을 이용해 map으로 요소들을 보여주는 코드입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711028877473&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;
import &quot;./App.css&quot;;

function App() {
	const [count, setCount] = useState(0);
	const [list, setList] = useState();
	useEffect(() =&amp;gt; {
		const fetchData = async () =&amp;gt; {
			const response = await fetch(&quot;https://api.example.com/data&quot;);
			const result = await response.json();

			setList(result);
		};
		fetchData();
	}, []);
	return (
		&amp;lt;&amp;gt;
			&amp;lt;p&amp;gt;Count:{count}&amp;lt;/p&amp;gt;
			&amp;lt;button
				onClick={() =&amp;gt; {
					setCount(count + 1);
				}}&amp;gt;
				Increment
			&amp;lt;/button&amp;gt;

			{list.map((item) =&amp;gt; {
				console.log(item);
			})}
		&amp;lt;/&amp;gt;
	);
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 실행시켜보면 해당 값이 빈값이거나 null값일때 처리를 해주지 않아서 렌더링 오류가 발생하고 아무런 화면이 보이지 않게 됩니다. 그래서 위의 코드에서 list 상태값을 선언할때 빈배열 ' [] ' 을 넣어주게 된다면 이를 방지할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711029209064&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [list, setList] = useState([]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기상태를 올바르게 설정하자&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 업데이트 되지 않는 값&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업데이트가 되지 않는 일반적인 객체라면 리액트 외부로 내보내는 것이 좋습니다. 예를 들어보면 아래의 코드에서 INFO라는 싱글리터럴객체가 있습니다. 이런 케이스는 상수를 다루거나 아니면 일반적인 방치가 흔한 케이스입니다. 중요한것은 이 객체는 어디서도 지금 업데이트가 되고 있지 않습니다. 즉 이객체에 있는 속성을 바꾸는 경우가 전혀 존재하지 않습니다. 그리고 return문을 보면 props로 객체를 전달해주고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711029656421&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;
import &quot;./App.css&quot;;

function App() {
	const INFO = {
		name: &quot;My Component&quot;,
		value: &quot;Clean Code React&quot;,
	};
	const [count, setCount] = useState(0);
	const onIncrement = () =&amp;gt; {
		setCount(count + 1);
	};
	const onDecrement = () =&amp;gt; {
		setCount(count - 1);
	};
	return (
		&amp;lt;&amp;gt;
			&amp;lt;ShowCount info={INFO} count={count} /&amp;gt;
		&amp;lt;/&amp;gt;
	);
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 경우는 정말 좋지 못한 케이스입니다. 일단 리액트 컴포넌트 내부에 있기 때문에 매번 렌더링 될때마다 같은 값이더라도 또 다시 참조해서 트리거하고 계산해야 될 시기와 기억해야 될 시기에 대한 로직이 전혀 들어가 있지 않습니다. 그러다 보니까 불필요하게 참조하고 물고 있는 겁니다. 그래서 어떻게 하냐면 컴포넌트 외부로 빼주면됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711029974517&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;
import &quot;./App.css&quot;;

const INFO = {
	name: &quot;My Component&quot;,
	value: &quot;Clean Code React&quot;,
};

function App() {
	const [count, setCount] = useState(0);
	const onIncrement = () =&amp;gt; {
		setCount(count + 1);
	};
	const onDecrement = () =&amp;gt; {
		setCount(count - 1);
	};
	return (
		&amp;lt;&amp;gt;
			&amp;lt;ShowCount info={INFO} count={count} /&amp;gt;
		&amp;lt;/&amp;gt;
	);
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업데이트가 되지 않는 일반적인 객체라면 리액트 외부로 내보내기&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.플래그 상태&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플래그 값 : 프로그래밍에서 주로 특정 조건 혹은 제어를 위한 조건을 불리언으로 나타내는 값&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플래그 상태의 가장 흔한 예는 무엇일까?&amp;nbsp;바로 로그인이다. 우리가만드는 리액트 앱은 대채적으로 client state 프론트단에서 상태를 가지고 화면을 보여주는 경우가 많기 때문에 이러한 값들을 가지기 위해 정말 많은 값들을 다룹니다. 토큰, 쿠키, valid한지,새로운유저인지 등 수많은 값을 조건으로 분기합니다. 아래처럼 useEffect로 다 잡아서 관리하고 분기문을 넣어서 때려넣는 경우가 많습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717231439707&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;
import &quot;./App.css&quot;;

function FlagState() {
	const [isLogin, setIsLogin] = useState(false);

	useEffect(()=&amp;gt;{
		if(hasToken){
			setIsLogin(true);
		}
		if(hasCookie){
			setIsLogin(true);
		}
		if(isValidCookie){
			setIsLogin(true);
		}
		if(isNewUser===false){
			setIsLogin(true);
		}
		if(isValidToken){
			setIsLogin(true);
		}
	},[hasToekn,hasCookie,isValidCookie,isNewUser,isValidToken]);
	return (
		&amp;lt;&amp;gt;
			&amp;lt;div&amp;gt;{isLogin&amp;amp;&amp;amp;'안녕하세요! 반갑습니다!'}&amp;lt;/div&amp;gt;
		&amp;lt;/&amp;gt;
	);
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 좀더 깔끔히 해보면 아래처럼 if문을 하나로 합칠 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717231785898&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;
import &quot;./App.css&quot;;

function FlagState() {
	const [isLogin, setIsLogin] = useState(false);
	
	useEffect(()=&amp;gt;{
		if( hasToken &amp;amp;&amp;amp;
			hasCookie &amp;amp;&amp;amp;
			isValidCookie &amp;amp;&amp;amp;
			isNewUser==false &amp;amp;&amp;amp;
			isValidToken
			)
			{
			setIsLogin(true);
			}
	},[hasToekn,hasCookie,isValidCookie,isNewUser,isValidToken]);
	return (
		&amp;lt;&amp;gt;
			&amp;lt;div&amp;gt;{isLogin&amp;amp;&amp;amp;'안녕하세요! 반갑습니다!'}&amp;lt;/div&amp;gt;
		&amp;lt;/&amp;gt;
	);
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 isLogin으로 아래처럼 묶어볼 수 있습니다. 그런데 이전에 '불필요한 상태를 최대한 만들지 않는것'이 좋다고 했습니다. 그러면 useEffect안의 isLogin을 밖으로 뺄 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717234473397&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function FlagState() {
	const [isLogin, setIsLogin] = useState(false);

	useEffect(()=&amp;gt;{
		const isLogin=hasToken &amp;amp;&amp;amp;
		hasCookie &amp;amp;&amp;amp;
		isValidCookie &amp;amp;&amp;amp;
		isNewUser==false &amp;amp;&amp;amp;
		isValidToken;

		if(isLogin){
			setIsLogin(true);
		}
	},[hasToekn,hasCookie,isValidCookie,isNewUser,isValidToken]);
	return (
		&amp;lt;&amp;gt;
			&amp;lt;div&amp;gt;{isLogin&amp;amp;&amp;amp;'안녕하세요! 반갑습니다!'}&amp;lt;/div&amp;gt;
		&amp;lt;/&amp;gt;
	);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 최종적인 코드를 보면 토큰과 쿠키가 true인지 쿠키와토큰이 유효한값을 가지는지 그리고 새로운 유저가 아닐때 라는 조건이 지금 매 랜더링마다 다시계산이 되면서 isLogin에 들어가고 아래 return에서 불리언 값이 판단이 되기 때문에 굳이 상태가 필요하지 않다는 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717234610535&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function FlagState() {
	const isLogin =
		hasToken &amp;amp;&amp;amp;
		hasCookie &amp;amp;&amp;amp;
		isValidCookie &amp;amp;&amp;amp;
		isNewUser == false &amp;amp;&amp;amp;
		isValidToken;

	return (
		&amp;lt;&amp;gt;
			&amp;lt;div&amp;gt;{isLogin &amp;amp;&amp;amp; &quot;안녕하세요! 반갑습니다!&quot;}&amp;lt;/div&amp;gt;
		&amp;lt;/&amp;gt;
	);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 하면 굳이 상태를 만들고 그 상태를 위한 분기문을 칠 필요가 없습니다. 무슨 얘기냐면 로그인 대상에 무언가 값이 또 바뀌어서 추가적으로 요구사항이 생긴다면 정말 골치 아파집니다. 이럴때마다 if문이 늘어나고 switch케이스가 늘어나고 계속 조건들이 늘어나는데 이렇게 한번 플래그 변수를 컴포넌트 내부에서 만들고 이 조건만 잘 관리하면 굳이 큰 수정 없이 큰버전 변경없이 관리할 수가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;usetState 대신 플래그로 상태를 정의할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;불필요한 상태&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료한 일만 가지고 있는 유저(MOCK_DATA에 리스트객체의 속성 중 completed 값이 true인) 리스트를 만든다고 했을때 filter로 completed가 true인 녀석들만 넣어준다고 할 수 있습니다. 하지만 이렇게 하면 큰 낭비입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717242355624&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [userList,setUserList] = useState(MOCK_DATA);  // 1. 초기상태선언
const [complUserList,setComplUserList]=useState(MOCK_DATA); // 2. 변경 후 저장할 상태 선언

useEffect(()=&amp;gt;{
	const newList = complUserList.filter((user):boolean=&amp;gt;user.completed==true);
	setUserList(newList);
},[userList]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 불필요한 상태를 제거 해보면 아래처럼 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717242393576&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const complUserList = complUserList.filter((user):boolean=&amp;gt;user.completed===true);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링마다 고유한 값을 가질 수 있다라고 가정을 하면 렌더링 될때마다 값이 바뀌고 관여되지만 이 값은 굳이 상태로 만들 필요가 없다는 겁니다. 결국엔 렌더링될때마다 고유의 'complUserList'가 생성이 되기 때문에 관리할 필요도 없고 set할 필요도 없어집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 React컴포넌트 내부에서 변수는 어떤 역할을 할까?라는 생각이 들 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트 내부에서의 변수 : 렌더링마다 고유한 값을 가지는 계산된 값을 가진다. 즉, Computed value 랜더링마다 고유한 값을 가지는 값&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;props를 useState에 넣지 않고 바로 return문에 사용하기&lt;/li&gt;
&lt;li&gt;컴포넌트 내부 변수는 렌더링마다 고유한 값을 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useState 대신 useRef&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 useState랑 useRef를 직접적으로 비교하기보다 useRef는 필요에 따라 많이 사용합니다. 하지만 간혹 useState를 대신해서 useRef를 사용할 때가 있습니다. 기본적인 차이는 useState와 다르게 useRef는 가변 컨테이너 라고볼 수 있습니다. 즉 한번 고정된 값을 컴포넌트에서 계속해서 사용하는 값을 굳이 useState로 사용할 필요가 없습니다. 즉 '컴포넌트의 전체적인 수명과 동일하게 지속된 정보를 일관적으로 제공해야 하는 경우' useState로 만들 필요가 없습니다. 만약 useState로 사용할 경우에는 setState를 사용하면서 리렌더가 컴포넌트에 발생하기 때문에 좋지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717245982863&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const component = () =&amp;gt; {
	const [isMount, setIsMount] = useState(false);

	useEffect(() =&amp;gt; {
		if (!isMount) {
			setIsMount(true);
		}
	}, [isMount]);

	return &amp;lt;&amp;gt;&amp;lt;/&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 DOM과 관련할때만 useRef를 사용할 필요가 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717246325970&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const component = () =&amp;gt; {
	const isMount = useRef(false);

    useEffect(()=&amp;gt;{
        isMount.current=true;

        return()=&amp;gt;(isMount.current=false);
    },[]);

	return &amp;lt;&amp;gt;&amp;lt;/&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/react</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/240</guid>
      <comments>https://myeongsu0257.tistory.com/240#entry240comment</comments>
      <pubDate>Thu, 21 Mar 2024 23:07:15 +0900</pubDate>
    </item>
    <item>
      <title>01. 클린코드 리액트(React) - 클린코드, Vite</title>
      <link>https://myeongsu0257.tistory.com/239</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;클린코드란&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 클린코드는 무적이 아니다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클린코드가 무조건 최고는 아니다&lt;/li&gt;
&lt;li&gt;무조건 클린코드 === 좋은코드는 아니다&lt;/li&gt;
&lt;li&gt;클린코드를 지켰다며 TDD를 통과했다며 기뻐하지만 React 앱이 동작하지 않음&lt;/li&gt;
&lt;li&gt;성급한 기술 도입으로 과도한 추상화 팀원들에게 피해를 줌&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 클린코드보다 중요한 것은 상당히 많다.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작성한 코드의 확장성&lt;/li&gt;
&lt;li&gt;React 앱의 완성도&lt;/li&gt;
&lt;li&gt;앱의 완성도가 떨어지는 경우 사소한 동작에도 잦은 버그가 발생&lt;/li&gt;
&lt;li&gt;함께 개발하는 팀원들과의 코드를 두고 할 수 있는 소통&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vite란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에 따르면 프랑스어로 빠르다를 의미하고, 빠르고 간결한 모던 웹 프로젝트 개발 경험에 초점을 맞춰 탄생한 빌드 도구이다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 시 네이티브 ES Module을 넘어 더욱 다양한 기능을 제공&lt;/li&gt;
&lt;li&gt;번들링 시, Rollup 기반의 다양한 빌드 커멘드를 사용할 수 있습니다. 이는 높은 수준으로 최적화된 정적(static) 리소스들을 배포할 수 있게끔 하며, 미리 정의된 설정(Pre-configured)을 제공합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Creact React App 대신 사용하는 이유?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CRA는 JS로 구성된 Webpack을 사용하는데 속도가 느린편입니다. 평소에는 못느길 쑤 있지만 처리해야할 코드의 양이 많아질수록 느린 속도를 체감할 수 있고 현재 React 공식문서에 가보면 과거에는 CRA를 사용하라고 되어 있었지만 현재는 나와있지 않습니다. 그래서 Esbuild를 기반으로 만들어진 빌드툴인 Vite(비트)를 사용하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Esbuild : Go 언어로 작성된 Js빌드툴로 속도가 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인은 Window환경이기 때문에 아래의 명령어를 입력해줬습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1710942812179&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm create vite@latest cleanCodeReact -- --template react&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 입력하면 아래처럼 명령어가 표시되고 그대로 입력하면 정말 빠르게 build가 됩니다. npm install까지 입력해보면&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCWZD4/btsFYNSPvsS/cK2hOMGk8XaJ51jK1ZrKcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCWZD4/btsFYNSPvsS/cK2hOMGk8XaJ51jK1ZrKcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCWZD4/btsFYNSPvsS/cK2hOMGk8XaJ51jK1ZrKcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCWZD4%2FbtsFYNSPvsS%2FcK2hOMGk8XaJ51jK1ZrKcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;257&quot; height=&quot;116&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 생성된 폴더&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uOhlg/btsFW6Z8twG/E7rl2gTazpVSIzOKvsTP81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uOhlg/btsFW6Z8twG/E7rl2gTazpVSIzOKvsTP81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uOhlg/btsFW6Z8twG/E7rl2gTazpVSIzOKvsTP81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuOhlg%2FbtsFW6Z8twG%2FE7rl2gTazpVSIzOKvsTP81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;830&quot; height=&quot;370&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vs Code로 폴더를 열어보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/db9Q8q/btsFWt9rLIi/K3xEICUovQZnsBfWl3QYG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/db9Q8q/btsFWt9rLIi/K3xEICUovQZnsBfWl3QYG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/db9Q8q/btsFWt9rLIi/K3xEICUovQZnsBfWl3QYG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdb9Q8q%2FbtsFWt9rLIi%2FK3xEICUovQZnsBfWl3QYG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1159&quot; height=&quot;397&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 실행시켜 보겠습니다. npm run dev를 입력하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 화면을 보시면 정말 빠르게 리액트 개발환경을 구축했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p9Ycn/btsFYnmDqBj/WkGMozOoz00TtApsZp2620/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p9Ycn/btsFYnmDqBj/WkGMozOoz00TtApsZp2620/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p9Ycn/btsFYnmDqBj/WkGMozOoz00TtApsZp2620/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp9Ycn%2FbtsFYnmDqBj%2FWkGMozOoz00TtApsZp2620%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;742&quot; height=&quot;729&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;816&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/react</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/239</guid>
      <comments>https://myeongsu0257.tistory.com/239#entry239comment</comments>
      <pubDate>Wed, 20 Mar 2024 22:59:34 +0900</pubDate>
    </item>
    <item>
      <title>자바 - 변수, 연산자, 반복문, 스코프, 형변환, Scanner, 배열, 메소드</title>
      <link>https://myeongsu0257.tistory.com/238</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;변수&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변수초기화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 선언하고, 선언한 변수에 처음으로 값을 저장하는 것을 변수 초기화라고 한다. 자바는 변수를 초기화 하도록 강제한다.(초기화 하지 않으면 에러가 발생) -&amp;gt; 지역변수&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;String&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열을 다룬다. 큰따옴표를 사용해야 한다. 예) &quot;hello java&quot;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;String은 첫글자가 대문자로 시작하는 특별한 타입이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리터럴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서 개발자가 직접 적은 100, 10.5, true, 'A', &quot;Hello Java&quot;와 같은 고정된 값을 프로그래밍 용어로 리터럴(literal)이라 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKLBKN/btsFiMHZifJ/3L9n7yNeNlrDP3ClOajZmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKLBKN/btsFiMHZifJ/3L9n7yNeNlrDP3ClOajZmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKLBKN/btsFiMHZifJ/3L9n7yNeNlrDP3ClOajZmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKLBKN%2FbtsFiMHZifJ%2F3L9n7yNeNlrDP3ClOajZmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;118&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수의 값은 변할 수 있지만 리터럴은 개발자가 직접 입력한 고정된 값이다. 따라서 리터럴 자체는 변하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변수 타입 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 타입은 실무에서 거의 사용하지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;byte : 표현 길이가 너무 작다. 또 자바는 기본으로 4byte(int)를 효율적으로 계산하도록 설계되어 있다. int를 사용하자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;byte 타입을 직접 선언하고 여기에 숫자값을 대입해서 계산하는 일은 거의 없다&lt;/li&gt;
&lt;li&gt;대신에 파일을 바이트 단위로 다루기 때문에 파일 전송, 파일 복사 등에 주로 사용된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;short : 표현길이가 너무 작다. 또 자바는 기본으로 4byte(int)를 효율적으로 계산하도록 설계되어 있다. int 사용하자.&lt;/li&gt;
&lt;li&gt;float : 표현 길이와 정밀도가 낮다. 실수형은 double을 사용하자.&lt;/li&gt;
&lt;li&gt;char : 문자 하나를 표현하는 일은 거의 없다. 문자하나를 표현할 때도 문자열을 사용할 수 있다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 String a = &quot;b&quot;와 같이 사용하면 된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 자주 사용하는 타입은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정수 int, long : 자바는 정수에 기본으로 int를 사용한다. 만약 20억이 넘을 것 같으면 long을 쓰면 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일을 다룰 때는 byte를 사용한다.&lt;/li&gt;
&lt;li&gt;숫자가 어느정도 크다고 생각되면 뒤에 'L'을 붙이자.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;long num1 = 10000000000L;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실수 double : 실수는 고민하지 말고 double을 쓰면된다.&lt;/li&gt;
&lt;li&gt;불린형 boolean : true, false 참 거짓말을 표현한다. 이후 조건문에서 자주 사용된다.&lt;/li&gt;
&lt;li&gt;문자열 String : 문자를 다룰 때는 문자 하나든 문자열이든 모두 String을 사용하는 것이 편리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변수 명명 규칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 변수의 이름을 짓는데는 규칙과 관례가 있다. &lt;b&gt;규칙은 필수&lt;/b&gt;이다. 규칙을 지키지 않으면 컴파일 오류가 발생한다. &lt;b&gt;관례는 필수가 아니지만 전세계 개발자가 해당 관례를 따르기 때문에 사실상 규칙&lt;/b&gt;이라고 생각해도 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;규칙&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수 이름은 숫자로 시작할 수 없다.(ex. 1num, 1st)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그러나 숫자를 이름에 포함하는 것은 가능하다. (ex. myVar1, num1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이름에는 공백이 들어갈 수 없다.&lt;/li&gt;
&lt;li&gt;자바의 예약어를 변수 이름으로 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;변수 이름에는 영문자(a-z, A-Z), 숫자(0~9),달러기호($) 또는 밑줄(_)만 사용할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;관례&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소문자로 시작하는 낙타 표기법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수 이름은 소문자로 시작하는 것이 일반적이다. 여러 단어로 이루어진 변수 이름의 경우, 첫번째 단어는 소문자로 시작하고 그 이후의 단어는 대문자로 시작하는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;낙타 표기법(camel case)&lt;/b&gt;&lt;/span&gt;를 사용한다. (ex. orderDetal, myAccount)&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클래스는 대문자로 시작, 나머지는 소문자로 시작&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;자바에서 클래스 이름의 첫 글자는 대문자로 시작한다. 그리고 나머지 모두 첫 글자를 소문자로 시작&lt;/span&gt;&lt;/b&gt;한다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 : Person, OrderDetail&lt;/li&gt;
&lt;li&gt;변수를 포함한 나머지 : firstName, userAccount&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;여기에 딱 예외가 2개 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상수는 모두 대문자를 사용하고 언더바로 구분한다. (ex. USER_LIMIT )&lt;/li&gt;
&lt;li&gt;패키지는 모두 소문자를 사용한다.(ex. org.spring.boot )&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 : 변수 이름은 의미있고, 그 용도를 명확하게 설명해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;a,b : 이런 변수는 용도를 설명하지 않는다.&lt;/li&gt;
&lt;li&gt;studentCount, maxScore, userAccount, orderCount : 용도를 명확하게 설명한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;연산자&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자열 더하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 식은 문자열과 숫자를 더한다. 자바에서 문자와 숫자를 더하면 &lt;span style=&quot;color: #006dd7;&quot;&gt;숫자를 문자열로 변경&lt;/span&gt;한 다음에 서로 더한다. 쉽게 말해서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;문자열에 더하는 것은 다 문자열&lt;/b&gt;&lt;/span&gt;이 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708945600899&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String result3 = &quot;a + b = &quot;+10;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;143&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YgQdE/btsFlU6w4cG/H9hqNaXx40zj1eYLWl33x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YgQdE/btsFlU6w4cG/H9hqNaXx40zj1eYLWl33x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YgQdE/btsFlU6w4cG/H9hqNaXx40zj1eYLWl33x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYgQdE%2FbtsFlU6w4cG%2FH9hqNaXx40zj1eYLWl33x0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;143&quot; height=&quot;37&quot; data-origin-width=&quot;143&quot; data-origin-height=&quot;37&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연산자 우선순위 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연산자 우선순위는 상식선에서 생각하고, 애매하면 괄호를 사용하자&lt;/li&gt;
&lt;li&gt;누구나 코드를 보고 쉽고 명확하게 이해할 수 있어야 한다. 개발자들이 연산자 우선순위를 외우고 개발하는 것이 아니다! 복잡하면 명확하게 괄호를 넣어라!&amp;nbsp;&lt;/li&gt;
&lt;li&gt;개발에서 가장 중요한 것은 단순함과 명확함이다! 애매하거나 복잡하면 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비교연산자 문자열비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;문자열이 같은지 비교할 때는 ==이 아니라 .equals() 메서드를 사용&lt;/b&gt;&lt;/span&gt;해야 한다. ==를 사용하면 성공할 때도 있지만 실패할 때도 있다. 지금은 이 부분을 이해하기 어려우므로 지금은 단순히 문자열의 비교는 .equals() 메서드를 사용해야 한다 정도로 알고 있자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708947375129&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        String str1 = &quot;문자열1&quot;;
        String str2 = &quot;문자열2&quot;;

        boolean result1 = &quot;hello&quot;.equals(&quot;hello&quot;);
        boolean result2 = str1.equals(&quot;문자열1&quot;);
        boolean result3 = str1.equals(str2);

        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);

        System.out.println(&quot;hello&quot;==&quot;hello&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b81QOO/btsFmhmRJ3D/wTTjm0f6DRuoAZZktjsAP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b81QOO/btsFmhmRJ3D/wTTjm0f6DRuoAZZktjsAP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b81QOO/btsFmhmRJ3D/wTTjm0f6DRuoAZZktjsAP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb81QOO%2FbtsFmhmRJ3D%2FwTTjm0f6DRuoAZZktjsAP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;216&quot; height=&quot;119&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;반복문(while vs for)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정해진 횟수만큼 반복을 수행해야 하면 for문을 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 while문 사용 항상 정답은 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스코프&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스코프존재이유&lt;/h3&gt;
&lt;pre id=&quot;code_1710331329202&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package scope;

public class Scope1 {
    public static void main(String[] args) {
        int m = 10;
        int temp = 0;
        if(m&amp;gt;0){
            temp = m * 2;
            System.out.println(&quot;temp = &quot; + temp);
        }
        System.out.println(&quot;m = &quot; + m);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;비효율적인 메모리 사용&lt;/b&gt;&lt;/span&gt; : temp는 if코드 블록에서만 필요한데 main()이 종료될때까지 메모리에 유지된다. 따라서 불필요한 메모리가 낭비된다. -&amp;gt; if문안에 temp선언하면 효율적인 메모리 사용 가능&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;코드 복잡성 증가&lt;/b&gt;&lt;/span&gt; : 좋은 코드는 단순한 코드이다. temp는 if코드 블록에서만 필요하고 여기서만 사용하면 된다. 만약 if블록안에 temp를 선언했다면 if가 끝나고 나면 temp를 전혀 생각할 필요가 없다. 머리속에서 생각할 변수를 하나 줄일 수 있다. 누군가 이 코드를 유지보수 할 때 m은 물론이고 temp까지 계속 신경써야 한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;while vs for문 - 스코프관점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;while&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710331882654&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package loop;

public class While1 {
    public static void main(String[] args) {
        int sum = 0;
        int i = 1;
        int endNum = 3;
        while(i&amp;lt;=endNum){
            sum = sum + i;
            System.out.println(&quot;i=&quot;+i+&quot; sum=&quot;+sum);
            i++;
        }
        //... 아래에 더 많은 코드들이 있다고 가정
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;For&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710331956370&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package loop;

public class For2 {
    public static void main(String[] args) {
        int sum = 0;
        int endNum=3;
        for(int i=1; i&amp;lt;=endNum; i++){
            sum = sum + i;
            System.out.println(&quot;i=&quot;+i+&quot; sum=&quot;+sum);
        }
        //..아래에 더 많은 코드들이 있다고 가정
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;while문의 경우 변수 i의 스코프가 main() 메서드 전체가 된다. 반면에 for문의 경우 변수 i의 스코프가 for문 안으로 한정된다.&lt;/li&gt;
&lt;li&gt;따라서 변수 i와 같이 for문안에서만 사용되는 카운터 변수가 있다면 while문 보다는 for문을 사용해서 스코프의 범위를 제한하는 것이 메모리 사용과 유지보수 관점에서 더 좋다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수는 꼭 필요한 범위로 한정해서 사용하는 것이 좋다. 변수의 스코프는 꼭 필요한 곳으로 한정해서 사용하자. 메모리를 효율적으로 사용하고더 유지보수하기 좋은 코드를 만들 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;좋은 프로그램은 무한한 자유가 있는 프로그램이 아니라 적절한 제약이 있는 프로그램이다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;형변환&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 숫자를 표현할 수 있는 범위는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;int &amp;lt; long &amp;lt; double&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;int -&amp;gt; long -&amp;gt; double&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 범위에서 큰 범위로는 대입할 수 있다. ( 이것을 묵시적 형변환 또는 자동 형변환이라 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 범위에서 작은 범위의 대입은 다음과 같은 문제가 발생할 수 있다. 이때는 명시적 형변환을 사용해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소수점 버림&lt;/li&gt;
&lt;li&gt;오버플로우 ( long -&amp;gt; int로 변경한경우&amp;nbsp; 전혀 다른 숫자가 표현된다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;중요한 것은 오버플로우가 발생하는 것 자체가 문제라는 점이다. 오버플로우가 발생했을 때 결과가 어떻게 되는지 계산하는데 시간을 낭비하면 안된다!&lt;/b&gt; &lt;/span&gt;오버플로우 자체가 발생하지 않도록 막아야 한다. 대입하는 변수(intValue)의 타입을 int -&amp;gt; long으로 변경해서 사이즈를 늘리면 해결)&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐스팅 : 영어단어 &quot;cast&quot;에서 유래되었다. &quot;cast&quot;는 금속이나 다른 물질을 녹여서 특정한 형태나 모양으로 만드는 과정을 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 계산은 다음 2가지를 기억하자&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;같은 타입끼리의 계산은 같은 타입의 결과를 낸다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int + int는 int를, double + double은 double의 결과가 나온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int + long은 long + long으로 자동 형변환이 일어난다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;int + double은 double + double로 자동 형변환이 일언난다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산과 형변환&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 타입은 같은 결과를 낸다&lt;/li&gt;
&lt;li&gt;서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Scanner&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System.out을 통해서 출력을 했듯이, Systems.in을 통해서 사용자의 입력을 받을 수 있다. 그런데 자바가 제공하는 System.in을 통해서 사용자 입력을 받으려면 여러 과정을 거쳐야해서 복잡하고 어렵다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 이런 문제를 해결하기 위해 Scanner라는 클래스를 제공한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710415794478&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package scanner;

import java.util.Scanner;

public class Scanner1 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print(&quot;문자열을 입력하세요:&quot;);

        String str = scanner.nextLine();
        System.out.println(&quot;입력한 문자열: &quot;+str);

        System.out.print(&quot;정수를 입력하세요:&quot;);
        int intValue = scanner.nextInt();
        System.out.println(&quot;입력한 정수: &quot;+intValue);

        System.out.print(&quot;실수를 입력하세요:&quot;);
        double doubleValue = scanner.nextDouble();
        System.out.print(&quot;입력한 실수: &quot;+doubleValue);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;scanner.nextLine() : 엔터(\n)을 입력할 때까지 문자를 가져온다.&lt;/li&gt;
&lt;li&gt;scanner.nextInt() : 입력을 int형으로 가져온다. 정수 입력에 사용한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;scanner.nextDouble() : 입력을 double형으로 가져온다. 실수 입력에 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의해야 할점은 &lt;b&gt;입력받는 타입과 입력하는 타입이 다르면 오류가 발생한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수값을 입력받고 문자를 입력받을때 10\n 처럼 공백값이 들어갈 수 있다. 그래서&lt;b&gt; input.nextLine()&lt;/b&gt;을 사용해서 값을 비워준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배열&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열선언과 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1713076782084&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] students; // 1. 배열 변수 선언
students = new int[5]; // 2. 배열 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;배열 변수 선언
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열을 사용하려면 int[] students;와 같이 배열 변수를 선언해야 한다.&lt;/li&gt;
&lt;li&gt;배열 변수를 선언한다고 해서 아직 사용할 수 있는 배열이 만들어진 것은 아니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int a 에는 정수를, double b 에는 실수를 담을 수 있다. int[] students와 같은 배열 변수에는 배열을 담을 수 있다.( 배열 변수에는 10,20같은 값이 아니라 배열이라는 것을 담을 수 있다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;배열 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열을 사용하려면 배열을 생성해야 한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;new int[5] 라고 입력하면 5개의 int형 변수가 만들어진다.&lt;/li&gt;
&lt;li&gt;new는 새로 생성한다는 뜻이고, int[5]는 int 형 변수 5개라는 뜻이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;배열 초기화 : 자바는 배열을 생성할 때 그 내부값을 자동으로 초기화한다.&lt;/li&gt;
&lt;li&gt;배열 참조값 보관
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;new int[5]로 배열을 생성하면 배열의 크기만큼 메모리를 확보한다.(int형 5개 -&amp;gt; 20byte)&lt;/li&gt;
&lt;li&gt;x001은 참조값이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;앞서 선언한 배열 변수인 int[] students에 생성된 배열의 참조값(x001)을 보관한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;int[] students 변수는 new int [5]로 생성한 배열의 참조값을 가지고 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nBNi1/btsGB2W6SKY/tEBFoTQ2wsrt8nyU4OQQQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nBNi1/btsGB2W6SKY/tEBFoTQ2wsrt8nyU4OQQQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nBNi1/btsGB2W6SKY/tEBFoTQ2wsrt8nyU4OQQQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnBNi1%2FbtsGB2W6SKY%2FtEBFoTQ2wsrt8nyU4OQQQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;104&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조값을 확인하고 싶다면 배열의 변수를 출력해보면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1713077167862&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;System.out.println(students); // [I@6acbcfc0 @앞의 [I는 int형 배열을 뜻한다. @뒤에 16진수는 참조값을 뜻한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본형 vs 참조형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 변수 데이터 타입을 가장 크게보면 기본형과 참조형으로 분류할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본형(Primitive Type) : int, long, double, boolean 처럼 변수에 사용할 값을 직접 넣을 수 있는 데이터 타입&lt;/li&gt;
&lt;li&gt;참조형(Reference Type) : int[] students와 같이 데이터에 접근하기 위한 참조(주소)를 저장하는 데이터타입&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열은 왜 참조형을 사용할까? 단순히 그 안에 값을 넣고 사용하면 되는 것 아닐까?&amp;nbsp;기본형은 모두 사이즈가 명확하게 정해져있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713079506846&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int i; // 4byte
long l; // 8byte
double d; //8byte&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 배열은 동적으로 사이즈를 변경할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1713079542384&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int size = 10000; // 사용자가 입력한 값을 넣었다고 가정
new int[size]; // 이 코드가 실행되는 시점에 배열의 크기가 정해진다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본형은 선언과 동시에 크기가 정해진다. 따라서 크기를 동적으로 바꾸거나 할 수 없다. 반면에 앞서본 배열과 같은 참조형은 크기를 동적으로 할당할 수 있다. 이런 것을 동적메모리 할당이라 한다. 기본형은 선언과 동시에 사이즈가 정적으로 정해지지만, 참조형을 사용하면 이처럼 동적으로 크기가 변해서 유연성을 제공할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조형은 더 복잡한 데이터구조를 만들고 관리, 기본형은 더 빠르고 메모리를 효율적으 로 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열 생성 및 초기화&lt;/h3&gt;
&lt;pre id=&quot;code_1713081197514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] students;
students = new int[]{90.80,70,60,50}; // 배열 생성과 초기화 

------
int[] students = new int[]{90,80,70,60,50}; // 배열 변수 선언, 배열 생성과 초기화


----
int[] students = {90,80,70.60,50}; // 자바내부에서 배열 요소의 크기를 보고 new int[5]를 사용해서 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;2차원배열&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차원 배열은 행과 열로 구성된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZvWHf/btsGBdd5BhI/aj7GZJCUyow5LihYNT826k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZvWHf/btsGBdd5BhI/aj7GZJCUyow5LihYNT826k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZvWHf/btsGBdd5BhI/aj7GZJCUyow5LihYNT826k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZvWHf%2FbtsGBdd5BhI%2Faj7GZJCUyow5LihYNT826k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;124&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1713082368085&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[][] arr = new int[2][3];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;향상된 for문&lt;/h3&gt;
&lt;pre id=&quot;code_1713085016155&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for( 변수 : 배열 또는 컬렉션 ) { 
  // 배열 또는 컬렉션의 요소를 순회하면서 수행할 작업
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 일반 for문에서는 int i 와 같은 인덱스를 탐색할 수 있는 변수와 배열의 끝 조건을 지정해주는 i&amp;lt;numbers.length와 마지막으로 배열의 값을 하나 읽을 때 마다 인덱스를 하나씩 증가시켜줘야한다. 개발자 입장에서는 단순히 배열을 순서대로 끝까지 탐색하고 싶은데, 너무 번잡한 일 일수도 있다. 그래서 향상된 for문이 등장했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713085108176&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] numbers = {1,2,3,4,5};

//일반 for문
for(int i = 0; i&amp;lt;numbers.length; i++){
       int number = numbers[i];
       System.out.println(number);
}

//향상된 for문, for-each문
for (int number : numbers) {
       System.out.println(number);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;향상된 for문을 사용하지 못하는 경우가 있다. 향상된 for문에서는 증가하는 인덱스 값이 감추어져 있다. 따라서 int i와 같은 증가하는 인덱스 값을 직접 사용해야 하는 경우에는 향상된 for문을 사용할 수 없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메서드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메서드 호출과 용어정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드를 호출할 때는 다음과 같이 메서드에 넘기는 값과 매개변수(파라미터)의 타입이 맞아야 한다. 물론 넘기는 값과 매개변수(파라미터)의 순서와 갯수도 맞아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 : call(&quot;hello&quot; , 20 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 정의 : int call(String str, int age)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인수(Argument) : &quot;hello&quot;, 20처럼 넘기는 값을 영어로 Argument(아규먼트), 한글로 인수 또는 인자라 한다&lt;/li&gt;
&lt;li&gt;매개변수(Parameter) : 메스드를 정의할때 선언한 변수인 String str, int age를 매개변수,파라미터라 한다. 메서드를 호출할 때 인수를 넘기면, 그 인수가 매개변수에 대입된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어정리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인수(인자) : 메스더 내부로 들어가는 값&lt;/li&gt;
&lt;li&gt;매개변수(파라미터) : 매개 + 변수의 합성어로 '중간에서 전달하는 변수'라는 의미를 가진다. 즉, 메서드 호출부와 메서드 내부 사이에서 값을 전달하는 역할을 하는 변수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708935982711&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;[지금 무료] 김영한의 자바 입문 - 코드로 시작하는 자바 첫걸음 강의 - 인프런&quot; data-og-description=&quot;프로그래밍에 처음 입문하는 분들을 위한 자바 강의입니다. 코드를 따라하면서 손쉽게 자바를 배울 수 있습니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 자바 입문[사진][임베&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AJzhC/hyVqhdIANI/YHkV54naloE55Ws9gee9G1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/B6zqu/hyVqkO4rda/bOKGppP3PaiH2jkb4cck61/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/ZvzaQ/hyVqtrGfhc/qEEATkrMjKPJ9DJWjsf5BK/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%9E%90%EB%B0%94-%EC%9E%85%EB%AC%B8/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AJzhC/hyVqhdIANI/YHkV54naloE55Ws9gee9G1/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/B6zqu/hyVqkO4rda/bOKGppP3PaiH2jkb4cck61/img.png?width=1200&amp;amp;height=781&amp;amp;face=0_0_1200_781,https://scrap.kakaocdn.net/dn/ZvzaQ/hyVqtrGfhc/qEEATkrMjKPJ9DJWjsf5BK/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[지금 무료] 김영한의 자바 입문 - 코드로 시작하는 자바 첫걸음 강의 - 인프런&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프로그래밍에 처음 입문하는 분들을 위한 자바 강의입니다. 코드를 따라하면서 손쉽게 자바를 배울 수 있습니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 자바 입문[사진][임베&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자바/java</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/238</guid>
      <comments>https://myeongsu0257.tistory.com/238#entry238comment</comments>
      <pubDate>Mon, 26 Feb 2024 22:32:14 +0900</pubDate>
    </item>
    <item>
      <title>도서주문관리 프로젝트 - 5. JWT적용, 예외처리(토큰만료)</title>
      <link>https://myeongsu0257.tistory.com/237</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 사용자(로그인이 된 회원)의 권리를 가진 사용자만이 할수있는 좋아요추가/취소, 장바구니담기/조회 등을 Request(요청)할때 단순히 유저id 또는 이메일 등의 방식으로 요청을 했습니다. 이번에는 이러한 방식에서 벗어나 JWT를 이용해 Header에 JWT를 담아서 서버에 보내고 요청을 할 수 있도록 설계해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 한번 jwt가 무엇인지 어떻게 발행하고 어떻게 검증하는지에 대해 다뤄봤습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://myeongsu0257.tistory.com/231&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://myeongsu0257.tistory.com/231&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705045147138&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;JWT&quot; data-og-description=&quot;인증과 인가 인증(Authentication) = 로그인 : 관리자든 고객이든 인증을 통해서 사이트에 가입된 사용자라는 걸 증명하는 것 ex) : ID, PW로 로그인 하는 행위 인가(Authorization) : 인증 이후의 프로세스로&quot; data-og-host=&quot;myeongsu0257.tistory.com&quot; data-og-source-url=&quot;https://myeongsu0257.tistory.com/231&quot; data-og-url=&quot;https://myeongsu0257.tistory.com/231&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jVMCU/hyU2orD8MK/gYno3f9dDKCBoCkikS9Jm1/img.png?width=800&amp;amp;height=383&amp;amp;face=0_0_800_383,https://scrap.kakaocdn.net/dn/FSbs5/hyU2jw8amd/DS0mkMWNK8wVjQ1Jt5RJVk/img.png?width=800&amp;amp;height=383&amp;amp;face=0_0_800_383,https://scrap.kakaocdn.net/dn/eadOXh/hyU5JAVZe7/B47RNFwWKt6sLCTmuBgTF1/img.png?width=944&amp;amp;height=627&amp;amp;face=0_0_944_627&quot;&gt;&lt;a href=&quot;https://myeongsu0257.tistory.com/231&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://myeongsu0257.tistory.com/231&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jVMCU/hyU2orD8MK/gYno3f9dDKCBoCkikS9Jm1/img.png?width=800&amp;amp;height=383&amp;amp;face=0_0_800_383,https://scrap.kakaocdn.net/dn/FSbs5/hyU2jw8amd/DS0mkMWNK8wVjQ1Jt5RJVk/img.png?width=800&amp;amp;height=383&amp;amp;face=0_0_800_383,https://scrap.kakaocdn.net/dn/eadOXh/hyU5JAVZe7/B47RNFwWKt6sLCTmuBgTF1/img.png?width=944&amp;amp;height=627&amp;amp;face=0_0_944_627');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JWT&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인증과 인가 인증(Authentication) = 로그인 : 관리자든 고객이든 인증을 통해서 사이트에 가입된 사용자라는 걸 증명하는 것 ex) : ID, PW로 로그인 하는 행위 인가(Authorization) : 인증 이후의 프로세스로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;myeongsu0257.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 좀 더 자세히 어떻게 cookie에 실어서 클라이언트로 token을 보내는지 알아보겠습니다. 먼저 발행입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;token을 발급하고 cookie에 담아서 보내는데 postman으로 확인해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705045253783&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express');
const app = express();
const dotenv = require('dotenv');
let jwt=require('jsonwebtoken');
dotenv.config();
app.listen(process.env.PORT);

// GET + &quot;/jwt&quot; : 토큰 발행
app.get('/jwt', function (req, res) {
  let token = jwt.sign({foo:'bar'},process.env.PRIVATE_KEY);
  console.log(token);
  res.cookie(&quot;jwt&quot;,token,{
    httpOnly:true
  });
  res.send('토큰 발행');
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 해보니 이상한점을 발견할 수 있습니다. Request Headers에 Cookie값이 있었습니다. 저는 분명 Cookie값을 보낸적이 없는데?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c00SIb/btsDrgb26Wj/ZOuUMKB5IvyQoQkImuMpO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c00SIb/btsDrgb26Wj/ZOuUMKB5IvyQoQkImuMpO0/img.png&quot; data-alt=&quot;이전 Request&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c00SIb/btsDrgb26Wj/ZOuUMKB5IvyQoQkImuMpO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc00SIb%2FbtsDrgb26Wj%2FZOuUMKB5IvyQoQkImuMpO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;804&quot; height=&quot;237&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이전 Request&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;93&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvGpAw/btsDnEldfzG/q1alonhjtEWdvBiWHuJr1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvGpAw/btsDnEldfzG/q1alonhjtEWdvBiWHuJr1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvGpAw/btsDnEldfzG/q1alonhjtEWdvBiWHuJr1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvGpAw%2FbtsDnEldfzG%2Fq1alonhjtEWdvBiWHuJr1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1304&quot; height=&quot;93&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 한번 더 요청해봤습니다. 그랬더니 똑같이 Request에 Cookie가 또 담겨있었습니다. 하지만 자세히보니 Request의 Cookie값이 바로 전 요청에 있는 Reoseponse의 Set-Cookie값과 똑같음을 알게 되었습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7zEfx/btsDqRKjUs8/FKFBd9ukx2gwVhCDKBzLT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7zEfx/btsDqRKjUs8/FKFBd9ukx2gwVhCDKBzLT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7zEfx/btsDqRKjUs8/FKFBd9ukx2gwVhCDKBzLT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7zEfx%2FbtsDqRKjUs8%2FFKFBd9ukx2gwVhCDKBzLT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1256&quot; height=&quot;224&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 현상으로 인해 발생할 수 있는 문제점은 이렇게 쿠키에 계속 다른값을 담아주고 이전의 쿠키값과 혼동이되어 문제가발생할 수 있습니다. 그래서평소에 사이트를 이용하면서 쿠키삭제와같은 것들을 해주면 오류가 해결되는 현상이 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 토큰을 쿠키에 발급할고 다음번에 토큰이 필요한 요청을 할때 header에 Authorization에 토큰의 값을 담아서 보내주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705058802700&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const app = express();
const dotenv = require(&quot;dotenv&quot;);
let jwt = require(&quot;jsonwebtoken&quot;);
dotenv.config();
app.listen(process.env.PORT);

// GET + &quot;/jwt&quot; : 토큰 발행
app.get(&quot;/jwt&quot;, function (req, res) {
	let token = jwt.sign({ foo: &quot;bar&quot; }, process.env.PRIVATE_KEY);
	console.log(token);
	res.cookie(&quot;jwt&quot;, token, {
		httpOnly: true,
	});
	res.send(&quot;토큰 발행&quot;);
});

// GET + &quot;/jwt/decoded&quot; : 토큰을 검증
app.get(&quot;/jwt/decoded&quot;, function (req, res) {
	let receivedJwt = req.headers[&quot;authorization&quot;];
	console.log(receivedJwt);
	let decoded = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
	res.json(decoded);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰을 발행하고 해당 토큰값을 postman의 콘솔의 response의 set-cookie의 jwt= 여기값을 header에 실어서 보내주면됩니다. 참고로 ;(세미콜론)은 빼야 됩니다.( 이걸로 시간을 좀 잡아먹었습니다...)&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doloGs/btsDrl5MMwP/t35bOx3liBJ17ndEtUhle0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doloGs/btsDrl5MMwP/t35bOx3liBJ17ndEtUhle0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doloGs/btsDrl5MMwP/t35bOx3liBJ17ndEtUhle0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoloGs%2FbtsDrl5MMwP%2Ft35bOx3liBJ17ndEtUhle0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1480&quot; height=&quot;615&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 프로젝트에 적용&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좋아요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 좋아요 api에서 Request Body에 token을 보내서 접근이 가능한 사용자인지 아닌지 확인하는 부분을 설계해보겠습니다. 먼저 이전에 설계한 api는 body에 user_id값을 받아서 확인을 했었는데 아래 명세처럼 jwt를 받아서 확인해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;좋아요 추가&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #353638; text-align: left; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 21.9767%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.907%;&quot;&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 21.9767%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.907%;&quot;&gt;/likes/{bookdId}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 21.9767%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.907%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 21.9767%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.907%;&quot;&gt;// 로그인할 때 받은 token &amp;gt; req.header &quot;Authorization&quot;&lt;br /&gt;// payload 값을 읽는다 -&amp;gt; 사용자의 id를 읽어낼 수 있다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 21.9767%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.907%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;좋아요 취소&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #353638; text-align: left; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 22.2093%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.6744%;&quot;&gt;DELETE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 22.2093%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.6744%;&quot;&gt;/likes/{bookdId}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 22.2093%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.6744%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 22.2093%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.6744%;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;// 로그인할 때 받은 token &amp;gt; req.header &quot;Authorization&quot;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;// payload 값을 읽는다 -&amp;gt; 사용자의 id를 읽어낼 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #000000; width: 22.2093%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;color: #000000; width: 77.6744%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 코드입니다.&amp;nbsp; 2개의함수 좋아요추가(addLike), 좋아요삭제(removeLike) 두개 다 모두 body에 user_id를 받아서 sql문을 실행했었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705059308066&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const conn = require(&quot;../mariadb&quot;);
const {StatusCodes}= require('http-status-codes');

const addLike=(req,res)=&amp;gt;{
    const {id}=req.params;
    const {user_id}=req.body;
    const sql = `INSERT INTO likes VALUES(?,?)`;
    let values=[user_id, Number(id)];
    conn.query(sql,values,
        (err,results,fileds)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            return res.status(StatusCodes.CREATED).json(results);
        })
}

const removeLike = (req,res)=&amp;gt;{
    const {id} = req.params;
    const {user_id} = req.body;
    const sql = `DELETE FROM likes WHERE user_id= ? AND liked_book_id=?`;
    let value = [user_id,Number(id)]
    conn.query(sql,value,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            return res.status(StatusCodes.OK).json(results);
        })
}

module.exports={
    addLike,
    removeLike
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jwt를 이용해서 바뀐코드를 보기전에 로그인을 할때 어떤값을 payload에 담아서 보내줄건지 정해야합니다. 저는 아래와 같이 유저의 email과 id값을 담아서 보내줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5wOpl/btsDqlZBMGN/zvEzzN6Wl6N2RICc1zpCK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5wOpl/btsDqlZBMGN/zvEzzN6Wl6N2RICc1zpCK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5wOpl/btsDqlZBMGN/zvEzzN6Wl6N2RICc1zpCK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5wOpl%2FbtsDqlZBMGN%2FzvEzzN6Wl6N2RICc1zpCK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;386&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 코드에서 receivedJwt에 Request의 Header에서 전송한 Authorization의 토큰값을 저장하고 해당 값을 이용해 복호화를 한후 decodedJwt에 담아주면 해당변수에는 Payload의 값이 담깁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705060267683&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;....
const jwt = require(&quot;jsonwebtoken&quot;);

const addLike = (req, res) =&amp;gt; {
	const { id } = req.params;

	const receivedJwt = req.headers[&quot;authorization&quot;];
	const decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);

	const sql = `INSERT INTO likes VALUES(?,?)`;
	let values = [decodedJwt.id, Number(id)];
	conn.query(sql, values, (err, results, fileds) =&amp;gt; {
		if (err) {
			console.log(err);
			return res.status(StatusCodes.BAD_REQUEST).end();
		}
		return res.status(StatusCodes.CREATED).json(results);
	});
};

const removeLike = (req, res) =&amp;gt; {
	const { id } = req.params;

	const receivedJwt = req.headers[&quot;authorization&quot;];
	const decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);

	const sql = `DELETE FROM likes WHERE user_id= ? AND liked_book_id=?`;
	let values = [decodedJwt.id, Number(id)];
	conn.query(sql, values, (err, results, fields) =&amp;gt; {
		if (err) {
			console.log(err);
			return res.status(StatusCodes.BAD_REQUEST).end();
		}
		return res.status(StatusCodes.OK).json(results);
	});
};

....&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 console에 decodedJwt를 찍어보았을때 email과 id가 잘 담겨져 있는 것을 확인할 수 있습니다. 그래서 해당값에서 id를 value에 담아서 사용해주면됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjAf7s/btsDqTBI5MW/otYBcs2r2uw0UBAPEIpO41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjAf7s/btsDqTBI5MW/otYBcs2r2uw0UBAPEIpO41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjAf7s/btsDqTBI5MW/otYBcs2r2uw0UBAPEIpO41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjAf7s%2FbtsDqTBI5MW%2FotYBcs2r2uw0UBAPEIpO41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;311&quot; height=&quot;174&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 중복되는 부분을 함수로 만들어주고 나머지들도 정리한 최종본코드입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705062211206&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const conn = require(&quot;../mariadb&quot;);
const { StatusCodes } = require(&quot;http-status-codes&quot;);
const jwt = require(&quot;jsonwebtoken&quot;);


const addLike = (req, res) =&amp;gt; {
	const bookId = req.params.id;

	const decodedJwt = decodeJwt(req);

	const sql = `INSERT INTO likes VALUES(?,?)`;
	let values = [decodedJwt.id, Number(bookId)];
	conn.query(sql, values, (err, results, fileds) =&amp;gt; {
		if (err) {
			console.log(err);
			return res.status(StatusCodes.BAD_REQUEST).end();
		}
		return res.status(StatusCodes.CREATED).json(results);
	});
};

const removeLike = (req, res) =&amp;gt; {
	const bookId = req.params.id;

	const decodedJwt = decodeJwt(req);

	const sql = `DELETE FROM likes WHERE user_id= ? AND liked_book_id=?`;
	let values = [decodedJwt.id, Number(bookId)];
	conn.query(sql, values, (err, results, fields) =&amp;gt; {
		if (err) {
			console.log(err);
			return res.status(StatusCodes.BAD_REQUEST).end();
		}
		return res.status(StatusCodes.OK).json(results);
	});
};


function decodeJwt(req) {
	const receivedJwt = req.headers[&quot;authorization&quot;];
	return jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
}

module.exports = {
	addLike,
	removeLike,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 토큰만료 예외처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰을 Header에 넣어서 요청을 해주다가 아래처럼 JWT가 만료되었다고 나옵니다.(만료시간이 지났기때문) 이럴때는 예외처리를 해줘야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;31&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3AgcJ/btsDsXRq7sF/0vG1l5SSXKOct7IPEj3pmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3AgcJ/btsDsXRq7sF/0vG1l5SSXKOct7IPEj3pmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3AgcJ/btsDsXRq7sF/0vG1l5SSXKOct7IPEj3pmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3AgcJ%2FbtsDsXRq7sF%2F0vG1l5SSXKOct7IPEj3pmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;318&quot; height=&quot;31&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;31&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 JWT가 발생할 수 있는 대표적인 에러 2가지입니다. 이 에러에 대해 try-catch문을 이용해 예외처리를 해주겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TokenExpiredError : 유효기간이 지난 토큰 = 만료된 토큰&lt;/li&gt;
&lt;li&gt;JsonWebToken : 문제 있는 토큰&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 TokenExpiredError입니다. 복호화하는 함수에서 try-catch문을 이용해 정상적이면 복호화된 jwt값을 에러가 발생하면 err를 리턴해주었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705236679964&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function decodeJwt(req, res) {
	try {
		const receivedJwt = req.headers[&quot;authorization&quot;];
		return jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
	} catch (err) {
		console.log(err);
		return err;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 함수를 호출하는 api에서는 if-else문과 instanceof를 이용해 토큰이 만료되었으면 message를 아니면 기존에 sql문을 실행하도록 해주었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705236722133&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const addToCart = (req, res) =&amp;gt; {
	const { book_id, quantity } = req.body;

	const decodedJwt = decodeJwt(req, res);
	if (decodedJwt instanceof jwt.TokenExpiredError) {
		return res.status(StatusCodes.UNAUTHORIZED).json({
			message: &quot;로그인 세션이 만료되었습니다. 다시 로그인 해주세요&quot;,
		});
	} else {
		const sql = `INSERT INTO cartItems(book_id,quantity,user_id) VALUE(?,?,?)`;
		let values = [book_id, quantity, decodedJwt.id];
		conn.query(sql, values, (err, results, fields) =&amp;gt; {
			if (err) {
				console.log(err);
				return res.status(StatusCodes.BAD_REQUEST).end();
			}
			return res.status(StatusCodes.CREATED).json(results);
		});
	}
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 JsonWebTokenError입니다. 이 에러는 토큰이 이상한값일때 에러를 출력합니다. 실제로 토큰에서 이상한값을 넣었더니 아래의 에러가 발생하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFjuvP/btsDnxmPp0w/uqEKRYMAG9cOaMm96jzsKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFjuvP/btsDnxmPp0w/uqEKRYMAG9cOaMm96jzsKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFjuvP/btsDnxmPp0w/uqEKRYMAG9cOaMm96jzsKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFjuvP%2FbtsDnxmPp0w%2FuqEKRYMAG9cOaMm96jzsKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;33&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;else-if문으로 아래처럼 해당 오류가 발생했을때 에러만 처리해주면됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705237300802&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (decodedJwt instanceof jwt.TokenExpiredError) {
		return res.status(StatusCodes.UNAUTHORIZED).json({
			message: &quot;로그인 세션이 만료되었습니다. 다시 로그인 해주세요&quot;,
		});
	} else if (decodedJwt instanceof jwt.JsonWebTokenError) {
		return res.status(StatusCodes.UNAUTHORIZED).json({
			message: &quot;토큰 값을 확인해주세요&quot;,
		});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 문제가 발생합니다. 아래의 처리를 모든 api함수에서 다해주면 코드가 중복되는 부분이 많아집니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>백엔드/node.js(express)</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/237</guid>
      <comments>https://myeongsu0257.tistory.com/237#entry237comment</comments>
      <pubDate>Fri, 12 Jan 2024 21:24:37 +0900</pubDate>
    </item>
    <item>
      <title>도서주문관리 프로젝트 - 4.비동기(async-awiat)이용한 쿼리실행(주문)</title>
      <link>https://myeongsu0257.tistory.com/236</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;주문하기컨트롤러를 설계하면서 3개의sql문을 한번에 실행해야하는 코드가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704857707044&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const order = (req,res)=&amp;gt;{
    const {items,delivery,totalQuantity,totalPrice,userId,firstBookTitle}=req.body;
    let delivery_id;
    let order_id;
    let sql =&quot;INSERT INTO delivery(address,receiver,contact) VALUES(?,?,?)&quot;

    let values = [delivery.address, delivery.receiver, delivery.contact];
    conn.query(sql,values,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            console.log(results);
            delivery_id=results.insertId; //여기

            
        })
    sql = `INSERT INTO orders(book_title, total_quantity, total_price, user_id, delivery_id)
     VALUE(?,?,?,?,?)`;
    values =[firstBookTitle,totalQuantity,totalPrice,userId,delivery_id];
     conn.query(sql,values,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            console.log(results);
            order_id=results.insertId;//여기
            
        })
    sql =`INSERT INTO orderedBook(order_id,book_id,quantity) VALUE(?)`;
    values=[];
    items.forEach((item)=&amp;gt;{
        values.push([order_id,item.book_id,item.quantity]);
    })

    conn.query(sql,values,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
           
            return res.status(StatusCodes.OK).json(results);
        })


};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 위 코드를 실행시키면 delivery_id와 order_id를 할당하기 전에 아래 sql문이 실행되어 값이 할당되지 않아 null이라는 값이 들어가 있다고 나옵니다. 이러한이유는 node의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;논블로킹&lt;/b&gt;&lt;/span&gt;이라는 특징때문에 그렇습니다. 간단하게 말해 논블로킹이란 어떤 함수가 실행되는 중에도 다른 작업을 동시에 진행할 수 있다는 것입니다. 따라서 위의코드에서도 값을 할당하는것을 기다리지 않고 바로 아래의 sql문으로 진행되었던 것 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oVuYr/btsDfUVAuOL/KM0RFTkgrK32k23bBuo5jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oVuYr/btsDfUVAuOL/KM0RFTkgrK32k23bBuo5jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oVuYr/btsDfUVAuOL/KM0RFTkgrK32k23bBuo5jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoVuYr%2FbtsDfUVAuOL%2FKM0RFTkgrK32k23bBuo5jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;146&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;39&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dG0kPb/btsDj17cdnD/8JXf6b8DB99PvaKGuVXcF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dG0kPb/btsDj17cdnD/8JXf6b8DB99PvaKGuVXcF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dG0kPb/btsDj17cdnD/8JXf6b8DB99PvaKGuVXcF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdG0kPb%2FbtsDj17cdnD%2F8JXf6b8DB99PvaKGuVXcF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;416&quot; height=&quot;39&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;39&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 하나의 작업(sql문)이 끝날때 다음 작업(sql문)을 실행시키는 즉, 순서를 맞춰서 코드를 실행시켜주는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;비동기 처리&lt;/b&gt;&lt;/span&gt;를 해야 합니다. 비동기처리의 방식은 대표적으로 4가지가 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;콜백 함수 : 할 일 다하고, 이거 실행해줘(순서 맞춰서 실행)&lt;/li&gt;
&lt;li&gt;promise(resolve, reject)&lt;/li&gt;
&lt;li&gt;then &amp;amp; catch&lt;/li&gt;
&lt;li&gt;EX2017 promise =&amp;gt; async &amp;amp; await&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번의 콜백함수는 지금까지 사용했던 방식이였고 4번 async &amp;amp; await방식을 사용해보겠습니다. async-await는 비동기로 실행되는 것들을 끝날 때 까지 기다린다는 의미로 callback처럼 동기방식으로 처리가 가능하게 해주는 문법입니다. 즉, Promise객체를 좀 더 쉽게 편하게 사용하는 문법입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704868732594&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let promise = new Promise(function(resolve,reject){
    setTimeout(() =&amp;gt; resolve('완료!'),3000);
});

promise.then(
    function(result){
        console.log(result);
    },
    function(error){}
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 실행하면 3초뒤에 완료!가 출력됩니다.위의 코드에서는 promise.then을 코드 밖에 선언을해서 사용해줬지만 아래의 코드에서는 await를 사용해 똑같이 Promise객체 일이 끝날 때까지 기다릴 수 있게 해줍니다. 아래의코드를 실행해도 3초뒤에 완료!가 출력됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704868826301&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function f(){
    let promise=new Promise(function(resolve,reject){
        setTimeout(()=&amp;gt;resolve(&quot;완료!&quot;),3000);
    });

    let result = await promise;
    console.log(result);
};

f();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;async-await 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async-await를 적용한 주문하기 api입니다. 기존에 작성하던 api설계에서 다른점은 크게 3가지 입니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mysql2/promise : 기존에는 mysql2만 사용&lt;/li&gt;
&lt;li&gt;conn : 상단부에서 require해서 사용해오던 방식을 api함수안에서 선언하여 사용&lt;/li&gt;
&lt;li&gt;conn.query : conn.execute로 변경하여 사용&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 asnyc-await를 사용하기 위해서는 mysql2/promise를 사용해야합니다. 그리고 conn을 require해서 사용했었는데 비동기처리를 해주기 위해 api함수안에서 정의하여 사용했습니다. 마지막으로&amp;nbsp; conn.query()대신 conn.execute()를 사용해줍니다. 코드를 보면 마지막부분쯤에 conn.query()를 사용한 부분이 있는데 이유는 SQL의 insert문에서 여러개의 행들(데이터)를 넣어줘야 하는데 conn.execute()는 insert가 되지 않아서 query를 사용했습니다. (WHERE의 IN과 같은 곳에서도 안됨, 즉 여러개의 값들을 이용할때 안되는것같음)&lt;/p&gt;
&lt;pre id=&quot;code_1704966199745&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const mysql = require('mysql2/promise');
//const conn = require('../mariadb');
const {StatusCodes} = require('http-status-codes');

const order = async (req,res)=&amp;gt;{
    const conn = await mysql.createConnection({
        host:&quot;localhost&quot;,
        user:'root',
        password:'root',
        database:'Bookshop',
        dateStrings:true
    });

    //배송지
		const { items, delivery, totalQuantity, totalPrice, firstBookTitle } = req.body;
		let sql = &quot;INSERT INTO delivery(address,receiver,contact) VALUE(?,?,?)&quot;;
		let values = [delivery.address, delivery.receiver, delivery.contact];
		let [results] = await conn.execute(sql, values);
		let delivery_id = results.insertId;

		//주문
		sql = `INSERT INTO orders(book_title, total_quantity, total_price, user_id, delivery_id)
         VALUE(?,?,?,?,?)`;
		values = [firstBookTitle, totalQuantity, totalPrice, decodedJwt.id, delivery_id];
		[results] = await conn.execute(sql, values);
		let order_id = results.insertId;
		console.log(&quot;주문번호&quot;);
		console.log(order_id);

		//장바구니에서 선택한 상품 목록 조회
		sql = `SELECT book_id,quantity FROM cartItems WHERE id IN(?)`;
		let [orderItems, fields] = await conn.query(sql, [items]);
		console.log(orderItems);

		//주문된 책 목록 조회
		sql = `INSERT INTO orderedBook(order_id,book_id,quantity) VALUES ?`;
		values = [];
		orderItems.forEach((item) =&amp;gt; {
			values.push([order_id, item.book_id, item.quantity]);
		});
		console.log(values);
		[results] = await conn.query(sql, [values]);
		console.log(results);

		//장바구니에서 선택한 상품 삭제
		let result = await deleteCartItems(conn, items);
		console.log(result);

		return res.status(StatusCodes.OK).json(results[0]);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래코드에서도 conn.execute()대신 conn.query()를 사용해주었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704967620950&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const deleteCartItems = async(conn,items)=&amp;gt;{
    let sql =`DELETE FROM cartItems WHERE id IN(?)`;
    let values=items;
    let result = await conn.query(sql,values);
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 이전에는 conn.query()를 한후 결과값을 단순히 results라는 변수에 받아서 사용했는데 conn.execute()처리를 하고나서 결과값들은 [rows,fields]에 담아줘야 활용할 수 있습니다.(rows, fields 이름은 바꿔도 상관없습니다). 그거에 대한 예시로 주문목록조회, 주문상세조회코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704967773748&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const getOrders = async (req,res)=&amp;gt;{
    const conn = await mysql.createConnection({
        host:&quot;localhost&quot;,
        user:'root',
        password:'root',
        database:'Bookshop',
        dateStrings:true
    });

    // let {id}=req.body;
    let sql = `SELECT orders.id,book_title,total_quantity,total_price,created_at,address,receiver,contact
     FROM orders LEFT OUTER JOIN delivery ON orders.delivery_id=delivery.id`;
    let [rows,fields] = await conn.execute(sql,[]);
    console.log(rows);
    return res.status(StatusCodes.OK).json(rows);
};

const getOrderDetail = async (req,res)=&amp;gt;{
    const conn = await mysql.createConnection({
        host:&quot;localhost&quot;,
        user:'root',
        password:'root',
        database:'Bookshop',
        dateStrings:true
    });
    let {id}=req.params;
    let sql = `SELECT books.id,books.title,books.author,books.price,orderedBook.quantity
     FROM orderedBook LEFT OUTER JOIN books
     ON orderedBook.book_id=books.id
      WHERE order_id=?`;
    let values = [id];
    let [rows,fields] = await conn.execute(sql,values);
    console.log(rows);
    return res.status(StatusCodes.OK).json(rows);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;여러개의 VALUES, WHERE IN&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;insert into에서 id값을 제외한 모든값들을 삽입해야하는 경우가 있습니다. 그럴때는 아래처럼 사용합니다. SQL문을 보면 VALUES에 ?를 적어주고 values라는 배열에 값들을 넣은뒤 conn.query문에서 처리해주면됩니다. (어떻게 보면 Values가 2차배열이 된다고 볼 수 있겠네요)&lt;/p&gt;
&lt;pre id=&quot;code_1705313667003&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//주문된 책 목록 조회
		sql = `INSERT INTO orderedBook(order_id,book_id,quantity) VALUES ?`;
		values = [];
		orderItems.forEach((item) =&amp;gt; {
			values.push([order_id, item.book_id, item.quantity]);
		});
		console.log(values);
		[results] = await conn.query(sql, [values]);
		console.log(results);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래고 WHERE IN에서도 (?)를 적어준뒤 아래에서 items라는 배열을 한번더 배열을 씌워서 처리해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705313751912&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//장바구니에서 선택한 상품 목록 조회
		sql = `SELECT book_id,quantity FROM cartItems WHERE id IN(?)`;
		let [orderItems, fields] = await conn.query(sql, [items]);
		console.log(orderItems);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>백엔드/node.js(express)</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/236</guid>
      <comments>https://myeongsu0257.tistory.com/236#entry236comment</comments>
      <pubDate>Thu, 11 Jan 2024 19:10:14 +0900</pubDate>
    </item>
    <item>
      <title>도서주문관리 프로젝트 - 3. 도서,좋아요 API(SQL시간범위, 페이지네이션,서브쿼리)</title>
      <link>https://myeongsu0257.tistory.com/235</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;도서&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체 도서 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체도서조회입니다. query로 4개의 값을 받는데 이 중 필수적으로 받아야 하는 값들은 limit와 currentPage입니다. category_id는 카테고리별로 도서의 개수를 알고 싶을때, news는 현재 날짜로부터 1달이내에 출간된 도서를 조회하고 싶을때 받아서 사용합니다. 따라서 category_id와 news의 값이 있는 경우들을 if문으로 작성하여 기본적인 sql문( 모든책조회) 에 WHERE로 조건을 걸어주었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704460804708&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const allBooks = (req,res)=&amp;gt;{
    const {category_id,news,limit,currentPage} = req.query;
    // limit : page 당 도서 수 ex. 3
    // currentPage : 현재 몇 페이지 ex. 1, 2, 3 ...
    // offset : 0, 3, 6, 9, 12... ( limit * (currentPage-1) )
    let offset = limit * (currentPage-1);
    const values = [];
    let sql = &quot;SELECT *, (SELECT count(*) FROM likes WHERE books.id=liked_book_id) AS likes FROM books&quot;
    if(category_id&amp;amp;&amp;amp;news){
        sql = sql + &quot; WHERE category_id=? AND pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()&quot; 
        values.push(category_id);
    }
    else if(category_id){  
        sql = sql + ` WHERE category_id=?`;
        values.push(category_id);
    }
    else if(news){
        sql = sql + &quot; WHERE pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()&quot;
    }
    values.push(Number(limit));
    values.push(offset);
    sql = sql + &quot; LIMIT ? OFFSET ?&quot;;

    conn.query(sql,values,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            if(results)
                return res.status(StatusCodes.OK).json(results);
            else
                return res.status(StatusCodes.NOT_FOUND).end();
        })
   
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;페이지네이션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드 중 페이지네이션을 조금더 설명해보자면 limit는 페이지 당 도서의 개수이고 currentPage는 현재 몇 페이지인지를 알려주는 수 입니다. 즉,&amp;nbsp; 페이지네이션을 구현하기 위해 limit와 currentPage를 받아주었고 그 값을 이용해 offset을 설정해주었습니다. offset은 어떤 데이터부터 시작할지 정해주는 기준점과 같습니다. 예를 들면 1~100까지 데이터가 있고 offset이 10(데이터베이스 상 데이터 순서때문에 10-1=9) 이면 10번째 행부터 데이터를 가져올 수 있습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;limit : 페이지 당 도서 개수 ex) 4, 8, 12 등과 같은다양한 배수의 값들로 이루어짐&lt;/li&gt;
&lt;li&gt;currentPage : 현재 페이지 ex) 1,2,3 페이지 .. 등&amp;nbsp;&lt;/li&gt;
&lt;li&gt;orrset : 조회를 시작할 기준점(가져올 데이터의 초기 위치값)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개별 도서 조회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개별 도서 조회입니다. like테이블을 이용해 개별 도서의 좋아요수, 좋아요여부를 알 기 위해 서브쿼리를 사용했습니다. COUNT함수를 사용해 좋아요갯수를 가져왔고 EXISTS함수를 이용해 좋아요여부를 알 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704461425266&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const bookDetail = (req,res)=&amp;gt;{
    let {user_id} = req.body;
    let book_id = req.params.id;
    const sql = `SELECT *,
        (SELECT count(*) FROM likes WHERE liked_book_id=books.id) AS likes, 
        (SELECT EXISTS (SELECT * FROM likes WHERE user_id=? AND liked_book_id=?)) AS liked
         FROM books LEFT OUTER JOIN category ON books.category_id=category.category_id WHERE books.id=?`;
    const values = [user_id,book_id,book_id];
    conn.query(sql,values,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            if(results[0])
                return res.status(StatusCodes.OK).json(results[0]);
            else
                return res.status(StatusCodes.NOT_FOUND).end();
        })
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋아요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 추가입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704461544616&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const addLike=(req,res)=&amp;gt;{
    const {id}=req.params;
    const {user_id}=req.body;
    const sql = `INSERT INTO likes VALUES(?,?)`;
    let values=[user_id, Number(id)];
    conn.query(sql,values,
        (err,results,fileds)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            return res.status(StatusCodes.CREATED).json(results);
        })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 삭제입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704461554051&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const removeLike = (req,res)=&amp;gt;{
    const {id} = req.params;
    const {user_id} = req.body;
    const sql = `DELETE FROM likes WHERE user_id= ? AND liked_book_id=?`;
    let value = [user_id,Number(id)]
    conn.query(sql,value,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            return res.status(StatusCodes.OK).json(results);
        })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>백엔드/node.js(express)</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/235</guid>
      <comments>https://myeongsu0257.tistory.com/235#entry235comment</comments>
      <pubDate>Thu, 11 Jan 2024 16:12:48 +0900</pubDate>
    </item>
    <item>
      <title>도서주문관리 프로젝트 - 2. 유저API(컨트롤러, 단뱡향암호화(crypto),jwt token)</title>
      <link>https://myeongsu0257.tistory.com/234</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Express-generator&amp;nbsp;프로젝트&amp;nbsp;구조&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O8oFm/btsCQrsgxDi/dpu9B3QlMDSLFioknKbF9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O8oFm/btsCQrsgxDi/dpu9B3QlMDSLFioknKbF9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O8oFm/btsCQrsgxDi/dpu9B3QlMDSLFioknKbF9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO8oFm%2FbtsCQrsgxDi%2Fdpu9B3QlMDSLFioknKbF9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;302&quot; height=&quot;582&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bin/www : 포트번호 등과 같은 웹 서버를 구축하는데에 필요한 설정 데이터가 정의되어 있는 파일
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.env파일과 같이 설정값을 가지고 에러처리, 기타추가 설정을 해주는 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;node_modules : Node.js, Express에 필요한 모듈들이 설치되는폴더&lt;/li&gt;
&lt;li&gt;public : images, javascripts, stylesheets -&amp;gt; 정적(ex. 로고, 회사 소개 페이지) 파일&lt;/li&gt;
&lt;li&gt;routes : 각 경로를 담당하는 모듈들이 들어있는 폴더 = 라우팅 로직을 구현하는 모듈들 : 클라이언트에서 어떤 요청을 주냐에 따라서 어떤 로직을 수행할 지 파일별로 분할 해서 관리하는 정도&amp;nbsp;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 controller 역할&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;views : 클라이언트에게 html코드로 &quot;화면을 보내는파일&quot;&lt;/li&gt;
&lt;li&gt;app.js(서버의 시작점) -&amp;gt; URL에 따라서 라우팅&lt;/li&gt;
&lt;li&gt;package.json : 이 프로젝트에 설치된 모듈 이름, 버전 등등 정보들이 작성되어 있는 파일&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컨트롤러&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;계층분리의필요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 Express를 학습하면서 계층구조는 단순히 app.js에서 routes를 설정해주고 url에 따른 사용자의 요청이 오면 해당 url의 routes로가서 로직을 실행시켜주는 구조였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;routes에서 로직을 정의하고 실행시키는 것의 장점은 어떤 엔드포인트로 접근햇을 때 어떤 동작을 하게 되는지 한 눈에 볼 수 있어 유용했다. 하지만 단순한프로젝트가 아닌 규모가 커지게 된다면 하나의 routes안에서 코드가 많아져 관리하는 것이 힘들어 질 수 있다. 즉 유지보수가능한코드가 되기 어렵다. 따라서 장기적인 측면을 생각했을때 정확한 계층 분리는 꼭 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Router : 엔드포인트와 해당 엔드포인트에서 실행되어야 할 로직을 연결해주는 역할&lt;/li&gt;
&lt;li&gt;Controller : Middlewar의 일종이지만 메인 로직을 담당하므로 분리해서 관리&lt;/li&gt;
&lt;li&gt;Middleware : 메인 로직의 Controller 앞뒤로 추가적인 일을 담당&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Router는 정말 코드의 전체적인 흐름, 경로만 나타내주고 Controller가 메인 로직을 담당(Service 계층으로연결, Service는 DB계층으로 연결), Middleware는 그 외의 특별한 일을 함수로 따로 빼서 거치도록 해주는 느낌이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 회원가입을 컨트롤러를 통해 분리하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;users.js&lt;/p&gt;
&lt;pre id=&quot;code_1704203595378&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
const join = require('../controller/UserController');
...
//회원가입
router.post('/join',
    [
        body('email').notEmpty().isEmail().withMessage('이메일확인필요'),
        body('password').notEmpty().isString().withMessage('비밀번호 확인필요'),
        validate
    ]
    ,join);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserController.js&lt;/p&gt;
&lt;pre id=&quot;code_1704203619151&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const conn = require('../mariadb'); //db모듈
const {StatusCodes} = require('http-status-codes');//status code 모듈

const join = (req,res)=&amp;gt;{
    let {email,password}=req.body;
    const sql = `INSERT INTO users (email,password) 
    VALUES(?,?)`
    let values =[email,password];
    conn.query(sql,values,
        function(err,results,fields){
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            return res.status(StatusCodes.CREATED).json(results);
        })
}

module.exports=join;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 로그인, 비밀번호초기화요청, 비밀번호초기화까지 모두 컨트롤러에 작성해보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 유저라우터인 users.js이다&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704208078690&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express'); //express 모듈
const router = express.Router();
const conn = require('../mariadb'); //db모듈
const {body,validationResult}= require('express-validator');

const {
    join,
    login,
    passwordResetRequest,
    passwordReset
    } = require('../controller/UserController');
router.use(express.json());

const validate = (req,res,next)=&amp;gt;{
    const err = validationResult(req);
    if(err.isEmpty()){
        return next();
    }
    else{
        return res.status(400).json(err.array());
    }
}

//회원가입
router.post('/join',
    [
        body('email').notEmpty().isEmail().withMessage('이메일확인필요'),
        body('password').notEmpty().isString().withMessage('비밀번호 확인필요'),
        validate
    ]
    ,join);

router.post('/login',login);  //로그인
router.post('/reset',passwordResetRequest); //비밀번호 초기화 요청
router.put('/reset',passwordReset); //비밀번호 초기화 

module.exports=router&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음을 유저 컨트롤러인 UserController.js이다.&lt;/p&gt;
&lt;pre id=&quot;code_1704208116305&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const conn = require('../mariadb'); //db모듈
const {StatusCodes} = require('http-status-codes');//status code 모듈
const jwt = require('jsonwebtoken');
const dotenv = require('dotenv');
dotenv.config();

const join = (req,res)=&amp;gt;{
    let {email,password}=req.body;
    const sql = `INSERT INTO users (email,password) 
    VALUES(?,?)`;
    const values =[email,password];
    conn.query(sql,values,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            return res.status(StatusCodes.CREATED).json(results);
        })
};

const login = (req,res)=&amp;gt;{
    let {email,password}=req.body;
    const sql = `SELECT * FROM users WHERE email=?`;
    const values = [email,password];
    conn.query(sql,values,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            const loginUser=results[0];
            if(loginUser&amp;amp;&amp;amp;loginUser.password==password){
                const token = jwt.sign({
                    email:loginUser.email,
                    name:loginUser.name
                },process.env.PRIVATE_KEY,{
                    expiresIn:'5m',
                    issuer:&quot;choims&quot;
                });      
                res.cookie(&quot;token&quot;,token,{
                    httpOnly:true
                });
                console.log(token);
                return res.status(StatusCodes.CREATED).json({
                    message:`${loginUser.email}님 로그인 되었습니다.`
                });
            }else{
                return res.status(StatusCodes.UNAUTHORIZED).end();
            }
        })
};
const passwordResetRequest = (req,res)=&amp;gt;{
    const {email} = req.body;
    let sql ='SELECT * FROM users WHERE email=?';
    conn.query(sql,email,
        (err,results)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }

            const user = results[0];
            if(user){
                return res.status(StatusCodes.OK).json({
                    email:email
                })
            }else{
                return res.status(StatusCodes.UNAUTHORIZED).end();
            }
            
        })
};
const passwordReset = (req,res)=&amp;gt;{
    const {email,password} = req.body;
    const sql = &quot;UPDATE users SET password=? WHERE email=?&quot;;
    let values =[password,email];
    conn.query(sql,values,
        (err,results)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            if(results.affectedRows==0)
                return res.status(StatusCodes.BAD_REQUEST).end();
            else   
                return res.status(StatusCodes.OK).json(results);
        })
};

module.exports={
    join,
    login,
    passwordResetRequest,
    passwordReset
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비밀번호 암호화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 user의 db를 보면 password가 암호화가 되어있지 않고 그대로 있는 것을 볼 수 있는데 만약 db가 해킹당하게 되면 회원의 password가 그대로 노출이 될 수 있다. 따라서 반드시 암호화를 해야합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TQ7MV/btsCReVkBi8/29yJuEGj9SXaVRpu0CcfVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TQ7MV/btsCReVkBi8/29yJuEGj9SXaVRpu0CcfVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TQ7MV/btsCReVkBi8/29yJuEGj9SXaVRpu0CcfVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTQ7MV%2FbtsCReVkBi8%2F29yJuEGj9SXaVRpu0CcfVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;517&quot; height=&quot;259&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;259&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;암호화 방법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단방향 암호화 : 복호화(암호화된 문자열을 다시 원래 문자열로 돌려놓는 것) 할 수 없는 암호화 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복호화 할 수 없으면 왜필요? -&amp;gt; 잘생각해보면 홈페이지 비밀번호 같은 경우는 복호화 할 필요가 없다. 비밀번호를 암호화해서 db에 저장해둔 후, 나중에 로그인할 때, 다시 입력받은 비밀번호를 같은 알고리즘으로 암호화해서 DB에 저장된 문자열과 비교하면 된다. 즉 원래 비밀번호는 어디에도 저장되지 않고, 암호화된 문자열로만 비교&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;양방향 암호화 : 비대칭형 암호화, 대칭형 암호화&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단방향 암호화를 해보겠습니다. 기본 내장 모듈인 크립토모듈(crypto)를 불러와서 암호화를 해주겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704209588118&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
const crypto = require('crypto'); // crypot 모듈 : 암호화
...
//비밀번호 암호화
     const salt = crypto.randomBytes(64).toString('base64');
     const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 64,'sha512').toString('base64');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 salt(소금)라는 특정값을 통해 레인보우테이블을 방지할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레인보우 테이블 : 서로 다른 유저가 '1234', '1234' 비밀번호로 회원가입을 했다고 가정하면, 둘의 암호화된 비밀번호가 같아진다. 해커는 이를 통해 비밀번호를 유추할 수 있다.(이를 찾는 문자열의 목록을 레인보우 테이블)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 slat는 랜덤 문자열을 생성해서 비밀번호와 같이 DB에 저장하면 된다. 위의 코드를 자세히 살펴보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;randomBytes : 메서드르 64바이트 길이의 salt를 생성, base64문자열 salt로 변경&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;pbkdf2Sync : 인자는 차례대로 비밀번호, salt, 반복 횟수, 비밀번호 길이, 해시 알고리즘 순&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반복 횟수는 해시함수를 몇번 반복하느냐를 나타낸다. 이 숫자가 높을수록 슈퍼컴퓨터를 써도 레인보우 테이블을 만들기 힘들어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이제 위의 코드를 가지고 어떻게 활용하나면&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;회원가입 시, 비밀번호를 암호화해서 암호화된 비밀번호와, salt 값을 같이 저장&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;로그인 시, 이메일&amp;amp;비밀번호(날 것) -&amp;gt; salt 값 꺼내서 비밀번호 암호화 해보고 -&amp;gt; 디비 비밀번호랑 비교&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서는 db를 수정해줘야 한다. users테이블에 salt라는 열을 하나 추가해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7uUMi/btsCX5JCBRw/joHyTHChYcqFTDw7VXYdLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7uUMi/btsCX5JCBRw/joHyTHChYcqFTDw7VXYdLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7uUMi/btsCX5JCBRw/joHyTHChYcqFTDw7VXYdLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7uUMi%2FbtsCX5JCBRw%2FjoHyTHChYcqFTDw7VXYdLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;148&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 추가한 회원가입 api코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1704212382200&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const join = (req,res)=&amp;gt;{
    let {email,password}=req.body;
    const sql = `INSERT INTO users (email,password,salt) VALUES(?,?,?)`;

    //암호화된 비밀번호와 salt값을 같이 DB에 저장 
    const salt = crypto.randomBytes(10).toString('base64');
    const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 10,'sha512').toString('base64');

    const values =[email,hashPassword,salt];
    conn.query(sql,values,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            return res.status(StatusCodes.CREATED).json(results);
        })
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정한 후 post를 해본뒤 db를 확인해보면&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n0akc/btsCR9TZ5Vy/iNhR8BSRMNW4qIRpGQkWUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n0akc/btsCR9TZ5Vy/iNhR8BSRMNW4qIRpGQkWUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n0akc/btsCR9TZ5Vy/iNhR8BSRMNW4qIRpGQkWUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn0akc%2FbtsCR9TZ5Vy%2FiNhR8BSRMNW4qIRpGQkWUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;607&quot; height=&quot;203&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 뒤 로그인을 할때는 어떻게 할까요? db에서 salt를 꺼내서 비밀번호를 body에 받고 다시 암호화를 해서 비교해주면됩니다. 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704213738343&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const login = (req,res)=&amp;gt;{
    let {email,password}=req.body;
    const sql = `SELECT * FROM users WHERE email=?`;
    const values = [email,password];
    conn.query(sql,values,
        (err,results,fields)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            const loginUser=results[0];
            //salt값 꺼내서 비밀번호를 암호화하고 디비 비밀번호랑비교
            const hashPassword = crypto.pbkdf2Sync(password, loginUser.salt, 10000, 10,'sha512').toString('base64');
           
            if(loginUser&amp;amp;&amp;amp;loginUser.password==hashPassword){
                const token = jwt.sign({
                    email:loginUser.email,
                    name:loginUser.name
                },process.env.PRIVATE_KEY,{
                    expiresIn:'5m',
                    issuer:&quot;choims&quot;
                });      
                res.cookie(&quot;token&quot;,token,{
                    httpOnly:true
                });
                console.log(token);
                return res.status(StatusCodes.CREATED).json({
                    message:`${loginUser.email}님 로그인 되었습니다.`
                });
            }else{
                return res.status(StatusCodes.UNAUTHORIZED).end();
            }
        })
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비밀번호 초기화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호초기화입니다. UPDATE SET WHERE을 사용하는데 새로운 비밀번호로 업데이트할때 새로운 salt를 생성해서 회원가입처럼 똑같이 하면됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704214434685&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const passwordReset = (req,res)=&amp;gt;{
    const {email,password} = req.body;
     
    const sql = &quot;UPDATE users SET password=? , salt=? WHERE email=?&quot;;
    const salt = crypto.randomBytes(10).toString('base64');
    const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 10,'sha512').toString('base64');
    let values =[hashPassword,salt,email];
    conn.query(sql,values,
        (err,results)=&amp;gt;{
            if(err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            
            if(results.affectedRows==0)
                return res.status(StatusCodes.BAD_REQUEST).end();
            else   
                return res.status(StatusCodes.OK).json(results);
        })
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>백엔드/node.js(express)</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/234</guid>
      <comments>https://myeongsu0257.tistory.com/234#entry234comment</comments>
      <pubDate>Wed, 3 Jan 2024 01:54:04 +0900</pubDate>
    </item>
    <item>
      <title>도서주문관리 프로젝트 - 1.API 설계(API,테이블)</title>
      <link>https://myeongsu0257.tistory.com/232</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;회원 API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/users/join&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;201&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; email : &quot;사용자가 입력한 이메일&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; password : &quot;사용자가 입력한 비밀번호&quot;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/users/login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; email : &quot;사용자가 입력한 이메일&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; password : &quot;사용자가 입력한 비밀번호&quot;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Cookie&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;JWT token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호 초기화 요청&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/users/reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; email : &quot;사용자가 입력한 이메일&quot;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; email : &quot;이메일&quot;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호 초기화(=수정)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 144px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;PUT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;/users/reset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 76px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 76px;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 76px;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; email :&quot;이전 페이지에서 입력했던 이메일&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; password : &quot;사용자가 입력한 비밀번호&quot;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도서 API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 도서 조회(이미지경로&amp;nbsp; n개씩 보내줘야함)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/books?limit={page당 도서 수}&amp;amp;currentPage={현재 page}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;// 전체 도서 목록에는 도서의 상세 정보를 포함합니다&lt;br /&gt;// 필요한 데이터만 선별하여 구현 부탁드립니다&lt;br /&gt;&lt;br /&gt;{&lt;br /&gt;&amp;nbsp; books:[&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; id : 도서 id,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;title : &quot;도서 제목&quot;,&lt;/span&gt; &lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;img : 이미지 id(picksum image #id),&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp;summary : &quot;요약 정보&quot;,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;author : &quot;도서 작가&quot;,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;price : 가격,&lt;br /&gt;&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp; likes : 좋아요 수,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; pubDate :&quot;출간일&quot;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp;},&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp;{&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; id : 도서 id,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; title : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp; img : 이미지 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; summary : &quot;요약 정보&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; author : &quot;도서 작가&quot;,&lt;br /&gt;&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;price : 가격,&lt;br /&gt;&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp; likes : 좋아요 수,&lt;br /&gt;&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp; pubDate :&quot;출간일&quot; &lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; &amp;nbsp;}, &amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; ...&lt;br /&gt;&amp;nbsp; &amp;nbsp; ].&lt;br /&gt;&amp;nbsp; pagination : {&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; currentPage : 현재 페이지,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; totalBooks : 총 도서 수&lt;br /&gt;&amp;nbsp; }&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개별 도서 조회&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/books/{bookid}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; id: 도서 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; title : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; img : 이미지 id, &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; categoryName:&quot;카테고리&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; form:&quot;포맷&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; isbn : &quot;isbn&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; summary : &quot;요약 설명&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; detail :&quot;상세 설명&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; author : &quot;도서 작가&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; pages : 쪽 수,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; contents : &quot;목차&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; price : 가격,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; likes : 좋아요 수,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; liked : boolean,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; pubDate :&quot;출간일&quot; &lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리별 도서 목록 조회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new : true -&amp;gt; 신간 조회(기준: 출간일 30일 이내)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/books?limit={page당 도서 수}&amp;amp;tPage={현재 page}&amp;amp;categoryId={categoryId}&amp;amp;new={boolean}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; books:[&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp;{&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; id : 도서 id,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;title : &quot;도서 제목&quot;,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;img : 이미지 id(picksum image #id),&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp;summary : &quot;요약 정보&quot;,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;author : &quot;도서 작가&quot;,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;price : 가격,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; likes : 좋아요 수,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; pubDate :&quot;출간일&quot;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp;},&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp;{&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; id : 도서 id,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; title : &quot;도서 제목&quot;,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; img : 이미지 id,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; summary : &quot;요약 정보&quot;,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; author : &quot;도서 작가&quot;,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;price : 가격,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; likes : 좋아요 수,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; pubDate :&quot;출간일&quot;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp;}, &amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;...&lt;/span&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; ].&lt;br /&gt;&amp;nbsp; pagination : {&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; currentPage : 현재 페이지,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp; totalBooks : 총 도서 수&lt;/span&gt;&lt;br /&gt;&amp;nbsp; }&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카테고리API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리 전체 조회&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/category&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;[&lt;br /&gt;&amp;nbsp; &amp;nbsp;{&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;id:0,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;categoryName : &quot;동화&quot;&lt;br /&gt;&amp;nbsp; &amp;nbsp;},&lt;br /&gt;&amp;nbsp; &amp;nbsp;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;id:1,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;categoryName:&quot;소설&quot;&lt;br /&gt;&amp;nbsp; &amp;nbsp; }&lt;br /&gt;&amp;nbsp; &amp;nbsp; ...&lt;br /&gt;]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋아요 API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 추가&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 102px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;/likes/{bookdId}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Request Header&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;&quot;Authorization&quot; : 로그인 할때 받은 JWT TOKEN(문자열)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 취소&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;DELETE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/likes/{bookdId}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Header&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&quot;Authorization&quot; : 로그인 할때 받은 JWT TOKEN(문자열)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장바구니 API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장바구니 담기&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 179px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;/cart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;201&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Request Header&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;&quot;Authorization&quot; : 로그인 할때 받은 JWT TOKEN(문자열)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 94px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 94px;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 94px;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; bookId : 도서 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; count : 수량&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장바구니 아이템 목록 조회 / 선택한 장바구니 목록 조회&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/cart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Header&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&quot;Authorization&quot; : 로그인 할때 받은 JWT TOKEN(문자열)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; selected : [cartIemId,cartItemId, ...]&amp;nbsp;&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; id : 장바구니 도서 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; bookId : 도서 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;title : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;summary : &quot;도서 요약&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;quantity : 수량,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;price : 가격&lt;br /&gt;&amp;nbsp; &amp;nbsp;},&lt;br /&gt;&amp;nbsp; &amp;nbsp;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; id : 장바구니 도서 id, &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; bookId : 도서 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;title : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;summary : &quot;도서 요약&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; count : 수량, &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;price : 가격&lt;br /&gt;&amp;nbsp; &amp;nbsp; } &lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장바구니 도서 삭제&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;DELETE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/cart/{cartItemId}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;선택한 장바구니 목록 조회&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/cart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;[ cartItemId, cartItemId, ... ]&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; cartItemId : 장바구니 도서 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; bookId : 도서 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;title : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;summary : &quot;도서 요약&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;count : 수량,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;price : 가격&lt;br /&gt;&amp;nbsp; &amp;nbsp;},&lt;br /&gt;&amp;nbsp; &amp;nbsp;{&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;cartItemId : 장바구니 도서 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; bookId : 도서 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;title : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;summary : &quot;도서 요약&quot;,&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;count : 수량,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;price : 가격&lt;br /&gt;&amp;nbsp; &amp;nbsp; }&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(결제)주문 API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제하기 = 주문하기 = 주문등록 데이터베이스 주문 insert = 장바구니에서 주문된 상품은 delete&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/orders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Header&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&quot;Authorization&quot; : 로그인 할때 받은 JWT TOKEN(문자열)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;{&lt;br /&gt;&amp;nbsp; &amp;nbsp; items : [ 장바구니 도서 id, 장바구니 도서 id ... ],&lt;br /&gt;&amp;nbsp; &amp;nbsp; delivery : {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;address : &quot;주소&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;reveiver : &quot;이름&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;contact : &quot;010-0000-0000&quot;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;},&lt;br /&gt;&amp;nbsp; &amp;nbsp;totalQuantity : &quot;총수량&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp;totalPrice : &quot;총금액&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp;firstBookTitle: 대표 도서 제목&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문 목록(내역) 조회&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 555px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;/orders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 17px;&quot;&gt;Request Header&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 17px;&quot;&gt;&quot;Authorization&quot; : 로그인 할때 받은 JWT TOKEN(문자열)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 54px;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 54px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 433px;&quot;&gt;
&lt;td style=&quot;width: 19.3023%; height: 433px;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%; height: 433px;&quot;&gt;[&lt;br /&gt;&amp;nbsp; &amp;nbsp; {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; id : 주문 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; createdAt : &quot;주문일자&quot; ,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; address : &quot;주소&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; reveiver : &quot;이름&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; contact : &quot;010-0000-0000&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; bookTitle : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; totalPrice: 결재금액,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; totalQuantity: 총 수량&lt;br /&gt;&amp;nbsp; &amp;nbsp; },&lt;br /&gt;&amp;nbsp; {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; id : 주문 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; createdAt : &quot;주문일자&quot; ,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; address : &quot;주소&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; reveiver : &quot;이름&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; contact : &quot;010-0000-0000&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; bookTitle : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; totalPrice: 결재금액,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; totalQuantity: 총 수량&lt;br /&gt;&amp;nbsp; &amp;nbsp; }, &lt;br /&gt;&amp;nbsp; &amp;nbsp; ...&lt;br /&gt;]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문 상세 상품 조회&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;URI&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;/orders/{orderId}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;HTTP status code&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Request Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.3023%;&quot;&gt;Response Body&lt;/td&gt;
&lt;td style=&quot;width: 80.6977%;&quot;&gt;[&lt;br /&gt;&amp;nbsp; &amp;nbsp; {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; bookId : 주문 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; title : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; author : &quot;작가명&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; price:&quot;가격&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; quantity : &quot;수량&quot;&lt;br /&gt;&amp;nbsp; &amp;nbsp; },&lt;br /&gt;&amp;nbsp; &amp;nbsp; {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; bookId : 주문 id,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; title : &quot;도서 제목&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; author : &quot;작가명&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; price:&quot;가격&quot;,&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; quantity : &quot;수량&quot;&lt;br /&gt;&amp;nbsp; &amp;nbsp; } &lt;br /&gt;]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테이블&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;users&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 89px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 6.10465%; height: 17px;&quot;&gt;id&lt;/td&gt;
&lt;td style=&quot;width: 18.8954%;&quot;&gt;email&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 6.10465%; height: 21px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 18.8954%;&quot;&gt;kim@mail.com&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;1111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 6.10465%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 18.8954%;&quot;&gt;lee@mail.com&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;2222&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 6.10465%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 18.8954%;&quot;&gt;park@mail.com&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;3333&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 6.10465%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 18.8954%;&quot;&gt;choi@mail.com&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;4444&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 6.10465%;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 18.8954%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;5555&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 6.10465%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.8954%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;books&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 23px;&quot;&gt;
&lt;td style=&quot;width: 3.64344%; height: 23px;&quot;&gt;id&lt;/td&gt;
&lt;td style=&quot;width: 7.36436%; height: 23px;&quot;&gt;title&lt;/td&gt;
&lt;td style=&quot;width: 4.10854%;&quot;&gt;img&lt;/td&gt;
&lt;td style=&quot;width: 8.81787%;&quot;&gt;category&lt;/td&gt;
&lt;td style=&quot;width: 6.96707%;&quot;&gt;form&lt;/td&gt;
&lt;td style=&quot;width: 5.80423%;&quot;&gt;isbn&lt;/td&gt;
&lt;td style=&quot;width: 9.86431%; height: 23px;&quot;&gt;summary&lt;/td&gt;
&lt;td style=&quot;width: 8.00397%;&quot;&gt;detail&lt;/td&gt;
&lt;td style=&quot;width: 6.25969%;&quot;&gt;author&lt;/td&gt;
&lt;td style=&quot;width: 7.49031%;&quot;&gt;pages&lt;/td&gt;
&lt;td style=&quot;width: 7.6066%;&quot;&gt;contents&lt;/td&gt;
&lt;td style=&quot;width: 6.89931%; height: 23px;&quot;&gt;price&lt;/td&gt;
&lt;td style=&quot;width: 10.9691%; height: 23px;&quot;&gt;pub_date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 3.64344%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 7.36436%; height: 17px;&quot;&gt;어린왕자들&lt;/td&gt;
&lt;td style=&quot;width: 4.10854%;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;width: 8.81787%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 6.96707%;&quot;&gt;종이책&lt;/td&gt;
&lt;td style=&quot;width: 5.80423%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 9.86431%; height: 17px;&quot;&gt;어리다..&lt;/td&gt;
&lt;td style=&quot;width: 8.00397%;&quot;&gt;어림&lt;/td&gt;
&lt;td style=&quot;width: 6.25969%;&quot;&gt;미상&lt;/td&gt;
&lt;td style=&quot;width: 7.49031%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;width: 7.6066%;&quot;&gt;목차~&lt;/td&gt;
&lt;td style=&quot;width: 6.89931%; height: 17px;&quot;&gt;20000&lt;/td&gt;
&lt;td style=&quot;width: 10.9691%; height: 17px;&quot;&gt;2019-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 3.64344%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 7.36436%; height: 17px;&quot;&gt;신데렐라들&lt;/td&gt;
&lt;td style=&quot;width: 4.10854%;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;width: 8.81787%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 6.96707%;&quot;&gt;종이책&lt;/td&gt;
&lt;td style=&quot;width: 5.80423%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 9.86431%; height: 17px;&quot;&gt;유리구두..&lt;/td&gt;
&lt;td style=&quot;width: 8.00397%;&quot;&gt;구두&lt;/td&gt;
&lt;td style=&quot;width: 6.25969%;&quot;&gt;미상&lt;/td&gt;
&lt;td style=&quot;width: 7.49031%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;width: 7.6066%;&quot;&gt;목차~&lt;/td&gt;
&lt;td style=&quot;width: 6.89931%; height: 17px;&quot;&gt;20000&lt;/td&gt;
&lt;td style=&quot;width: 10.9691%; height: 17px;&quot;&gt;2023-12-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 3.64344%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 7.36436%; height: 17px;&quot;&gt;백설공주들&lt;/td&gt;
&lt;td style=&quot;width: 4.10854%;&quot;&gt;60&lt;/td&gt;
&lt;td style=&quot;width: 8.81787%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 6.96707%;&quot;&gt;종이책&lt;/td&gt;
&lt;td style=&quot;width: 5.80423%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 9.86431%; height: 17px;&quot;&gt;사과..&lt;/td&gt;
&lt;td style=&quot;width: 8.00397%;&quot;&gt;사과&lt;/td&gt;
&lt;td style=&quot;width: 6.25969%;&quot;&gt;미상&lt;/td&gt;
&lt;td style=&quot;width: 7.49031%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;width: 7.6066%;&quot;&gt;목차~&lt;/td&gt;
&lt;td style=&quot;width: 6.89931%; height: 17px;&quot;&gt;20000&lt;/td&gt;
&lt;td style=&quot;width: 10.9691%; height: 17px;&quot;&gt;2023-11-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 3.64344%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 7.36436%;&quot;&gt;흥부와 놀부들&lt;/td&gt;
&lt;td style=&quot;width: 4.10854%;&quot;&gt;90&lt;/td&gt;
&lt;td style=&quot;width: 8.81787%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 6.96707%;&quot;&gt;종이책&lt;/td&gt;
&lt;td style=&quot;width: 5.80423%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 9.86431%;&quot;&gt;제비..&lt;/td&gt;
&lt;td style=&quot;width: 8.00397%;&quot;&gt;제비&lt;/td&gt;
&lt;td style=&quot;width: 6.25969%;&quot;&gt;미상&lt;/td&gt;
&lt;td style=&quot;width: 7.49031%;&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;width: 7.6066%;&quot;&gt;목차~&lt;/td&gt;
&lt;td style=&quot;width: 6.89931%;&quot;&gt;20000&lt;/td&gt;
&lt;td style=&quot;width: 10.9691%;&quot;&gt;2023-12-31&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cateogry&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 42.5575%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.6612%;&quot;&gt;id&lt;/td&gt;
&lt;td style=&quot;width: 17.3432%;&quot;&gt;category_name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.6612%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 17.3432%;&quot;&gt;동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.6612%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 17.3432%;&quot;&gt;소설&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.6612%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 17.3432%;&quot;&gt;사회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;likes&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 32.4406%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.9134%;&quot;&gt;user_id&lt;/td&gt;
&lt;td style=&quot;width: 18.132%;&quot;&gt;liked_book_id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.9134%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 18.132%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.9134%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 18.132%;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.9134%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 18.132%;&quot;&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.9134%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 18.132%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.9134%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 18.132%;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.9134%;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 18.132%;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.9134%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 18.132%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cartItems&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 46.5116%; height: 103px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.4731%; height: 17px;&quot;&gt;id(PK)&lt;/td&gt;
&lt;td style=&quot;width: 12.8448%; height: 17px;&quot;&gt;book_id(FK)&lt;/td&gt;
&lt;td style=&quot;width: 8.81671%; height: 17px;&quot;&gt;quantity&lt;/td&gt;
&lt;td style=&quot;width: 9.0095%; height: 17px;&quot;&gt;user_id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.4731%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 12.8448%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 8.81671%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 9.0095%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.4731%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 12.8448%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 8.81671%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 9.0095%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.4731%; height: 18px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 12.8448%; height: 18px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 8.81671%; height: 18px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 9.0095%; height: 18px;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.4731%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 12.8448%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 8.81671%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 9.0095%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.4731%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 12.8448%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 8.81671%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 9.0095%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;delivery&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.14725%;&quot;&gt;id&lt;/td&gt;
&lt;td style=&quot;width: 57.5193%;&quot;&gt;address&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;receiver&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;contact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.14725%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 57.5193%;&quot;&gt;서울시 중구&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;최명수&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;010-1234-5678&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.14725%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 57.5193%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.14725%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 57.5193%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;orders&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 92.9054%; height: 52px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 11.1628%; height: 17px;&quot;&gt;order_id&lt;/td&gt;
&lt;td style=&quot;width: 12.2093%; height: 17px;&quot;&gt;delivery_id&lt;/td&gt;
&lt;td style=&quot;width: 19.5349%; height: 17px;&quot;&gt;total_price&lt;/td&gt;
&lt;td style=&quot;width: 15.9884%; height: 17px;&quot;&gt;created_at&lt;/td&gt;
&lt;td style=&quot;width: 13.4592%;&quot;&gt;book_title&lt;/td&gt;
&lt;td style=&quot;width: 11.9587%;&quot;&gt;total_count&lt;/td&gt;
&lt;td style=&quot;width: 10.5345%;&quot;&gt;user_id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 11.1628%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 12.2093%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 19.5349%; height: 17px;&quot;&gt;28000&lt;/td&gt;
&lt;td style=&quot;width: 15.9884%; height: 17px;&quot;&gt;2023-12-01&lt;/td&gt;
&lt;td style=&quot;width: 13.4592%;&quot;&gt;대표책제목&lt;/td&gt;
&lt;td style=&quot;width: 11.9587%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 10.5345%;&quot;&gt;&amp;nbsp;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 11.1628%; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 12.2093%; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 19.5349%; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 15.9884%; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 13.4592%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 11.9587%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 10.5345%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ordredBook&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 52.5581%; height: 140px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0542%;&quot;&gt;order_id&lt;/td&gt;
&lt;td style=&quot;width: 17.5194%;&quot;&gt;book_id&lt;/td&gt;
&lt;td style=&quot;width: 17.9844%;&quot;&gt;quantity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0542%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 17.5194%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 17.9844%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0542%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 17.5194%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 17.9844%;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0542%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 17.5194%;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 17.9844%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>백엔드/node.js(express)</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/232</guid>
      <comments>https://myeongsu0257.tistory.com/232#entry232comment</comments>
      <pubDate>Thu, 28 Dec 2023 20:20:46 +0900</pubDate>
    </item>
    <item>
      <title>JWT</title>
      <link>https://myeongsu0257.tistory.com/231</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;인증과 인가&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증(Authentication) = 로그인 :&amp;nbsp; 관리자든 고객이든 인증을 통해서 사이트에 가입된 사용자라는 걸 증명하는 것&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) : ID, PW로 로그인 하는 행위&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인가(Authorization) :&amp;nbsp; 인증 이후의 프로세스로, 인증된 유저가 어떠한 자원에 접근할 수 있는지 확인하는 절차&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) : 같은 사이트 내에 관리자/고객에 따라 접근할 수 있는 페이지가 다르다,&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿠키 vs 세션 vs JWT&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿠키&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면, 로그인을 하면 서버가 쿠키를 구워서 준다. 그 후 사용자랑 서버가 쿠키를 가지고 핑퐁한다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹에서 서버와 클라이언트가 주고받는 데이터 중 하나&lt;/li&gt;
&lt;li&gt;생성은 웹 서버에서 하여 웹 브라우저에게 주면, 브라우저가 자기 메모리에 저장한 후 다음번에 같은 웹서버 방문할 때 쿠키를 들고 요청한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 로그인 정보를 사용하기 때문에 인증을 위한 추가적인 데이터 저장이 필요없다.(쿠키는 서버가 아닌 클라이언트 웹 브라우저에 한다) -&amp;gt; Stateless(상태저장하지않는다)하다 -&amp;gt; RESTful&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 주요 정보를 매번 요청에 담기 때문에 보안상 문제가 있다.&lt;/li&gt;
&lt;li&gt;클라이언트에서 쿠키 정보를 쉽게 변경,삭제할 수 있고, 가로채기 당할 수 도 있다.&lt;/li&gt;
&lt;li&gt;쿠키사이즈가 커질수록 네트워크 부하가 심해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 Cookie에 담아서 계속 사용하기에는 누가 중간에 쿠키를 낚아채서 뜯어 볼까 걱정이된다. 그래서 해결방안이 Session이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면,로그인을 하면 서버가 금고를 만들어서 정보를 저장 하고 그 금고 번호를 준다. 그 후 사용자와 서버는 금고 번호만 가지고 핑퐁을 한다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cookie에 중요한 정보를 담지 말고 중요한 정보는 서버에 저장해두고 그 정보가 어딨는지 주소만 적어서 Cookie에 담는 것&amp;nbsp;&lt;/li&gt;
&lt;li&gt;쿠키에 넣어서 보내기엔 너무 중요한 내용은 서버가 가진 금고(Session)에 넣어두고, 그 금고번호(Seesion ID)만 쿠키에 넣어서 통신함&lt;/li&gt;
&lt;li&gt;장점&amp;nbsp;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 로그인 정보를 주고 받지 않기 대문에 상대적으로 안전&lt;/li&gt;
&lt;li&gt;사용자마다 고유한 세션 ID가 발급되기 때문에, 요청이 들어올 때마다 회원 DB를 찾지 않아도 된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자를 식별할 수 있는 값인 세션 ID를 생성하고, 서버에 저장해야 하는 작업이 생긴다. -&amp;gt; Stateless하지 않다&lt;/li&gt;
&lt;li&gt;서버세션저장소를 사용하므로 요청이 많아지면 서버 부하가 심해진다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT(JSON Web Token)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 형태의 데이터를 안전하게 전송하기 위한(웹에서 사용하는) 토큰 즉 토큰을 가진 사용자가 &quot;증명&quot;하기 위한 수단&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰 : (인증용) 입장 가능한 유저, (인가용) 관리자권환&amp;amp;일반 유저 권한&lt;/li&gt;
&lt;li&gt;장점 : 보안에 강하다(암호화가 되어 있다), Stateless하다(서버가 상태를 저장하지 않는다), 서버 부담을 줄일 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jwt.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jwt.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703580045492&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JWT.IO&quot; data-og-description=&quot;JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.&quot; data-og-host=&quot;jwt.io&quot; data-og-source-url=&quot;https://jwt.io/&quot; data-og-url=&quot;http://jwt.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b7835q/hyUTCvGiNK/KgXHzzR7GTFMlkIMLf7itk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/rO2aK/hyUTDg37Ds/b4PrkRAaBtu05QieGqQto0/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://jwt.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jwt.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b7835q/hyUTCvGiNK/KgXHzzR7GTFMlkIMLf7itk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/rO2aK/hyUTDg37Ds/b4PrkRAaBtu05QieGqQto0/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JWT.IO&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jwt.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JWT 구조&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1B7gp/btsCAhxDqXz/CEYi162WjrU7hdbsYEodpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1B7gp/btsCAhxDqXz/CEYi162WjrU7hdbsYEodpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1B7gp/btsCAhxDqXz/CEYi162WjrU7hdbsYEodpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1B7gp%2FbtsCAhxDqXz%2FCEYi162WjrU7hdbsYEodpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;288&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;744&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;헤더 : 토큰을 암호화하 데 사용한 알고리즘, 토큰의 형태(jwt)&lt;/li&gt;
&lt;li&gt;페이로드 : 사용자 정보(이름, 주소, 핸드폰, ... 비밀번호 X)&lt;/li&gt;
&lt;li&gt;서명 : 만약 페이로드 값이 바뀌면, 이 서명값이 통째로 바뀌기 때문에 우리는 JWT를 믿고 쓸 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JWT로 인증/인가 하는 절차&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S3MIM/btsCy37IcKP/p9jnz2s6MPrIKOW5ekKLAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S3MIM/btsCy37IcKP/p9jnz2s6MPrIKOW5ekKLAK/img.png&quot; data-alt=&quot;https://velog.io/@hahan/JWT%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S3MIM/btsCy37IcKP/p9jnz2s6MPrIKOW5ekKLAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS3MIM%2FbtsCy37IcKP%2Fp9jnz2s6MPrIKOW5ekKLAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;308&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://velog.io/@hahan/JWT%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로그인 요청( HTTP body에 username과 password)&lt;/li&gt;
&lt;li&gt;내부로직을 통해 DB에서 사용자 확인&amp;nbsp;&lt;/li&gt;
&lt;li&gt;로그인 정보를 Payload에 담고, Secret Key를 사용해서 JWT(토큰, Access Token) 발행&lt;/li&gt;
&lt;li&gt;서버가 JWT를 Client에 전달( 전달방법은 개발자가 정함)&lt;/li&gt;
&lt;li&gt;클라이언트는 전달받은 토큰(JWT)를 저장(쿠키, Local Storage 등)&lt;/li&gt;
&lt;li&gt;클라이언트가 서버에 요청할 때마다 토큰(JWT)를 요청 Header의 Authorization에 포함시켜 함께 전달&lt;/li&gt;
&lt;li&gt;서버는 클라이언트가 전달한 토큰의 Signature(서명)를 secret Key로 복호화한 후, 위변조 여부 및 유효기간을 검증&lt;/li&gt;
&lt;li&gt;검증에 성공하면 jwt에 사용자 정보를 확인하고 요청에 응답&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/jsonwebtoken&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.npmjs.com/package/jsonwebtoken&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703581374820&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;jsonwebtoken&quot; data-og-description=&quot;JSON Web Token implementation (symmetric and asymmetric). Latest version: 9.0.2, last published: 4 months ago. Start using jsonwebtoken in your project by running &amp;#96;npm i jsonwebtoken&amp;#96;. There are 26011 other projects in the npm registry using jsonwebtoken.&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/jsonwebtoken&quot; data-og-url=&quot;https://www.npmjs.com/package/jsonwebtoken&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WUKaU/hyUTHcIKI0/VIJUFVfthvjxGkwISt5yW0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/jsonwebtoken&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/jsonwebtoken&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WUKaU/hyUTHcIKI0/VIJUFVfthvjxGkwISt5yW0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;jsonwebtoken&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JSON Web Token implementation (symmetric and asymmetric). Latest version: 9.0.2, last published: 4 months ago. Start using jsonwebtoken in your project by running `npm i jsonwebtoken`. There are 26011 other projects in the npm registry using jsonwebtoken.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703581648350&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install jsonwebtoken&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703581656069&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var jwt = require('jsonwebtoken');
var token = jwt.sign({foo:'bar'},'shhhhh');
//token생성 = jwt 서명을 했다!(페이로드, 나만의 암호키) + SHA256

console.log(token);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔에서 확인한 토큰 값&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3dk8L/btsCBMRFi9V/eBYVVSsyeS1GDrMJKlwwUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3dk8L/btsCBMRFi9V/eBYVVSsyeS1GDrMJKlwwUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3dk8L/btsCBMRFi9V/eBYVVSsyeS1GDrMJKlwwUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3dk8L%2FbtsCBMRFi9V%2FeBYVVSsyeS1GDrMJKlwwUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;143&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 발급한 토큰을 jwt.io에서 검증해보면 Decoded된 정보들을 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;627&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6VjiI/btsCyx84ngQ/w4hxV8kgttExrlDyuhSKU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6VjiI/btsCyx84ngQ/w4hxV8kgttExrlDyuhSKU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6VjiI/btsCyx84ngQ/w4hxV8kgttExrlDyuhSKU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6VjiI%2FbtsCyx84ngQ%2Fw4hxV8kgttExrlDyuhSKU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;438&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;627&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703581849307&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// verify a token symmetric
var decoded = jwt.verify(token, 'shhhhh');
console.log(decoded);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 검증을 jwt.io에서 할 수 없으므로 위의 코드를 실행시켜 확인해보면 iat가 위의 jwt.io랑 다르게 나옵니다. 즉, iat(Issued at)는 토큰이 발행한 시간을 초로 나타낸 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo7uAo/btsCzFFtpaw/qbhJqTGklGKJHohOnFjim1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo7uAo/btsCzFFtpaw/qbhJqTGklGKJHohOnFjim1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo7uAo/btsCzFFtpaw/qbhJqTGklGKJHohOnFjim1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo7uAo%2FbtsCzFFtpaw%2FqbhJqTGklGKJHohOnFjim1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;103&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;103&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번더 실행해보면 마찬가지로 Header의 값은 같은데 Payload의 값과 서명값은 다릅니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkdKVs/btsCy5xHB9p/R4mAtcDUkTCKLud39yyv61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkdKVs/btsCy5xHB9p/R4mAtcDUkTCKLud39yyv61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkdKVs/btsCy5xHB9p/R4mAtcDUkTCKLud39yyv61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkdKVs%2FbtsCy5xHB9p%2FR4mAtcDUkTCKLud39yyv61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;106&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위코드에서 verify(token, 'shhhhh') 에서 'shhhhh'는 나만의 암호키입니다. 저렇게 코드에 유출하면 안 되고 따로 파일에 저장하든가 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;.env(environment : 환경변수 '설정 값')&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발을 하다가 포트넘버, 데이터베이스 계정, 암호키 등 외부에 유출되면 안 되는 중요한 환경변수(깃허브에 올라가면 안되는 값)들을 다로 관리하기 위한 파일&amp;nbsp;&lt;/li&gt;
&lt;li&gt;파일 확장자 : . env&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env 파일은 환경변수파일이기 때문에 프로젝트 최상위 패키지에 존재해야한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703582651205&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# install locally (recommended)
npm install dotenv --save&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상단에 .env파일을 만들어주고 키값을 작성해준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/omlg6/btsCLp1VwmE/kV4fzvsKRfjKDkdmxj1iuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/omlg6/btsCLp1VwmE/kV4fzvsKRfjKDkdmxj1iuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/omlg6/btsCLp1VwmE/kV4fzvsKRfjKDkdmxj1iuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fomlg6%2FbtsCLp1VwmE%2FkV4fzvsKRfjKDkdmxj1iuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;330&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;env파일을 불러와서 수정한 코드이다. 포트넘버도 해당파일에서 config해준뒤 process.env.PORT를 하면 사용가능하다&lt;/p&gt;
&lt;pre id=&quot;code_1703583081548&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var jwt = require('jsonwebtoken');
var dotenv = require('dotenv');
dotenv.config();

//서명 = 토큰발행 
var token = jwt.sign({foo:'bar'}, process.env.PRIVATE_KEY);
//token생성 = jwt 서명을 했다!(페이로드, 나만의 암호키) + SHA256

console.log(token);

//검증
// 만약 검증에 성공하면 ,페이로드 값을 확인할 수 있음

// verify a token symmetric
var decoded = jwt.verify(token, process.env.PRIVATE_KEY);
console.log(decoded);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cookie&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 클라이언트에게 token 값을 아래처럼 body에 실어서 보낼 수는 있지만 보통 cookie에 많이 실어서 보냅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1703584402615&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//token 발급
 const token = jwt.sign({
       email : loginUser.email,
       name : loginUser.name
}, process.env.PRIVATE_KEY);

 res.status(200).json({
   message: `${loginUser.name}님 로그인 되었습니다.`,
   token : token
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cookie에 token이라는 상자를 하나 만들어서 여기에 위의 token을 담아서 보내주겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703584664191&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//token 발급
const token = jwt.sign({
  email : loginUser.email,
  name : loginUser.name
}, process.env.PRIVATE_KEY);

res.cookie(&quot;token&quot;,token);

res.status(200).json({
  message: `${loginUser.name}님 로그인 되었습니다.`
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;postman에서 확인해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;585&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/suTUM/btsCCzx18Lx/kVXYYyTJokeXtGOUSOZAZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/suTUM/btsCCzx18Lx/kVXYYyTJokeXtGOUSOZAZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/suTUM/btsCCzx18Lx/kVXYYyTJokeXtGOUSOZAZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsuTUM%2FbtsCCzx18Lx%2FkVXYYyTJokeXtGOUSOZAZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1450&quot; height=&quot;585&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;585&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 postman에서 Cookies탭에 Secure,HttpOnly를 확인해보면 false인것을 알 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Secure는 HTTP이면 false HTTPS이면 true가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http:://localhost:1234/login&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTPS(secure)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;https:://www.naver.com&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPOnly( true가 되면&amp;nbsp; 프론트엔드신경쓰지 않고 API 호출&quot;만&quot;허락하게 한다) : XSS 공격(프론트엔드 공격: 웹 브라우저 JS접근 =&amp;gt; 공격) true로 바꿀려면 아래처럼 httpOnly:true 해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703585303844&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;res.cookie(&quot;token&quot;,token, {
    httpOnly:true
 });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvxEv/btsCzGqT4gj/obsG1QnIR39yUHXw7mei71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvxEv/btsCzGqT4gj/obsG1QnIR39yUHXw7mei71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvxEv/btsCzGqT4gj/obsG1QnIR39yUHXw7mei71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmvxEv%2FbtsCzGqT4gj%2FobsG1QnIR39yUHXw7mei71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1375&quot; height=&quot;500&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;jwt 유효기간 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jwt토큰에 유효기간을 설정할 수 있습니다. expriesIn를 이용해 토큰의 유효기간을 설정할 수 있습니다. 30m 즉 30분으로 설정했고 추가적으로 누가 토큰을 생성했는지 issuer을 통해 설정할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703585590431&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//token 발급
const token = jwt.sign({
     email : loginUser.email,
     name : loginUser.name
     }, process.env.PRIVATE_KEY, {
        expiresIn : '30m',
        issuer : &quot;choims&quot;
});

res.cookie(&quot;token&quot;,token, {
   httpOnly:true
});
console.log(token);
res.status(200).json({
  message: `${loginUser.name}님 로그인 되었습니다.`
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발급된 토큰을 jwt.io 에서 확인해보면 PAYLOAD를 보면 정확하게 나옵니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEIG19/btsCLsdikhP/xvAmFM7vzjKIgWm8nxbpvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEIG19/btsCLsdikhP/xvAmFM7vzjKIgWm8nxbpvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEIG19/btsCLsdikhP/xvAmFM7vzjKIgWm8nxbpvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEIG19%2FbtsCLsdikhP%2FxvAmFM7vzjKIgWm8nxbpvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;472&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>백엔드/node.js(express)</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/231</guid>
      <comments>https://myeongsu0257.tistory.com/231#entry231comment</comments>
      <pubDate>Tue, 26 Dec 2023 19:37:29 +0900</pubDate>
    </item>
    <item>
      <title>토이프로젝트 유튜브4 (유효성검사,next)</title>
      <link>https://myeongsu0257.tistory.com/230</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://myeongsu0257.tistory.com/229&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://myeongsu0257.tistory.com/229&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703228897059&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;토이프로젝트 유튜브4(회원API,채널API sql적용)&quot; data-og-description=&quot;이전 회원 API 설계 토이프로젝트 유튜브1(회원API설계), router 유튜브 회원 로그인 회원가입 회원정보조회 회원탈퇴 회원은 계정 1개당 채널 100개를 가질 수 있다. 채널 채널 생성 채널 수정 채널 &quot; data-og-host=&quot;myeongsu0257.tistory.com&quot; data-og-source-url=&quot;https://myeongsu0257.tistory.com/229&quot; data-og-url=&quot;https://myeongsu0257.tistory.com/229&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NbLAN/hyUPCXCON0/8C90DooJlw2maRpVu2zdE1/img.png?width=800&amp;amp;height=308&amp;amp;face=0_0_800_308,https://scrap.kakaocdn.net/dn/su5Lm/hyUPCQTOZs/dZIrvNzkr8NA9KkOqSl7M0/img.png?width=800&amp;amp;height=308&amp;amp;face=0_0_800_308,https://scrap.kakaocdn.net/dn/ZQGjm/hyUPHdyH4a/KtBezyFe2roxIUp5R9XBRk/img.png?width=564&amp;amp;height=624&amp;amp;face=0_0_564_624&quot;&gt;&lt;a href=&quot;https://myeongsu0257.tistory.com/229&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://myeongsu0257.tistory.com/229&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NbLAN/hyUPCXCON0/8C90DooJlw2maRpVu2zdE1/img.png?width=800&amp;amp;height=308&amp;amp;face=0_0_800_308,https://scrap.kakaocdn.net/dn/su5Lm/hyUPCQTOZs/dZIrvNzkr8NA9KkOqSl7M0/img.png?width=800&amp;amp;height=308&amp;amp;face=0_0_800_308,https://scrap.kakaocdn.net/dn/ZQGjm/hyUPHdyH4a/KtBezyFe2roxIUp5R9XBRk/img.png?width=564&amp;amp;height=624&amp;amp;face=0_0_564_624');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;토이프로젝트 유튜브4(회원API,채널API sql적용)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 회원 API 설계 토이프로젝트 유튜브1(회원API설계), router 유튜브 회원 로그인 회원가입 회원정보조회 회원탈퇴 회원은 계정 1개당 채널 100개를 가질 수 있다. 채널 채널 생성 채널 수정 채널&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;myeongsu0257.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅까지 api를 설계하면서 body에 담겨온 값들을 그냥 무조건 사용하는 방식으로 했는데 이러한 방식은 좋지않습니다. 따라서 유효성검사에 대해 알아보고 코드에 적용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유효성검사&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유효성 : &quot;사용자가 입력한 값&quot; 유효성(타당성)을 확인하는 것&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;express-validator&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://express-validator.github.io/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://express-validator.github.io/docs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703229168053&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;express-validator | express-validator&quot; data-og-description=&quot;Overview&quot; data-og-host=&quot;express-validator.github.io&quot; data-og-source-url=&quot;https://express-validator.github.io/docs&quot; data-og-url=&quot;https://express-validator.github.io/docs/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://express-validator.github.io/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://express-validator.github.io/docs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;express-validator | express-validator&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Overview&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;express-validator.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703229264446&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install express-validator&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치를 완료했으면 코드 맨위에 아래와같이 선언해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1703229383071&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {body,validationResult}=require('express-validator')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;채널생성 유효성검사&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기존의 채널생성api에서 상단에 코드도 추가해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703230089236&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//채널 개별 생성
    .post(
        [body('userId').notEmpty().isInt().withMessage('숫자 입력필요'),
        body('name').notEmpty().isString().withMessage('문자 입력필요')]
        , (req, res) =&amp;gt; {
            const err = validationResult(req)
            if (!err.isEmpty()) {
                return res.status(400).json(err.array())
            }
            const { name, userId } = req.body
            conn.query(
                `INSERT INTO channels (name,user_id) VALUES(?,?)`, [name, userId],
                function (err, results, fields) {
                    res.status(201).json(results);
                }
            )
        })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비정상적인 값(userid에 문자열)을 body에 넣었을때 콘솔에 아래처럼 출력됩니다. 따라서 위의코드처럼 userId나 name을 if문에서 예외처리를 해주지 않아도 됩니다. 추가적으로 유효성검사에서 에러가 발생했을때 return을 추가해주어서 함수를 종료해줍니다.(아래를 코드를 읽게하지 않게 하기 위해)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;body에서 name과 userId에 형식에 맞지 않은 값을 넣은 경우&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YR3Jh/btsCv4So1du/SVGZ5IlCmiOZy3FSIRQh3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YR3Jh/btsCv4So1du/SVGZ5IlCmiOZy3FSIRQh3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YR3Jh/btsCv4So1du/SVGZ5IlCmiOZy3FSIRQh3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYR3Jh%2FbtsCv4So1du%2FSVGZ5IlCmiOZy3FSIRQh3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1001&quot; height=&quot;572&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;sql 에러&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 DB에 없는 userId를 넣으면 아무런 오류를 리턴해주지 않습니다. 따라서 위의 코드에서 query에 아래의 코드로 수정해줍니다. 즉 err가 발생하면 400에러를 돌려줍니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703318178864&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  conn.query(
                `INSERT INTO channels (name,user_id) VALUES(?,?)`, [name, userId],
                function (err, results, fields) {
                    if(err){
                        console.log(err);
                        return res.status(400).end();
                    }
                    res.status(201).json(results);
                }
            )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 없는 userId를 넣은 경우&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mzW1K/btsCxBQATnC/bTtmp8fVkrrkJsPu0YhjzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mzW1K/btsCxBQATnC/bTtmp8fVkrrkJsPu0YhjzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mzW1K/btsCxBQATnC/bTtmp8fVkrrkJsPu0YhjzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmzW1K%2FbtsCxBQATnC%2FbTtmp8fVkrrkJsPu0YhjzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;847&quot; height=&quot;147&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;채널 전체조회 유효성검사&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;query문안의 if(results.length)이 부분은 사용자의 요청에 대한 내용이 아니기 때문에 if문으로처리했습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703318835354&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//채널 전체 조회 
    .get(
        body('userId').notEmpty().isInt().withMessage('숫자 입력필요')
        ,(req, res) =&amp;gt; {
            const err = validationResult(req)
            if(!err.isEmpty()){
                return res.status(400).json(err.array())
            }
        let { userId } = req.body
        if (userId) {
            conn.query(
                `SELECT * FROM channels WHERE user_id=?`, userId,
                function (err, results, fields) {
                    if(err){
                        console.log(err);
                        return res.status(400).end();
                    }
                    if (results.length) {
                        res.status(200).json(results);
                    }
                    else {
                        notFoundChannel(res)
                    }
                }
            )
        }
        
    })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;채널 개별조회 유효성 검사&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채널 개별조회 api는 param로 url의 id를 받아와서 사용합니다. 따라서 코드 맨위에 param을 추가해주고 사용합니다. param은 notEmpty()즉 비어있지만 않으면 됩니다. 어차피 문자열로 들어올 것이고 그걸 숫자로 변환해주고 있기 때문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1703319355279&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
const { body,param, validationResult } = require('express-validator')
...
//채널개별조회
    .get(
        param('id').notEmpty().withMessage('채널id 필요')
        ,(req, res) =&amp;gt; {
        const err = validationResult(req)
        if(!err.isEmpty()){
            return res.status(400).json(err.array())
        }
        
        let { id } = req.params
        id = Number(id)
        conn.query(
            `SELECT * FROM channels WHERE id=?`, id,
            function (err, results, fields) {
                if(err){
                    console.log(err);
                    return res.status(400).end();
                }
                
                if (results.length) {
                    res.status(200).json(results);
                }
                else {
                    notFoundChannel(res)
                }
            }
        )
    })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;채널명수정(put)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채널수정입니다. param으로 채널의 id를 받아오고 body에 변경하고 싶은 name을 받아왔습니다. 그리고 sql문을 실행하는데 param에 현재 db에 없는 id를 넣은경우도 200처리가 되길래 아래처럼 afftedRows가 0보다 크면 200을 아니면 400처리를 해주었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703320712722&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//채널수정
    .put(
        [param('id').notEmpty().withMessage('채널id 필요'),
        body('name').notEmpty().isString().withMessage('채널명 옲')]
        ,(req, res) =&amp;gt; {
            const err = validationResult(req)

            if(!err.isEmpty()){
                return res.status(400).json(err.array())
            }
        
            let { id } = req.params
            id = Number(id);
            let {name} = req.body

            conn.query(
                `UPDATE channels SET name=? WHERE id=?`,[name ,id],
                function(err,results,fields){
                    if(err){
                        console.log(err)
                        return res.status(400).end();
                    }
                    if(results.affectedRows&amp;gt;0)
                    {
                        res.status(200).json(results);
                    }
                    else{
                        res.status(400).end()
                    }
                }
            )
        })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;채널삭제(delete)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채널삭제 delete입니다. put과 마찬가지로 db에없는 채널id를 받은 경우 200처리하는것을 막아주기 위해 afttedRows를 활용했습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703322189778&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//채널삭제
    .delete(
        param('id').notEmpty().withMessage('채널 id 필요')
        ,(req, res) =&amp;gt; {
            const err = validationResult(req)
            if(!err.isEmpty()){
                return res.status(400).json(err.array())
            }    
            
            let { id } = req.params
            id = Number(id)

            conn.query(
                `DELETE FROM channels WHERE id=?`,id,
                function(err,results,fields){
                    if(err){
                        console.log(err)
                        return res.status(400).end();
                    }
                    if(results.affectedRows&amp;gt;0)
                    {
                        res.status(200).json(results)
                    }
                    else{
                        res.status(400).end()
                    }
                }
            )
    })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db에 있는 채널 id값을 넣은 경우 affectedRows가 0이아닌 변경된 로우의 숫자를 리턴합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;455&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xFVTw/btsCwR7js4t/k5FScklqrLzCmdd6OL5SN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xFVTw/btsCwR7js4t/k5FScklqrLzCmdd6OL5SN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xFVTw/btsCwR7js4t/k5FScklqrLzCmdd6OL5SN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxFVTw%2FbtsCwR7js4t%2Fk5FScklqrLzCmdd6OL5SN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;455&quot; height=&quot;331&quot; data-origin-width=&quot;455&quot; data-origin-height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검사 미들웨어 분리(next)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 모든 api를 보면 validationResult하는 부분이 중복됩니다. 그래서 이부분을 분리해주겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bltzrJ/btsCw9thxAL/S34vsjmDsowlej2upCE3r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bltzrJ/btsCw9thxAL/S34vsjmDsowlej2upCE3r0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bltzrJ/btsCw9thxAL/S34vsjmDsowlej2upCE3r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbltzrJ%2FbtsCw9thxAL%2FS34vsjmDsowlej2upCE3r0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;548&quot; height=&quot;130&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 validate라는 함수를 만들어줍니다. 추가한코드는 else인데 else의 next()함수를 사용하지 않으면 validate함수호출이후에 코드 진행이 안됩니다. 그래서 next를 사용해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1703323229948&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const validate=(req,res,next)=&amp;gt;{
    const err = validationResult(req)
    if(!err.isEmpty()){
        return res.status(400).json(err.array())
    }else{
        return next(); //다음 할일(미들웨어,함수)
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유효성검사를 추가한 최종코드입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;채널&lt;/h2&gt;
&lt;pre id=&quot;code_1703325468626&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//express 모듈 셋팅
const express = require('express')
const router = express.Router()
const conn = require('../mariadb')
const { body,param, validationResult } = require('express-validator')

router.use(express.json())

const validate=(req,res,next)=&amp;gt;{
    const err = validationResult(req)
    if(err.isEmpty()){
        return next(); //다음 할일(미들웨어,함수)
    }else{
        return res.status(400).json(err.array())
    }
}

router
    .route('/')
    //채널 전체 조회 
    .get(
        [
            body('userId').notEmpty().isInt().withMessage('숫자 입력필요'),
            validate
        ]
        ,(req, res,next) =&amp;gt; {
        let { userId } = req.body
        if (userId) {
            conn.query(
                `SELECT * FROM channels WHERE user_id=?`, userId,
                function (err, results, fields) {
                    if(err){
                        console.log(err);
                        return res.status(400).end();
                    }
                    
                    if (results.length) {
                        res.status(200).json(results);
                    }
                    else {
                        notFoundChannel(res)
                    }
                }
            )
        }
        
    })
    //채널 개별 생성
    .post(
        [body('userId').notEmpty().isInt().withMessage('숫자 입력필요'),
        body('name').notEmpty().isString().withMessage('문자 입력필요'),
        validate]
        , (req, res) =&amp;gt; {
            const { name, userId } = req.body
            conn.query(
                `INSERT INTO channels (name,user_id) VALUES(?,?)`, [name, userId],
                function (err, results, fields) {
                    if(err){
                        console.log(err);
                        return res.status(400).end();
                    }
                    res.status(201).json(results);
                }
            )
        })

router
    .route('/:id')
    //채널개별조회
    .get(
        [param('id').notEmpty().withMessage('채널id 필요'),
        validate]
        ,(req, res) =&amp;gt; {
        let { id } = req.params
        id = Number(id)
        conn.query(
            `SELECT * FROM channels WHERE id=?`, id,
            function (err, results, fields) {
                if(err){
                    console.log(err);
                    return res.status(400).end();
                }
                if (results.length) {
                    res.status(200).json(results);
                }
                else {
                    notFoundChannel(res)
                }
            }
        )
    })
    //채널삭제
    .delete(
        [param('id').notEmpty().withMessage('채널 id 필요'),
        validate]
        ,(req, res) =&amp;gt; {
            let { id } = req.params
            id = Number(id)

            conn.query(
                `DELETE FROM channels WHERE id=?`,id,
                function(err,results,fields){
                    if(err){
                        console.log(err)
                        return res.status(400).end();
                    }
                    if(results.affectedRows&amp;gt;0)
                    {
                        res.status(200).json(results)
                    }
                    else{
                        res.status(400).end()
                    }
                }
            )
    })
    //채널수정
    .put(
        [param('id').notEmpty().withMessage('채널id 필요'),
        body('name').notEmpty().isString().withMessage('채널명 옲'),
        validate]
        ,(req, res) =&amp;gt; {
            let { id } = req.params
            id = Number(id);
            let {name} = req.body

            conn.query(
                `UPDATE channels SET name=? WHERE id=?`,[name ,id],
                function(err,results,fields){
                    if(err){
                        console.log(err)
                        return res.status(400).end();
                    }
                    if(results.affectedRows&amp;gt;0)
                    {
                        res.status(200).json(results);
                    }
                    else{
                        res.status(400).end()
                    }
                }
            )
        })

function notFoundChannel(res) {
    res.status(404).json({
        message: `채널 정보를 찾을 수 없습니다`
    })
}

module.exports = router&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회원&lt;/h2&gt;
&lt;pre id=&quot;code_1703325478872&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require('express')
const router = express.Router()
const conn = require('../mariadb')
const {body, param, validationResult} = require('express-validator')

router.use(express.json())

const validate = (req, res, next) =&amp;gt; {
    const err = validationResult(req)
    if (err.isEmpty()) {
        return next(); //다음 할일(미들웨어,함수)
    } else {
        return res.status(400).json(err.array())
    }
}


//로그인
router.post('/login',
    [
        body('email').notEmpty().isEmail().withMessage('이메일 확인필요'),
        body('password').notEmpty().isString().withMessage('비밀번호 확인필요'),
        validate
    ]
    , (req, res) =&amp;gt; {
        const { email, password } = req.body
        conn.query(
            `SELECT * FROM users WHERE email=?`, email,
            function (err, results, fields) {
                if (err) {
                    console.log(err);
                    return res.status(400).end();
                }
                var loginUser = results[0];

                if (loginUser &amp;amp;&amp;amp; loginUser.password == password) {
                    res.status(200).json({
                        message: `${loginUser.name}님 로그인 되었습니다.`
                    })
                }
                else {
                    res.status(404).json({
                        message: &quot;이메일 또는 비밀번호가 틀렸습니다.&quot;
                    })
                }
            }
        )
    })

//회원가입
router.post('/join',
    [
        body('email').notEmpty().isEmail().withMessage('이메일 확인필요'),
        body('name').notEmpty().isString().withMessage('이름확인필요'),
        body('password').notEmpty().isString().withMessage('비밀번호 확인필요'),
        body('contact').notEmpty().isString().withMessage('연락처확인필요'),
        validate
    ]
    , (req, res) =&amp;gt; {
            const { email, name, password, contact } = req.body
            conn.query(
                `INSERT INTO users (email,name,password,contact) VALUES(?,?,?,?)`, [email, name, password, contact],
                function (err, results, fields) {
                    if (err) {
                        console.log(err);
                        return res.status(400).end();
                    }
                    res.status(201).json(results);
                }
            )
    })

router
    .route('/users')
    .get(
        [
            body('email').notEmpty().isEmail().withMessage('이메일 확인필요'),
            validate
        ]
        , (req, res) =&amp;gt; {
            let { email } = req.body;
            console.log(email);
            conn.query(
                `SELECT * FROM users WHERE email=?`, email,
                function (err, results, fields) {
                    if (err) {
                        console.log(err);
                        return res.status(400).end();
                    }

                    if (results.length) {
                        res.status(200).json(results);
                    }
                    else {
                        notFoundUsers(res)
                    }
                }
            )

        })
    .delete(
        [
            body('email').notEmpty().isEmail().withMessage('이메일 확인필요'),
            validate
        ]
        , (req, res) =&amp;gt; {
            let { email } = req.body;
            console.log(email);
            conn.query(
                `DELETE  FROM users WHERE email=?`, email,
                function (err, results, fields) {
                    if (err) {
                        console.log(err);
                        return res.status(400).end();
                    }

                    if (results.length) {
                        res.status(200).json(results);
                    }
                    else {
                        notFoundUsers(res)
                    }
                }
            )
        })
function notFoundUsers(res) {
    res.status(404).json({
        message: `회원 정보를 찾을 수 없습니다`
    })
}
module.exports = router&lt;/code&gt;&lt;/pre&gt;</description>
      <category>백엔드/node.js(express)</category>
      <author>최맹수</author>
      <guid isPermaLink="true">https://myeongsu0257.tistory.com/230</guid>
      <comments>https://myeongsu0257.tistory.com/230#entry230comment</comments>
      <pubDate>Sat, 23 Dec 2023 18:58:04 +0900</pubDate>
    </item>
  </channel>
</rss>