환경 변수 관리 - 로컬에서 개발 또는 서버에 배포할 때 DB연결, 포트 설정, 시크릿 키 등 안전하게 관리하기 위한 환경 변수로 처리 - .env라는 파일에 환경 변수를 설정 npm i dotenv
.env
# DB
DB_HOST =127.0.0.1
DB_PORT =3306
DB_USER =root
DB_PASSWORD =1234
# JWT
JWT_SECRET =abcd1234%^&*
JWT_EXPORES_SEC =172800
# BCRYPT
BCRYPT_SALT_ROUNDS =10
# SERVER
PORT =8080
// app.js
import express from "express" ;
import morgan from "morgan" ;
import tweetsRouter from './router/tweets.js'
import authRouter from './router/auth.js'
import { config } from "./config.js" ;
const app = express ();
app . use ( express . json ()); // json로 연결
app . use ( morgan ( "dev" ));
//tweetsRouter 미들웨어 등록
app . use ( '/tweets' , tweetsRouter );
//authsRouter 미들웨어 등록
app . use ( '/auth' , authRouter );
app . use (( req , res , next ) => {
res . sendStatus ( 404 );
});
app . listen ( config . host . port );
// controller/auth.js
import * as authRepository from '../data/auth.js' ;
import bcrypt from 'bcrypt' ;
import jwt from 'jsonwebtoken' ;
import { config } from "../config.js" ;
/*
문제
jwt.js를 참고하여 controller/auth.js 에 토큰을 발행하고 login()에 로그인 완료되면 클라이언트에 토큰을 출력하는 프로세스를 만들어보자
*/
function createJwtToken ( id ){
return jwt . sign ({ id }, config . jwt . secretKey , { expiresIn : config . jwt . expiresInSec });
}
/*
문제.
회원가입시 아이디 중복체크 하기
단. 중복이라면 409을 리턴
*/
// 회원가입
export async function signup ( req , res , next ){
const { username , password , name , email , url } = req . body ;
const found = await authRepository . findByUsername ( username );
if ( found ){
return res . status ( 409 ). json ({ message : ` ${ username } 이 이미 있습니다` });
}
const hashed = await bcrypt . hash ( password , config . bcrypt . saltRounds );
const userId = await authRepository . createUser ({ username , hashed , name , email , url });
const token = createJwtToken ( userId );
res . status ( 201 ). json ({ token , username });
}
/*
문제
controller/auth.js 에서 login()를 bcrypt를 적용하여 로그인 프로세스를 만들어보자
*/
//로그인
export async function login ( req , res , next ){
const { username , password } = req . body ;
// const user = await authRepository.login(username);
const user = await authRepository . findByUsername ( username );
if ( ! user ){
return res . status ( 401 ). json ({ message : '아이디를 찾을 수 없음' })
}
const isValidpassword = await bcrypt . compareSync ( password , user . password );
if ( ! isValidpassword ){
return res . status ( 401 ). json ({ message : `비밀번호가 틀렸음` });
}
const token = createJwtToken ( user . id );
return res . status ( 200 ). json ({ token , username });
}
// export async function verify(req, res, next){
// const token = req.header['Token'];
// if(token){
// res.status(200).json(token);
// }
// }
export async function me ( req , res , next ){
const user = await authRepository . findById ( req . userId );
if ( ! user ){
return res . status ( 404 ). json ({ message : `일치하는 사용자가 없음` });
}
res . status ( 200 ). json ({ token : req . Token , username : user . username });
}
HTTP 통신 - 요청(request, 클라이언트)과 응답(response, 서버)으로 이루어짐 - 클라이언트가 요청을 먼저하고 서버는 수동적으로 응답해주는 구조(반이중통신) - 반이중통신 웹소켓(Web Socket) - 브라우저에서 지원하는 소켓 통신 - 실시간 네트워킹이 보장 - 전이중통신 웹소켓의 동작 원리 1. 최초 연결 요청 시 클라이언트에서 HTTP를 통해 웹서버에 요청 (핸드셰이크, Handshake: Websocket 프로토콜로 전환) 2. 연결이 수립되면 클라이언트와 서버 양측간의 데이터 통신 단계가 시작 - 양방향 통신(전이중통신) - 상대방에게 ping 패킷을 보낼 수 있음, 수신한 측은 pong 패킷을 전송함 3. 클라이언트 혹은 서버 양측 누구나 연결을 종료할 수 있음 Socket.IO - Node.js 기반의 웹소켓 라이브러리 - 실시간 양방향 통신을 가능하게 해주는 도구 npm i socket.io
// server.js
import express from 'express' ;
import http from 'http' ;
import { Server } from 'socket.io' ;
import path from 'path' ;
import { fileURLToPath } from 'url' ;
const app = express ();
const server = http . createServer ( app );
const io = new Server ( server );
const __filename = fileURLToPath ( import . meta . url );
const __dirname = path . dirname ( __filename );
// 정적 파일 서빙
// http://localhost:8080/client.html
app . use ( express . static ( __dirname ));
server . listen ( 8080 , () => {
console . log ( '서버가 8080포트에서 실행중!!' );
})
https://socket.io/
Socket.IO
Reliable Rest assured! In case the WebSocket connection is not possible, it will fall back to HTTP long-polling. And if the connection is lost, the client will automatically try to reconnect.
socket.io
https://socket.io/docs/v4/client-installation/
Client Installation | Socket.IO
The latest release is currently 4.7.5, released in March 2024.
socket.io
복사해서 client.html에 붙여넣기
문제
이모지 누르면 이모지 추가되게
문제. 채널을 생성하여 채팅을 완성해보자 닉네임: [ ] [설정] 채널: [ ] [입장] 채널리스트 게임 만화 ... [ ] [보내기] socket.join(값): 해당 값에 속한 소켓 객체가 됨 // 채널 설정 socket.join(channel): // 해당 채널의 속한 객체가 됨 // 메세지 설정 io.emit(): 모든 소켓 객체에게 이벤트를 발생 io.to(channel).emit(): // 해당 채널의 속한 객체에게만 이벤트를 발생
<! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > 클라이언트 </ title >
< script >
const socket = io ();
function setNickname (){
const nickname = document . getElementById ( 'nickname' ). value ;
socket . emit ( 'setNickname' , nickname );
}
function sendMessage (){
const message = document . getElementById ( 'message' ). value ;
socket . emit ( 'message' , message );
}
function addEmoji ( emoji ){
const message = document . getElementById ( 'message' );
message . value += emoji ;
}
function setChannel (){
const channel = document . getElementById ( 'channel' ). value ;
socket . emit ( 'setChannel' , channel );
}
// 메세지 수신 이벤트 처리
socket . on ( 'setNickname' , ( message ) => {
const chatBox = document . getElementById ( 'chatBox' );
const newMessage = document . createElement ( 'p' );
newMessage . textContent = message ;
chatBox . appendChild ( newMessage );
});
socket . on ( 'message' , ( message ) => {
console . log ( message );
const chatBox = document . getElementById ( 'chatBox' );
const newMessage = document . createElement ( 'p' );
newMessage . textContent = ` ${ message . sender } : ${ message . message } ` ;
chatBox . appendChild ( newMessage );
document . getElementById ( 'message' ). value = '' ;
});
socket . on ( 'updateChannelList' , ( channelList ) => {
const channelListElement = document . getElementById ( 'channelList' );
channelListElement . innerHTML = '' ;
channelList . forEach (( channel ) => {
const channelItem = document . createElement ( 'li' );
channelItem . textContent = channel ;
channelListElement . appendChild ( channelItem );
});
})
</ script >
</ head >
< body >
< h2 > 간단한 채팅 </ h2 >
< form >
< p > 닉네임: < input type = "text" id = "nickname" > < button type = "button" onclick = " setNickname ()" > 설정 </ button ></ p >
< p > 채널: < input type = "text" id = "channel" > < button type = "button" onclick = " setChannel ()" > 입장 </ button ></ p >
< p >< ul id = "channelList" ></ ul ></ p >
< p >< input type = "text" id = "message" > < button type = "button" onclick = " sendMessage ()" > 보내기 </ button > < button type = "button" onclick = " addEmoji ('😎')" > 😎 </ button > < button type = "button" onclick = " addEmoji ('🎃')" > 🎃 </ button > < button type = "button" onclick = " addEmoji ('😛')" > 😛 </ button > < button type = "button" onclick = " addEmoji ('😊')" > 😊 </ button > < button type = "button" onclick = " addEmoji ('🤣')" > 🤣 </ button ></ p >
</ form >
< div id = "chatBox" ></ div >
</ body >
</ html >
import express from 'express' ;
import http from 'http' ;
import { Server } from 'socket.io' ;
import path from 'path' ;
import { fileURLToPath } from 'url' ;
const app = express ();
const server = http . createServer ( app );
const io = new Server ( server );
const __filename = fileURLToPath ( import . meta . url );
const __dirname = path . dirname ( __filename );
// 정적 파일 서빙
// localhost:8080/client.html
app . use ( express . static ( __dirname ));
const channels = new Set ();
io . on ( 'connection' , ( socket ) => {
console . log ( '사용자가 연결되었습니다' );
let nickname = '' ;
let channel = '' ;
// 닉네임 설정
socket . on ( 'setNickname' , ( name ) => {
nickname = name ;
console . log ( `닉네임 설정: ${ nickname } ` );
io . emit ( 'setNickname' , `알림: 닉네임 설정됨 ${ nickname } ` );
});
// 메세지 설정
socket . on ( 'message' , ( message ) => {
console . log ( `클라이언트: ${ channel } 에서 ${ nickname } -> ${ message } ` );
io . to ( channel ). emit ( 'message' , { sender : nickname , message });
});
// 채널 설정
socket . on ( 'setChannel' , ( ch ) => {
channel = ch ;
socket . join ( channel );
channels . add ( channel );
console . log ( `클라이언트: ${ nickname } 님이 채널 ${ channel } 에 입장` );
io . emit ( 'updateChannelList' , Array . from ( channels ))
});
// 소켓 종료
socket . on ( 'disconnect' , () => {
console . log ( `클라이언트: ${ nickname } 접속 종료` );
});
});
server . listen ( 8080 , () => {
console . log ( '서버가 8080포트에서 실행중!!' );
})