본문 바로가기

4. Node.js | React.js

5/9(화) IT K-DT(47일차) / twitter 예제(3)

 

7. dotenv 기능 실습

환경 변수를 관리하는 데 사용되는 모듈.

'보안적인 이유'로 애플리케이션에서 사용되는 중요한 정보를 환경 변수로 저장하고 dotenv를 사용.

→ 애플리케이션에서 이러한 환경 변수에 액세스할 수 있게 함.

 

server 폴더에 dotenv 모듈 설치

npm i dotenv

C:\yjcho\Node.js\Project\Client\src\index.js

const baseURL = process.env.REACT_APP_BASE_URL; 코드로 dotenv를 불러올 수 있음.
→ C:\yjcho\Node.js\Project\Client\.env 경로의 값을 읽어오기 위함.

 

C:\yjcho\Node.js\Project\Server\config.js 파일 생성 후 아래의 내용 추가.


export const config = {
    jwt: {
        secretKey: 
        expiresInSec:

    },
    bcrypt: {
        saltRounds:
    },
    host: {
        port:
    }
};

비어있는 값들은 dotenv에서 가져올 예정.

 

C:\yjcho\Node.js\Project\Server\.env 파일 생성 후 아래의 내용 추가.


JWT_SECRET=
JWT_EXPIRES_SEC=
BCRYPT_SALT_ROUND=
SERVER_PORT=

 

C:\yjcho\Node.js\Project\Server\config.js

dotenv에서 작성한 내용들을 require로 가져옴.


import dotenv from 'dotenv';
dotenv.config();


function required(key, defaultValue=undefined){
    const value = process.env[key] || defaultValue;       //  process.env[key]가 존재하지 않는 경우 defaultValue를 할당.
    if(value == null) {
        throw new Error(`Key ${key} is undefined`);
    }
    return value;
}


export const config = {
    jwt: {
    1)    secretKey: required('JWT_SECRET'),   // 환경 변수 JWT_SECRET의 값을 가져옴.
    2)    expiresInSec: parseInt(required('JWT_EXPIRES_SEC', 86400)), // parseInt(): 숫자로 변환시켜 저장.
      // 환경 변수 JWT_EXPIRES_SEC의 값을 가져오며, 해당 환경 변수가 존재하지 않는 경우 86400(defaultValue) 사용.
    },
    bcrypt: {
    3)    saltRounds:parseInt(required('BCRYPT_SALT_ROUND', 12)),
    // 환경 변수 BCRYPT_SALT_ROUND의 값을 가져오며, 해당 환경 변수가 존재하지 않는 경우 12(defaultValue)을 사용.
    },
    host: {
    4)    port:parseInt(required('SERVER_PORT', 8080)),
     //환경 변수 SERVER_PORT의 값을 가져오며, 해당 환경 변수가 존재하지 않는 경우 8080(defaultValue)을 사용.
    }
};

 

4) SERVER_PORT 부분 수정
C:\yjcho\Node.js\Project\Server\index.js

경로에 해당 내용 추가/수정.


import {config } from "./config.js";

const server = app.listen(config.host.port);


1) JWT_SECRET 부분 수정

C:\yjcho\Node.js\Project\Server\middleware\auth.js

경로에 해당 내용 추가/수정.


import {config } from "../config.js";

 jwt.verify(
        token,
        config.jwt.secretKey, // server>controller>auth.js에서의 secretkey를 가져옴.

 

2) JWT_EXPIRES_SEC 부분 수정

C:\yjcho\Node.js\Project\Server\controller\auth.js

경로에 해당 내용 추가/수정.


import {config } from '../config.js'

const jwtSecretKey = 'Eh$nZnt8Qz&IUE*Tb3cV90wC43Ea$6T0';   (삭제)

function createJwtToken(id){
    return jwt.sign({id}, config.jwt.secretKey, {expiresIn: config.jwt.expiresInSec});
}


3) BCRYPT_SALT_ROUND 부분 수정
C:\yjcho\Node.js\Project\Server\controller\auth.js

경로에 해당 내용 추가/수정.


const hashed = await bcrypt.hash(password, config.bcrypt.saltRounds);

 

8. socket 기능 실습

클라이언트-서버 간의 양방향 통신을 가능하게 해주는 기술.

소켓을 사용하여 서버와 클라이언트 간에 실시간으로 데이터를 전송하고 수신할 수 있음.

Node.js에서는 socket.io 모듈을 사용하여 소켓 기능을 구현할 수 있으며,

이를 통해 Node.js 서버에서 웹 소켓 프로토콜을 사용하는 어플리케이션을 구현할 수 있음.

 

서버쪽의 소켓을 이용하고 싶을 때, client단과 server단 양쪽 다 모듈이 필요함.
server쪽 라이브러리 설치 (서버소켓) npm i socket.io
client쪽 라이브러리 설치 (일반소켓) npm i socket.io-client

 

1. server쪽 수정

C:\yjcho\Node.js\Project\Server\index.js

경로에 해당 내용 추가/수정.


import {Server} from "socket.io";

const server = app.listen(config.host.port);
const socketIO = new Server(server, {
    cors: {
        origin: "*"
    }
});

socketIO.on('connection', ()=> {   // socket에 대한 이벤트 정의. connection 이벤트가 발생 시, 비동기처리함수를 발생시킴.
    console.log('클라이언트 연결 성공');
    socketIO.emit('dwitter', 'Hello')          // socket에 대한 이벤트를 발생.
});



2. client쪽 수정

C:\yjcho\Node.js\Project\Client\src\index.js

경로에 해당 내용 추가/수정.


import socket from 'socket.io-client';

const socketIO = socket(baseURL);
socketIO.on('connect_error', (error)=>{
  console.log('소켓에러!', error);
});
socketIO.on('dwitter', (msg)=>console.log(msg));

 

3. cors와 관련된 에러 방지용 코드 작성
C:\yjcho\Node.js\Project\Server\index.js

경로에 해당 내용 추가/수정.


const socketIO = new Server(server, {
    cors: {
        origin: "*"
    }
}); 

 

1초마다 메시지를 출력하고자 하는 경우,

C:\yjcho\Node.js\Project\Server\index.js

경로에 해당 내용 추가/수정.


setInterval(()=>{
    socketIO.emit('dwitter','ㅋㅋㅋㅋㅋㅋ');
}, 1000);


예) 실시간으로 다른사람의 글이 업로드되는 상황을 socket으로 구현하고자 하는 경우.
C:\yjcho\Node.js\Project\Client\src\network\socket.js

경로에 파일 생성 후 해당 내용 추가/수정.


 import socket from 'socket.io-client';

 export default class Socket {
  constructor(baseURL, getAccessToken) {
    this.io = socket(baseURL, {
      auth: (cb) => cb({ token: getAccessToken() }),
    });

    this.io.on('connect_error', (err) => {
      console.log('socket error', err.message);
    });
  }

  onSync(event, callback) {
    if (!this.io.connected) {
      this.io.connect();
    }

    this.io.on(event, (message) => callback(message));
    return () => this.io.off(event);
  }
 }

 

C:\yjcho\Node.js\Project\Client\src\service\tweet.js

경로에 파일 생성 후 해당 내용 추가/수정.


export default class TweetService {
  constructor(http, tokenStorage, socket) {
    this.http = http;
    this.tokenStorage = tokenStorage;
    this.socket = socket;
  }

  onSync(callback) {
    return this.socket.onSync('tweets', callback);
  }
}


C:\yjcho\Node.js\Project\Client\src\index.js

경로에 파일 생성 후 해당 내용 추가/수정.


 import Socket from './network/socket';

 const socketClient = new Socket(baseURL, () => tokenStorage.getToken());
 const tweetService = new TweetService(httpClient, tokenStorage, socketClient);

 

C:\yjcho\Node.js\Project\Client\src\components\Tweets.jsx

경로에 파일 생성 후 해당 내용 추가/수정.


    const stopSync = tweetService.onSync((tweet) => onCreated(tweet));
    return () => stopSync();
  }, [tweetService, username, user]);

 

 

C:\yjcho\Node.js\Project\Server\connection\socket.js

경로에 파일 생성 후 해당 내용 추가/수정.

import {Server} from 'socket.io';
import jwt from 'jsonwebtoken';
import {config} from '../config.js';

class Socket {              // Socket이라는 class 생성.
    constructor(server) {
        this.io = new Server(server, {
            cors: {
                origin: '*'
            }
        });

        this.io.use((socket, next)=>{
            const token = socket.handshake.auth.token; 
                   // socket 통신 시 token을 handshake에 감춰 눈에 보이지 않게 header로 전송.
            if(!token){
                return next(new Error('Authentication error'));
            }
            jwt.verify(token, config.jwt.secretKey, (error, decoded) => {
                if (error){
                return next(new Error('Authentication error'));
            }
            next();
        });
    });

    this.io.on('connection', (socket) => {
        console.log('Socket client connected');
    });

    }
}

let socket;
export function initSocket(server){  // initSocket이라는 socket의 객체화.
    if(!socket){
        socket = new Socket(server);
    }
}

export function getSocketIO(){
    if(!socket){
        throw new Error('init을 먼저 호출해주세요');
    }
    return socket.io;
}

 

C:\yjcho\Node.js\Project\Server\index.js

경로에 파일 생성 후 해당 내용 추가/수정.


import {initSocket} from "./connection/socket.js";

const server = app.listen(config.host.port);
initSocket(server);

 

C:\yjcho\Node.js\Project\Server\controller\tweet.js

경로에 파일 생성 후 해당 내용 추가/수정.


import {getSocketIO} from "../connection/socket.js";

export async function createTweet(req, res, next){ 
    const { text } = req.body;
    const tweet = await tweetRepository.create(text, req.userId);
    res.status(201).json(tweet);
    getSocketIO().emit('tweets', tweet);
}