์ ๋ฒ
Wepleshop
1์ฐจ ํ๋ก์ ํธ๋ฅผ ์ํ ํ ๋๋ฒ์งธ ํ ํ๋ก์ ํธ์ ๋๋ค. 1์ฐจ ํ๋ก์ ํธ์์์ ๋ถ์กฑํ๋ ์ ๊ณผ ๋ ๋ฐฐ์ฐ๊ณ ์ถ์๋ ์ ์ ๊นจ๋ฌ์๊ณ , ์ด๋ฅผ ๋ณด์ํ๋ ๋ฐฉํฅ์ผ๋ก ํ๋ก์ ํธ๋ฅผ ์งํํด๋ณด์ ํ๋ ์๊ฐ์ผ๋ก ์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์งํํ์์ต๋๋ค.
์ด๋ฒ ํด๋ก ์ฝ๋ฉ ํ๋ก์ ํธ์ ์ฃผ์ ๋Nike
์น์ฌ์ดํธ ์ ๋๋ค.
๋์ดํค๋ ์คํฌ์ธ ์จ์ด ๋ธ๋๋ ์ค์์ ๊ฐ์ฅ ์ ๋ช ํ ๋ธ๋๋๋ผ๊ณ ํด๋ ๊ณผ์ธ์ด ์๋ ๊ฒ์ ๋๋ค. ํ์ ๋์ดํค๋ฅผ ์ข์ํ๋ ์ ์ ํฌ์ค๋์ ์ํดNike
๊ฐ ์ฑํ๋์๊ณ , 1์ฐจ ํ๋ก์ ํธ์ธ ๋งํ์ต ํด๋ก ์ฝ๋ฉ๊ณผ ๊ฐ์ ์น ์ผํ๋ชฐ์ด๊ธฐ์ ์งํ ๋ฐฉํฅ์ด ๋น์ทํ์ต๋๋ค. ์ฐจ์ด์ ์ ๋ฆฌ์คํธ ํ์ด์ง ๋ด์์์ ๋ค์ํํํฐ๋ง
SNKRS ํ์ด์ง์์์ ์๋ชจ ๋ฐ ์ถ์ฒจ
๊ธฐ๋ฅ์ด์์ผ๋ฉฐ, ๋ฐฑ์๋๋ฅผ ๊ตฌํํ ์ ์ ์คํ๋์ ๊ฐ์ ์ด๋ฌํ ๋ฉ์ธ ๊ธฐ๋ฅ(ํํฐ๋ง, SNKRS)์ ํ ๊ฐ์ง์ฉ ๋ถ๋ดํ์ฌ ํ๋ก์ ํธ๋ฅผ ์งํํ์์ต๋๋ค.
๋ฐฑ์๋ : ์ด์คํ ๊น์์ฑ
ํ๋ก ํธ ์๋ : ํฉํฌ์ค, ์ด์ง์
- Front-End : React.js, Sass
- Back-End : Node.js, Express, Prisma, nodemon, JWT, Bcrypt, My SQL, CORS
- Common : RESTful API
- Community Tools : Slack, Zoom, Notion
์ ๋ฒ ํ๋ก์ ํธ๋ ํ๋ก ํธ์๋
์ ๋ฐฑ์๋
๋ฅผ ๊ฐ์ธ์ด ๋ชจ๋ ํ๊ฐ์ง ์ด์์ ๊ตฌํํ๋ฉฐ Fullstack
์ผ๋ก ํ๋ก์ ํธ๋ฅผ ์งํํ์์ต๋๋ค.
1์ฐจ ํ๋ก์ ํธ๋ฅผ ํตํด ํ๋ก ํธ ์๋
์ ๋ฐฑ์๋
์ค ์ด๋ค ๊ฒ์ด ๊ฐ์ ์์ ์๊ฒ ์ ํฉํ์ง๋ฅผ ํ์
๊ฐ๋ฅํ์ผ๋ฉฐ, ์ด๋ฒ 2์ฐจ ํ๋ก์ ํธ๋ ํ๋ก ํธ ์๋
์ ๋ฐฑ์๋
์ ์ญํ ์ ๋ช
ํํ ๋๋์ด, ํ๋ก์ ํธ๋ฅผ ์งํํ์์ต๋๋ค.
๋ฐฑ์๋ ์ธ์ ์ญํ ์ด ์์๋ค๋ฉด, ํ๋ก ํธ ์๋๋จ์์ ์ฌ์ฉํ ์ด๋ฏธ์ง(์ ํ ์ด๋ฏธ์ง)๋ ์์กฐ๋ก์ด ์์
์ ์ํด ๋ฐฑ์๋์ธ ์ ์ ์คํ๋์ด ์๋ฃ ์์ง์ ์งํํ์์ต๋๋ค.
๊ทธ ์ธ์ ํ๋ก ํธ ์๋์ ๋ฐฑ์๋์ ์์
๋ค์ด ๋ชจ๋ ์ญํ ๋ถ๋ด์ ํตํด ์ด๋ฃจ์ด์ก์ต๋๋ค.
๋์ดํค ๊ณต์ ํํ์ด์ง์ ๋๋ค. ํด๋น ํ์ด์ง์ ํฐ ๋ฒ์ฃผ์ธ ๋ฉ์ธ ํ์ด์ง, ๋ฆฌ์คํธ ํ์ด์ง ๋ฐ ํํฐ ๊ธฐ๋ฅ, ๋ํ ์ผ ํ์ด์ง ์ ๋๋ค.
(๋์ดํค ๋ํ ์ผ ํ์ด์ง / ์ถ์ฒ : ๋์ดํค ๊ณต์ ํํ์ด์ง)
(๋์ดํค ๋ฆฌ์คํธ ํ์ด์ง / ์ถ์ฒ : ๋์ดํค ๊ณต์ ํํ์ด์ง)
(๋์ดํค ๋ฆฌ์คํธ ํ์ด์ง - ํํฐ๋ง (1) / ์ถ์ฒ : ๋์ดํค ๊ณต์ ํํ์ด์ง)
(๋์ดํค ๋ฆฌ์คํธ ํ์ด์ง - ํํฐ๋ง (2) / ์ถ์ฒ : ๋์ดํค ๊ณต์ ํํ์ด์ง)
(๋์ดํค ๋ํ ์ผ ํ์ด์ง / ์ถ์ฒ : ๋์ดํค ๊ณต์ ํํ์ด์ง)
1์ฐจ ํ๋ก์ ํธ์์๋ ๋ก๊ทธ์ธ๊ณผ ํ์๊ฐ์ API๋ฅผ ์์ฒด์ ์ผ๋ก ๊ฐ๋ฐํ์ฌ ๋ก๊ทธ์ธ ํ์๊ฐ์ ๊ธฐ๋ฅ๊ณผ ๊ทธ์ ๋ฐ๋ฅธ ์ธ๊ฐ ๋ถ์ฌ๋ฅผ ๊ตฌํํ์๋ค๋ฉด, ์ด๋ฒ์๋ ์์ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ฐ๋์์ผ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ๊ตฌํํด์ฃผ๋๋ก ์งํํ์๋ค. ์ฌ์ค ์ด ๋ถ๋ถ์ ๊ตฌ๊ธ๋ง๊ณผ ์ ํ๋ธ, Kakao Developers ์ฌ์ดํธ์ ๊ณต์ ๋ฌธ์๋ฅผ ํตํด ๊ณต๋ถ๋ฅผ ํด๋ณธ ๊ฒฐ๊ณผ ๋ง์ ์๋ฃ๋ค๊ณผ ๋ฐฉ๋ฒ๋ค์ด ์์๊ณ , ์ฝ๋ ์์ฒด๊ฐ ์ด๋ ค์ด ๊ฑด ์๋์์ง๋ง, ์ด๋ฅผ ์ ์ฉ์ํค๋ ๊ณผ์ ๊ณผ ๊ทธ๊ฒ๋ค์ ์ดํดํ๋๋ฐ ๋ง์ ์๊ฐ์ ๋ณด๋๋ค.
์นด์นด์ค ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ฐ๋ํ๋ ๋ฐฉ๋ฒ์ ๋ค์ํ๋ค. ์์ธํ ๋ด์ฉ์ ๊ณต์ ๋ฌธ์์ ๋ง์ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ์์ผ๋ฉฐ, ํ์๋ REST API KEY๋ฅผ ํ์ฉํ์ฌ ํ๋ก ํธ์๋์์ Access Token๊ณผ Refresh Token์ ๋ฐ๊ธ ๋ฐ์ ์ ์ ์ ๋ณด๋ฅผ ๋ฐ์์ค๋๋ก ์ฒ๋ฆฌํด์ฃผ์๋ค. ๋ฐ์์จ ์ ์ ์ ๋ณด๋ฅผ ๋ฐฑ์๋์ ๋๊ฒจ์ฃผ์ด, ํด๋น ์ ์ ์ ๋ณด์ DB ๋ด์ ์ ๋ฌด๋ฅผ ํตํด ์ดํ์ ์ธ์ฆ ์ธ๊ฐ ๊ณผ์ ์ ์ฒ๋ฆฌํด์ฃผ๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์๋ค. (ํ๋, ๋ ์ข์ ๋ฐฉ๋ฒ์ ํ๋ก ํธ์๋์์๋ Access Token์ ๋ฐ์ ์ด๋ฅผ Token์ผ๋ก ๋ฐฑ์๋๋ก ๋๊ฒจ์ฃผ๋ ๊ฒ์์ ๋ฉ์ถ๊ณ , ๋ฐฑ์๋์์ ๋๋จธ์ง ๊ณผ์ ์ ์ฒ๋ฆฌํ๋ ๊ฒ์ด ๋ฐ๋์งํ๋ค๊ณ ํ๋ค. ์ด๋ฌํ ์์ ๋ํ ์ถํ ๋ค์ ์งํํ์์ผ๋ฉฐ ์ดํ ํฌ์คํ ์์ ๋ค๋ฃฐ ์์ ์ ๋๋ค.)
Auth.js
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import qs from 'qs';
import { CLIENT_SECRET, REDIRECT_URI, REST_API_KEY } from '../../config';
const Auth = () => {
// calllback์ผ๋ก ๋ฐ์ ์ธ๊ฐ์ฝ๋
// ๋ก๊ทธ์ธ ํ์ URL ๋ด์ code get
const code = new URL(window.location.href).searchParams.get('code');
const navigate = useNavigate();
// ๊ณ ์ REST API KEY , REDIRECT URI, CLIENT SECRET KEY, Code ๋ฅผ ํตํด Access Token ๋ฐํ
const getToken = async () => {
const payload = qs.stringify({
grant_type: 'authorization_code',
client_id: REST_API_KEY,
redirect_uri: REDIRECT_URI,
code: code,
client_secret: CLIENT_SECRET,
});
try {
// access token ๊ฐ์ ธ์ค๊ธฐ
const res = await axios.post('https://kauth.kakao.com/oauth/token', payload);
// Kakao Javascript SDK ์ด๊ธฐํ
window.Kakao.init(REST_API_KEY);
// access token ์ค์
window.Kakao.Auth.setAccessToken(res.data.access_token);
navigate('/login');
} catch (err) {
console.log(err);
}
};
useEffect(() => {
getToken();
}, []);
return null;
};
// Kakao SDK API๋ฅผ ์ด์ฉํด ์ฌ์ฉ์ ์ ๋ณด ํ๋
export default Auth;
์ก์ธ์ค ํ ํฐ์ ๋ฐ์์จ ํ
useNavigate()
ํ ์ ํตํด/login
ํ์ด์ง์์ ์ ์ ์ ๋ณด๋ฅผ ๋ฐ์์ด (ํ๋จ ์ฝ๋ ์ฐธ์กฐ)
Login.js
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { GET_SIGN_IN_API } from '../../config';
function Login() {
const [email, setEmail] = useState();
const [name, setName] = useState();
const [token, setToken] = useState();
const navigate = useNavigate();
// v2/user/me ์ ํด๋น ์ ์ ์ ์ ๋ณด(์ด๋ฉ์ผ, ๋๋ค์, ํ๋กํ ์ฌ์ง ๋ฑ์ ์ ๋ณด)๋ฅผ ๋ฐํ
const getLocal = async () => {
// Kakao SDK API๋ฅผ ์ด์ฉํด ์ฌ์ฉ์ ์ ๋ณด ํ๋
let data = await window.Kakao.API.request({
url: '/v2/user/me',
});
// ์ ์ ์ ๋ณด(์ด๋ฉ์ผ, ๋๋ค์) state์ ์ ์ฅ
setEmail(data.kakao_account.email);
setName(data.properties.nickname);
alert(`${data.properties.nickname}๋ ๋ฐ๊ฐ์ต๋๋ค!`);
};
}
์ ๋ฐฉ์์ ํ๋ก ํธ์๋ ๋ด์์ ์์ฒด์ ์ผ๋ก code > Access Token > ์ ์ ์ ๋ณด๋ฅผ ์์ฒด์ ์ผ๋ก ๋ฐํํ๋ ๊ณผ์ ์ด๋ค. ์ฑ๊ณต์ ์ผ๋ก ์ ์ ์ ๋ณด๋ฅผ ๋ฐํํ ์ ์์ง๋ง, ํ์ฅ์ฑ์ ๊ณ ๋ คํ์ ๋ ์ ํฉํ์ง ์๋ค. ์ด์ ๋ ๋ฐฑ์๋์์ Access Token์ ๋ฐ์ ์ฒ๋ฆฌํ๋ ์์ ์ด ์๋๊ธฐ ๋๋ฌธ์, ์ธ๊ฐ ๋ถ์ฌ ๊ณผ์ ์ ์ ํฉํ๊ฒ ์ฒ๋ฆฌํ์ง ๋ชปํ๊ณ , ํ์ฅ์ฑ์ ๊ณ ๋ คํ์ง ๋ชปํ ๋ถ๋ถ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋ ๊ธฐ์, ํ๋ก ํธ์๋์์๋ Access Token๋ฐํ ๊ณผ์ ๊น์ง๋ง ๊ฑฐ์น๊ณ , Access Token์ ๋ฐฑ์๋์ ๋๊ฒจ์ฃผ๊ณ , ๋ฐฑ์๋์์ Access Token์ ํตํด, ๋ก๊ทธ์ธ, ์ ์ ๋ฑ๋ก, ๋ฉค๋ฒ์ฝ ์ธ๊ฐ ๊ธฐ๋ฅ์ ์ฒ๋ฆฌํ์ฃผ๋๋ก ์ฝ๋๋ฅผ ์์ ํ์๋ค.
NewAuth.js
(ํ๋ก ํธ ์๋์์ Access Token์ ๋ณด๋ด์ฃผ๋ ์์
)
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import qs from 'qs';
import { CLIENT_SECRET, REDIRECT_URI, REST_API_KEY, GET_SIGN_IN_API } from '../../config';
const Auth = () => {
const navigate = useNavigate();
const [token, setToken] = useState();
// calllback์ผ๋ก ๋ฐ์ ์ธ๊ฐ์ฝ๋
const code = new URL(window.location.href).searchParams.get('code');
useEffect(() => {
try {
axios
.post(
'https://kauth.kakao.com/oauth/token',
qs.stringify({
grant_type: 'authorization_code',
client_id: REST_API_KEY,
redirect_uri: REDIRECT_URI,
code: code,
client_secret: CLIENT_SECRET,
})
)
.then(res =>
axios
.get(GET_SIGN_IN_API, { headers: { accessToken: res.data.access_token } })
.then(res => {
setToken(res.data.token);
localStorage.setItem('token', token);
})
)
.then(alert(`ํ์ํฉ๋๋ค!`))
.then(navigate('/'));
} catch (err) {
alert('๋ก๊ทธ์ธ ์คํจ');
}
}, []);
return null;
};
export default Auth;
์ด์ ์ฝ๋์ ๋ค๋ฅธ ์ ์ Access Token ์
SignIn API
์ ํค๋๋ฅผ ํตํด ๋ณด๋ด์ฃผ์ด get์ ํตํด ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค๋ ์ ์ด๋ค. ๊ทธ ํSignIn API
๋ฅผ ํตํด ๋ฐ์์จ ์ธ๊ฐ Token์ ํตํด ์ธ์ฆ๊ณผ ์ธ๊ฐ ์ฒ๋ฆฌ๋ฅผ ํ๋ก ํธ์๋์์ ๊ตฌํํ๋ค.
userController.js
import { userServices } from '../services';
const signIn = async (req, res) => {
try {
const accessToken = req.headers.accesstoken;
if (!accessToken) {
return res.status(401).json({ message: 'INVALID_KAKAO_TOKEN' });
}
const token = await userServices.signIn(accessToken);
return res.status(201).json({ message: 'LOGIN_SUCCESS', token });
} catch (err) {
console.log(err);
return res.status(500).json({ message: err.message });
}
};
userServices.js
import { userDao } from '../models';
import token from '../utils/token';
import axios from 'axios';
const signIn = async accessToken => {
// accessToken์ผ๋ก kakao API์ ์ ๊ทผํ์ฌ ์ฌ์ฉ์ ์ ๋ณด ์ทจ๋
const user = await axios.get('https://kapi.kakao.com/v2/user/me', {
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-type': 'application/x-www-form-urlencoded;charset=utf-8',
},
});
// ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ ์๋ฌ ์ฒ๋ฆฌ
if (!user.data) {
const err = new Error('INVALID_USER');
err.statusCode = 400;
throw err;
}
const data = user.data;
// ๋ฐ์ดํฐ ๋ฒ ์ด์ค ๋ด์ ํด๋น ์ด๋ฉ์ผ์ ์ ์ ์ ๋ณด๊ฐ ์๋์ง ํ์ธ
const isExist = await userDao.isExistEmail(data.kakao_account.email);
let userId;
// ํด๋น ์ ์ ์ ๋ณด๊ฐ ์ด๋ฏธ ์์ ๊ฒฝ์ฐ, ํด๋น userId์ ํ ํฐ์ ๋ฐํ
if (isExist) {
userId = await userDao.getUserId(data.kakao_account.email);
return token.signToken(userId);
}
// ํด๋น ์ ์ ์ ๋ณด๊ฐ ์์ ๊ฒฝ์ฐ, ํ์ ์ ๋ณด ์์ฑ
await userDao.createUser(data.kakao_account.email, data.properties.nickname);
// ์์ฑ๋ ์ ์ ์ userId์ ํ ํฐ ๋ฐํ
userId = await userDao.getUserId(data.kakao_account.email);
const signToken = token.signToken(userId);
return signToken;
};
userDao.js
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// email์ ํตํด userId ๋ฐํ
const getUserId = async email => {
const createData = await prisma.$queryRaw`
SELECT
id
FROM
users
WHERE
email=${email};
`;
return createData;
};
// email, name ์ ํตํ ํ์ ๊ฐ์
(์ ์ ๋ฑ๋ก)
const createUser = async (email, name) => {
const createData = await prisma.$queryRaw`
INSERT INTO
users (email, name)
VALUES
(${email}, ${name});
`;
return createData;
};
// ํด๋น email์ ์ ์ ์ ๋ณด์ ์ค๋ณต ์ฌ๋ถ ํ์
const isExistEmail = async email => {
const [user] = await prisma.$queryRaw`
SELECT
email,name
FROM
users
WHERE
email = ${email};
`;
return user;
};
ํ๋ก ํธ ์๋์์์ ์นด์นด์ค ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ๋ฌธ์ ์ ์ ํ์ ํ๊ณ , ๋ฐฑ์๋์์ Access Token์ ํ์ฉํด ๋ก๊ทธ์ธ ๋ฐ ์ ์ ๋ฑ๋ก์์ ํ์ํ ์ ์ ์ ๋ณด๋ฅผ axios์ kakao developers ์ ์๋ฃ๋ฅผ ์ฐธ๊ณ ํ์ฌ ๋ฐํํ๋ API๋ฅผ ๊ตฌํํ์๊ณ , ๋ฐํํ ์ ์ ์ ๋ณด๋ฅผ ํตํด User์ ๊ด๋ จ๋ ๋ชจ๋ API (๋ก๊ทธ์ธ, ํ์๊ฐ์ , ๋ฉค๋ฒ์ฝ ์ธ๊ฐ ๊ตฌํ, ๋ฆฌ๋ทฐ ๋ฑ๋ก, ๋ฆฌ๋ทฐ ํ๊ท ๋ฐํ)๋ฅผ ๊ตฌํํ ์ ์๊ฒ ๋์์ต๋๋ค. ์ด๊ธฐ๋ถํฐ ํ์ฅ์ฑ๊ณผ ๋ณ๊ฒฝ ์ฉ์ด์ฑ์ ์ฐ์ ์ ์ผ๋ก ๊ณ ๋ คํด์ผ๋๋ค๋ ํผ๋๋ฐฑ์ด ๋์๋ ์์ ์ด์์ต๋๋ค.