일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- unity
- c#
- AWS
- 백준
- react
- OAuth2.0
- 코딩
- 게임
- express
- 스프링부트
- Python
- IntelliJ
- frontend
- bcrypt
- 파이썬
- oAuth
- netlify
- 게임개발
- 프로그래밍
- springboot
- node.js
- 스프링
- jwt
- RiotAPI
- 유니티
- 깃
- MongoDB
- 백엔드
- JSON
- spring
- Today
- Total
Unwound Developer
Arcane 프로젝트 - 서버 구조 본문
위 사진은 서버 쪽 구조입니다.
app.js가 서버의 기본이 되는 파일입니다.
// app.js
import express from "express";
import { connectDB } from "./db/db.js";
import summonersRouter from "./router/summoners.js";
import mypageRouter from "./router/mypage.js";
import authRouter from "./router/auth.js";
const app = express();
// 회원가입 & 로그인 & 유저관련 디비 설정
app.use("/auth", authRouter);
// 글 작성
app.use("/post", postRouter);
// 유저 전적 검색
app.use("/api/summoners", summonersRouter);
// 내 정보
app.use("/api/mypage", mypageRouter);
// 위의 라우터 모두 충족하지 않을경우
app.use((req, res, next) => {
res.sendStatus(404);
});
// 에러 발생시
app.use((error, req, res, next) => {
console.error("error: " + error);
res.sendStatus(500);
});
connectDB() // connectDB는 mongoDB와 연결하는 함수
.then(() => {
console.log("db 연결 완료");
})
.catch((err) => {
console.log(err);
});
서버는 Routing을 통해 개발했기때문에, app.js는 코드가 간단합니다. app.use()를 통해 CRUD로 요청이 들어오면 해당 라우터로 보냅니다.
db.js 입니다.
// db.js
import Mongoose from "mongoose";
import { config } from "../config.js";
export async function connectDB() {
// mongoose 사용
// Mongoose 6.X 이상 버전을 사용하실때는 connect 두번째 인자로 따로 options을 명시해 주지 않아도된다
return Mongoose.connect(config.db.host);
}
저 return 문 하나로 Mongodb와 연결된거에요. 정말 간단합니다. 개발 과정에서 이럴때, no SQL 데이터베이스의 장점을 느꼈습니다. config에는 db호스트 포트번호같은 필요한 상수들을 저장해놓았습니다.
// auth.js
import express from "express";
import * as authController from "../controller/auth.js";
const router = express.Router();
router.get("/", authController.me);
router.get("/info", authController.IsExistFromClient);
router.get("/login", authController.login);
router.get("/check", authController.checkMarking);
router.get("/exist", authController.IsExistFromClient);
router.post("/signup", authController.signup);
router.post("/marking", authController.bookMarking);
export default router;
app.js에서 "/auth"로 요청이 들어왔을 때 보내는 auth.js 라우터 입니다.
다른 라우터들도 전부 동일한 구조를 가지고있는데, get요청인지 post요청인지 delete요청인지 등 요청에 맞춰서 다시 컨트롤러로 보내줍니다.
// summoner.js
import axios from "axios";
import * as userRepository from "../data/summoners.js";
export async function getRiotApi(req, res, next) {
const result = await getApi(req.headers.link);
res.status(200).json(result.data);
}
/**검색한 소환사가 디비에 이미 있는지 여부를 반환 (return boolean) */
export async function isSummoner(req, res, next) {
const summonerName = decodeURIComponent(req.headers.summonername);
const summoner = await userRepository.findBySummonerName(summonerName);
if (summoner) return res.status(200).json(true);
return res.status(200).json(false);
}
/**처음 검색한 소환사의 정보를 디비에 저장해줌 (return json) */
export async function saveSummonerInfo(req, res, next) {
const { summoner, rankData, matchHistoryList } = req.body;
let soloRank = {
queueType: "Unranked",
tier: "Unranked",
rank: "",
leaguePoints: 0,
wins: 0,
losses: 0,
};
let flexRank = {
queueType: "Unranked",
tier: "Unranked",
rank: "",
leaguePoints: 0,
wins: 0,
losses: 0,
};
for (const r in rankData) {
if (rankData[r].queueType === "RANKED_SOLO_5x5") soloRank = rankData[r];
else if (rankData[r].queueType === "RANKED_FLEX_SR")
flexRank = rankData[r];
}
const mList = await saveMatchHistroy(matchHistoryList);
const summId = await userRepository.createSummoner({
summonerName: summoner.name,
profileIconId: summoner.profileIconId,
level: summoner.summonerLevel,
soloRankQueueType: soloRank.queueType,
soloRankTier: soloRank.tier,
soloRankRank: soloRank.rank,
soloRankLP: soloRank.leaguePoints,
soloRankWinNum: soloRank.wins,
soloRankLoseNum: soloRank.losses,
flexRankQueueType: flexRank.queueType,
flexRankTier: flexRank.tier,
flexRankRank: flexRank.rank,
flexRankLP: flexRank.leaguePoints,
flexRankWinNum: flexRank.wins,
flexRankLoseNum: flexRank.losses,
matchList: mList,
});
const summ = await userRepository.findById(summId);
return res.status(201).json(summ);
}
이번에는 summoner.js의 컨트롤러 부분입니다. 만약 클라이언트에서 보낸 요청이 데이터베이스와의 상호작용을 요구한다면, 컨트롤러에서 다시 data 부분으로 보내줍니다. data는 mongoose를 통한 데이터베이스(MongoDB)와의 통신을 담당합니다. userRepository를 import한 부분이 data의 함수를 호출하기 위함입니다.
Arcane 프로젝트에서 'League of Legends' 유저의 전적검색은 간단히 다음처럼 이루어집니다.
클라이언트가 특정 닉네임을 검색하면 그 닉네임을 서버로 특정 get요청 (/api/summoners/isIn)과 함께 보냅니다.
서버는 summoners요청인것을 확인하면 summoners.js 라우터로 요청을 보내고, 라우터는 isIn을 보고 다시금 summoner.js 컨트롤러로 요청을 보냅니다. 이미 전적검색을 한 적이 있는 유저라면, DB에 그 정보를 저장했기 떄문에, Riot API에 요청할 필요가 없습니다. 이는 검색의 속도 향상을 위해서입니다. API 에 전적 정보를 요청하면 10초가량 걸리지만, Riot API에 데이터를 요청할 때 마다, 그 데이터를 DB에 저장하고 그 데이터를 불러오는것은 1초도 안걸립니다. 데이터가 DB에 존재함을 data부분에서 알아내면 Riot API에 직접 데이터를 요청할지, DB에서 데이터를 제공할지를 정하게 되는것입니다.
// data.js
import { Summoner, MatchHistory } from "../model/schema.js";
// 사용자 이름으로 찾기
export async function findBySummonerName(summonerName) {
return Summoner.findOne({ summonerName });
}
// 아이디로 찾기
export async function findById(id) {
return Summoner.findById(id);
}
// 아이디로 매치 찾기
export async function findByMatchId(id) {
return MatchHistory.findById(id);
}
data구조에서 summoner.js의 일부분입니다. findOne등의 함수는 Mongoose에서 제공하는 메소드 입니다. MongoDB 데이터베이스에서 값을 가져옵니다.
Arcane 서버의 구조는 모두 이렇게 이루어져있습니다. 클라이언트의 요청, 서버는 요청을 다시 라우터로 보내고, 라우터는 컨트롤러, 데이터베이스의 데이터가 필요하면 다시 data부분으로 요청을 보내는 것.
다음엔 클라이언트 구조를 작성해보겠습니다.
'Web > Node.js' 카테고리의 다른 글
Arcane 프로젝트 - netlify.toml (0) | 2022.11.14 |
---|---|
Arcane 프로젝트 - MongoDB와 Mongoose (0) | 2022.11.14 |
Arcane 프로젝트 - Axios 비동기 통신 (0) | 2022.11.13 |
Arcane - 클라이언트 구조 (0) | 2022.11.13 |
Node.js와 Riot API를 활용한 프로젝트 Arcane (0) | 2022.11.11 |