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' 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); } |