목차
Tic Tac Toe 서버 만들기
25.03.04
프로그램 설치
>> Node.js
: JavaScript를 사용하기 위해 --> 설치 완료 후, 명령 프롬프트에 'node'라고 치면 설치가 됐는지 확인할 수 있다.
Node.js — 어디서든 JavaScript를 실행하세요
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
nodejs.org
>> Postman
: 설치 및 로그인 --> 회원가입
https://www.postman.com/downloads/
Download Postman | Get Started for Free
Try Postman for free! Join 35 million developers who rely on Postman, the collaboration platform for API development. Create better APIs—faster.
www.postman.com
>> MongoDB
https://www.mongodb.com/try/download/community
Try MongoDB Community Edition
Try MongoDB Community Edition on premise non-relational database including the Community Server and Community Kubernetes Operator for your next big project!
www.mongodb.com
--> 설치할 때, 아래 사진과 같이 Complete 선택, 이후로는 Next만 눌러 설치
--> 이후 Name만 작성하여 Save&Connect
>> SocketIOUnity
: Unity를 이용하여 설치할 예정
>> ParrelSync
: Unity를 이용하여 설치할 예정
Express 서버 만들기
>> cmd에서 설치하고자 하는 경로로 이동 후, 명령어 입력
npx express-generator --view=pug projectName
--> 이후 프로젝트가 생성된 경로로 이동 (cd tictactoe-server)
>> 라이브러리 설치
: VisualStudio Code or Cursor에서 생성된 프로젝트 폴더 열기
--> Open Folder로 프로젝트 폴더 열어서 Package.json 파일 열기
>> cmd에서 프로젝트 경로로 이동하거나 Ctrl + ` 으로 Terminal을 열어서 명령어 입력
npm install
※ 에러
: 나온 경로로 따라 들어가서 'npm.ps1' 파일을 삭제한 후, 다시 명령어 실행
>> 이후 아래 명령어로 서버 시작
--> 연결 테스트 후, Ctrl + C 로 작업 중단
npm start
└ 서버 연결 테스트
>> 브라우저
: 인터넷의 주소창에 아래와 같이 입력하여 접속하면 Express 서버가 정상적으로 작동하는지 확인 가능
localhost:3000
heroku 프로그램..?
>> Postman으로 테스트
: 서버를 시작한 뒤, 테스트
--> 아래에 respond with a resource가 나오면 성공 (routes/users.js 파일에 있는대로 나온 것)
└ MongoDB에 연결
: 아래 명령어를 통해 필요한 라이브러리 추가 설치 --> 서버를 가동 중이었다면 작업 중단 후 설치하기
npm install bcrypt, express-session, session-file-store, socket.io, uuid, mongodb
※ 혹시 설치에 에러가 걸린다면 아래와 같이 ',' 를 쓰지말고 띄워쓰기로만 구분
npm install bcrypt express-session session-file-store socket.io uuid mongodb
>> app.js 파일에 코드 추가
var mongodb = require('mongodb');
var MongoClient = mongodb.MongoClient;
// MongoDB 추가
async function connectDB() {
var databaseURL = "mongodb://localhost:27017/tictactoe";
try {
const database = await MongoClient.connect(databaseURL, {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log("DB 연결 완료 : " + databaseURL);
app.set('database', database.db('tictactoe'));
// 연결 종료 처리
process.on("SIGINT", async () => {
await database.close();
console.log("DB 연결 종료");
process.exit(0);
});
} catch (err) {
console.error("DB 연결 실패 : " + err);
process.exit(1);
}
}
connectDB().catch(err => {
console.error("초기 DB 연결 실패 : " + err);
process.exit(1);
});
※ MongoDB Compass 프로그램에서 URl 확인
※ 추가한 전체 코드 (app.js)
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var mongodb = require('mongodb'); // 추가
var MongoClient = mongodb.MongoClient; // 추가
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
const req = require('express/lib/request');
var app = express();
// MongoDB 추가
async function connectDB() {
var databaseURL = "mongodb://localhost:27017/tictactoe";
try {
const database = await MongoClient.connect(databaseURL, {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log("DB 연결 완료 : " + databaseURL);
app.set('database', database.db('tictactoe'));
// 연결 종료 처리
process.on("SIGINT", async () => {
await database.close();
console.log("DB 연결 종료");
process.exit(0);
});
} catch (err) {
console.error("DB 연결 실패 : " + err);
process.exit(1);
}
}
connectDB().catch(err => {
console.error("초기 DB 연결 실패 : " + err);
process.exit(1);
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
서버 기능 만들기
└ 회원가입 만들기
: users.js에 코드 작성 --> get 방식은 post 방식보다 정보의 노출이 더 발생하기 때문에 사용하지 않고, post 방식을 사용
var bcrypt = require('bcrypt');
var saltrounds = 10;
// 회원가입
router.post('/signup', async function(req, res, next) {
try {
var username = req.body.username;
var password = req.body.password;
var nickname = req.body.nickname;
// 입력값 검증
if (!username || !password || !nickname) {
return res.status(400).send("모든 필드를 입력해주세요");
}
// 사용자 중복 체크
var database = req.app.get('database');
var users = database.collection('users');
const existingUser = await users.findOne({ username: username });
if (existingUser) {
return res.status(409).send("이미 존재하는 사용자입니다");
}
// 비밀번호 암호화
var salt = bcrypt.genSaltSync(saltrounds);
var hash = bcrypt.hashSync(password, salt);
// DB에 저장
await users.insertOne({
username: username,
password: hash, // 해시된 비밀번호 저장
nickname: nickname
});
res.status(201).send("사용자가 성공적으로 생성되었습니다");
} catch (err) {
console.error("사용자 추가 중 오류 발생 :", err);
res.status(500).send("서버 오류가 발생했습니다");
}
});
>> Postman에서 테스트
: Collection 생성 후 테스트
--> POST로 바꿔줘야 정상 작동
--> VisualStudio Code에서 F5를 눌러 서버를 작동한 다음 Send를 눌러 테스트 (또는 Run and Debug)
{
"username": "test01",
"password": "test1234",
"nickname": "Test-01"
}
※ 오타 확인하는 법
google에 'json parser' 을 검색하여 json parser Online에 접속한 다음 코드를 복사 붙여넣기하여 확인 가능
└ 로그인 만들기
- users.js에 코드 작성
var ResponseType = {
INVALID_USERNAME: 0,
INVALID_PASSWORD: 1,
SUCCESS: 2
}
// 로그인
router.post("/signin", async function(req, res, next) {
try {
var username = req.body.username;
var password = req.body.password;
var database = req.app.get('database');
var users = database.collection('users');
// 입력값 검증
if (!username || !password) {
return res.status(400).send("모든 필드를 입력해주세요.");
}
const existingUser = await users.findOne({ username: username });
if (existingUser) {
var compareResult = bcrypt.compareSync(password, existingUser.password);
if (compareResult) {
// 세션에 사용자 정보(로그인 정보) 저장
req.session.isAuthenticated = true;
req.session.userId = existingUser._id.toString();
req.session.username = existingUser.username;
req.session.nickname = existingUser.nickname;
res.json({ result: ResponseType.SUCCESS });
} else {
res.json({ result: ResponseType.INVALID_PASSWORD });
}
} else {
res.json({ result: ResponseType.INVALID_USERNAME });
}
} catch (err) {
console.error("로그인 중 오류 발생.", err);
res.status(500).send("서버 오류가 발생했습니다");
}
});
- app.js에 코드 작성
const session = require('express-session'); // 추가
var fileStore = require('session-file-store')(session); // 추가
// 로그인 만들기
app.use(session({
secret: process.env.SESSION_SECRET || 'session-login',
resave: false,
saveUninitialized: false,
store: new fileStore({
path: './sessions',
ttl: 24 * 60 * 60, // 24시간
reapInterval: 60 * 60 // 1시간
}),
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24시간
}
}));
>> Postman에서 테스트
: VisualStudio Code에서 F5를 눌러 서버를 작동한 다음 Send를 눌러 테스트 (또는 Run and Debug)
{
"username": "test01",
"password": "test1234"
}
└ 로그아웃 만들기
: user.js에 코드 작성
// 로그아웃
router.post('/signout', function(req, res, next) {
req.session.destroy((err) => {
if (err) {
console.log("로그아웃 중 오류 발생");
return res.status(500).send("서버 오류가 발생했습니다.");
}
res.status(200).send("로그아웃 되었습니다.");
});
});
>> Postman에서 테스트
: 로그인을 먼저 한 다음 테스트
└ 점수 추가 및 불러오기
: user.js에 코드 작성 --> User의 정보와 엮여있어야 한다.
// 점수 추가
router.post('/addscore', async function(req, res, next) {
try {
// 로그인 여부 체크
if (!req.session.isAuthenticated) {
return res.status(400).send("로그인이 필요합니다.");
}
var userId = req.session.userId;
var score = req.body.score;
// 점수 유효성 검사
if (!score || isNaN(score)) {
return res.status(400).send("유효한 점수를 입력해주세요.");
}
var database = req.app.get('database');
var users = database.collection('users');
const result = await users.updateOne(
{ _id: new ObjectId(userId) },
{
$set: {
score: Number(score),
updatedAt: new Date()
}
}
);
if (result.matchedCount === 0) {
return res.status(400).send("사용자를 찾을 수 없습니다.");
}
res.status(200).json({ message: "점수가 성공적으로 업데이트 되었습니다."});
} catch (err) {
console.error("점수 추가 중 오류 발생 : ", err);
res.status(500).send("서버 오류가 발생했습니다.");
}
});
// 점수 조회
router.get('/score', async function(req, res, next) {
try {
if (!req.session.isAuthenticated) {
return res.status(403).send("로그인이 필요합니다.");
}
var userId = req.session.userId;
var database = req.app.get('database');
var users = database.collection('users');
const user = await users.findOne({ _id: new ObjectId(userId) });
if (!user) {
return res.status(404).send("사용자를 찾을 수 없습니다.");
}
res.json({
id: user._id.toString(),
username: user.username,
nickname: user.nickname,
score: user.score || 0
});
} catch (err) {
console.error("점수 조회 중 오류 발생 : ", err);
res.status(500).send("서버 오류가 발생했습니다.");
}
});
>> Postman에서 테스트
- 점수 추가
: 로그인을 먼저 한 다음 테스트
{
"score": 10
}
- 점수 불러오기
: 로그인 및 점수 추가를 먼저 한 다음 테스트
최종 코드
>> app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var mongodb = require('mongodb'); // 추가
var MongoClient = mongodb.MongoClient; // 추가
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
const req = require('express/lib/request');
const session = require('express-session'); // 추가
var fileStore = require('session-file-store')(session); // 추가
var app = express();
// 로그인 만들기
app.use(session({
secret: process.env.SESSION_SECRET || 'session-login',
resave: false,
saveUninitialized: false,
store: new fileStore({
path: './sessions',
ttl: 24 * 60 * 60, // 24시간
reapInterval: 60 * 60 // 1시간
}),
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24시간
}
}));
// MongoDB 추가
async function connectDB() {
var databaseURL = "mongodb://localhost:27017/tictactoe";
try {
const database = await MongoClient.connect(databaseURL, {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log("DB 연결 완료 : " + databaseURL);
app.set('database', database.db('tictactoe'));
// 연결 종료 처리
process.on("SIGINT", async () => {
await database.close();
console.log("DB 연결 종료");
process.exit(0);
});
} catch (err) {
console.error("DB 연결 실패 : " + err);
process.exit(1);
}
}
connectDB().catch(err => {
console.error("초기 DB 연결 실패 : " + err);
process.exit(1);
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
>> users.js
var express = require('express');
var router = express.Router();
var bcrypt = require('bcrypt');
const { ObjectId } = require('mongodb');
var saltrounds = 10;
var ResponseType = {
INVALID_USERNAME: 0,
INVALID_PASSWORD: 1,
SUCCESS: 2
}
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
// 회원가입
router.post('/signup', async function(req, res, next) {
try {
var username = req.body.username;
var password = req.body.password;
var nickname = req.body.nickname;
// 입력값 검증
if (!username || !password || !nickname) {
return res.status(400).send("모든 필드를 입력해주세요");
}
// 사용자 중복 체크
var database = req.app.get('database');
var users = database.collection('users');
const existingUser = await users.findOne({ username: username });
if (existingUser) {
return res.status(409).send("이미 존재하는 사용자입니다");
}
// 비밀번호 암호화
var salt = bcrypt.genSaltSync(saltrounds);
var hash = bcrypt.hashSync(password, salt);
// DB에 저장
await users.insertOne({
username: username,
password: hash, // 해시된 비밀번호 저장
nickname: nickname
});
res.status(201).send("사용자가 성공적으로 생성되었습니다");
} catch (err) {
console.error("사용자 추가 중 오류 발생 :", err);
res.status(500).send("서버 오류가 발생했습니다");
}
});
// 로그인
router.post("/signin", async function(req, res, next) {
try {
var username = req.body.username;
var password = req.body.password;
var database = req.app.get('database');
var users = database.collection('users');
// 입력값 검증
if (!username || !password) {
return res.status(400).send("모든 필드를 입력해주세요.");
}
const existingUser = await users.findOne({ username: username });
if (existingUser) {
var compareResult = bcrypt.compareSync(password, existingUser.password);
if (compareResult) {
// 세션에 사용자 정보(로그인 정보) 저장
req.session.isAuthenticated = true;
req.session.userId = existingUser._id.toString();
req.session.username = existingUser.username;
req.session.nickname = existingUser.nickname;
res.json({ result: ResponseType.SUCCESS });
} else {
res.json({ result: ResponseType.INVALID_PASSWORD });
}
} else {
res.json({ result: ResponseType.INVALID_USERNAME });
}
} catch (err) {
console.error("로그인 중 오류 발생.", err);
res.status(500).send("서버 오류가 발생했습니다");
}
});
// 로그아웃
router.post('/signout', function(req, res, next) {
req.session.destroy((err) => {
if (err) {
console.log("로그아웃 중 오류 발생");
return res.status(500).send("서버 오류가 발생했습니다.");
}
res.status(200).send("로그아웃 되었습니다.");
});
});
// 점수 추가
router.post('/addscore', async function(req, res, next) {
try {
// 로그인 여부 체크
if (!req.session.isAuthenticated) {
return res.status(400).send("로그인이 필요합니다.");
}
var userId = req.session.userId;
var score = req.body.score;
// 점수 유효성 검사
if (!score || isNaN(score)) {
return res.status(400).send("유효한 점수를 입력해주세요.");
}
var database = req.app.get('database');
var users = database.collection('users');
const result = await users.updateOne(
{ _id: new ObjectId(userId) },
{
$set: {
score: Number(score),
updatedAt: new Date()
}
}
);
if (result.matchedCount === 0) {
return res.status(400).send("사용자를 찾을 수 없습니다.");
}
res.status(200).json({ message: "점수가 성공적으로 업데이트 되었습니다."});
} catch (err) {
console.error("점수 추가 중 오류 발생 : ", err);
res.status(500).send("서버 오류가 발생했습니다.");
}
});
// 점수 조회
router.get('/score', async function(req, res, next) {
try {
if (!req.session.isAuthenticated) {
return res.status(403).send("로그인이 필요합니다.");
}
var userId = req.session.userId;
var database = req.app.get('database');
var users = database.collection('users');
const user = await users.findOne({ _id: new ObjectId(userId) });
if (!user) {
return res.status(404).send("사용자를 찾을 수 없습니다.");
}
res.json({
id: user._id.toString(),
username: user.username,
nickname: user.nickname,
score: user.score || 0
});
} catch (err) {
console.error("점수 조회 중 오류 발생 : ", err);
res.status(500).send("서버 오류가 발생했습니다.");
}
});
module.exports = router;
'Development > C#' 카테고리의 다른 글
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 68일차 (0) | 2025.03.06 |
---|---|
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 67일차 (0) | 2025.03.05 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 65일차 (0) | 2025.03.05 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 64일차 (0) | 2025.02.27 |
멋쟁이사자처럼부트캠프 Unity 게임 개발 3기 63일차 (0) | 2025.02.26 |