1. 기술면접

[Node.js] Express + Socket.io 프로젝트 코드 구조 정리 (SoftDesk AI)

lhk9311 2026. 5. 11. 21:22

[Node.js] Express + Socket.io 프로젝트 코드 구조 정리(SoftDesk AI)

목차

1. 프로젝트 개요
2. 전체 파일 구조
3. Express란?
4. 요청 흐름
5. 파일별 역할 분석
6. 느낀점/ 개선점

 

1. 프로젝트 개요

SoftDesk AI는 사내 소프트웨어 라이선스 문의 챗봇 토이 프로젝트임.
Node.js + Express + Socket.io 백엔드, React 프론트엔드, MySQL DB, OpenAI API로 구성됨.

 

※ 기술 스택 요약 :

항목 기술
백엔드 Node.js + Express + Socket.io
프론트엔드 React
DB MySQL 8.0
AI OpenAI API (gpt-4.1-mini)
배포 AWS EC2 + Nginx + PM2

2. 전체 파일 구조

chatbot-backend/
├── server.js             ← 진입점. Express + Socket.io 초기화
├── db.js                   ← MySQL 연결 설정
├── .env                    ← 환경변수 (API 키, DB 비번) - GitHub 업로드 금지
├── package.json     ← 의존성 목록 │
├── routes/
│ └── chatRoutes.js  ← URL 경로 정의

├── controllers/
│ └── chatController.js ← 요청 처리 로직

├── services/
│ └── openaiService.js ← OpenAI API 호출 함수

├── socket/
│ └── chatSocket.js ← Socket.io 이벤트 처리 (채팅 핵심 로직)

└── images/ ← 정적 이미지 파일

chatbot-frontend/
├── public/      ← index.html
├── src/
│ ├── App.js    ← 메인 컴포넌트 (전체 UI + 소켓 연결)
│ ├── index.js  ← React 앱 시작점
│ ├── assets/   ← SW 로고 이미지들
│ └── App.css  ← 스타일
├── package.json
├── .env           ← 환경변수 (PORT 등)
└── build/         ← npm run build 결과물 → EC2에 올리는 파일

 

Spring Boot 프로젝트 구조랑 비교하면 :

Node.js Spring Boot
server.js Application.java
routes/ @RequestMapping
controllers/ @Controller / @RestController
services/ @Service
db.js DataSource 설정
.env application.yml

3. Express란?

Node.js는 자바스크립트로 서버를 만들 수 있는 런타임이고, Express는 그 위에서 동작하는 웹 프레임워크임.

Node.js = JVM (자바 실행 환경)
Express = Spring MVC (웹 요청 처리 프레임워크)

 

Express가 없으면 URL 파싱, 요청 처리, 응답 등을 전부 직접 구현해야 함.

// Express 없이 Node.js만 쓰면
const http = require('http');
http.createServer((req, res) => {
    if (req.url === '/chat/faqs' && req.method === 'GET') {
        // 직접 파싱하고 처리...
    }
});

// Express 쓰면
app.get('/chat/faqs', (req, res) => {
    res.json(results);  // 간단하게 처리
});

 

+ Express 미들웨어

요청이 컨트롤러에 도달하기 전에 거치는 공통 처리 단계임.
Spring Boot의 @Filter, @Interceptor 같은 개념. 요청이 들어올 때마다 자동으로 실행하는 공통 로직.

app.use(cors());          // CORS 허용 처리
app.use(express.json());  // 요청 바디를 JSON으로 파싱

 

---> JSON 파싱을 미들웨어가 대신 해줘서 컨트롤러에서 req.body.message를 바로 쓸 수 있음.

 

Spring Boot 랑 비교하면 ...

Node.js 미들웨어 Spring Boot
app.use(cors()) @CrossOrigin / CorsFilter
app.use(express.json()) @RequestBody + Jackson 자동 파싱
app.use(morgan()) AOP 로깅

 

Spring Boot는 어노테이션이나 자동설정으로 처리해주는 걸 Node.js는 미들웨어로 직접 등록해야 함.


4. 요청 흐름

■ HTTP 요청 흐름 (FAQ 조회 등)

브라우저
  ↓ GET /chat/faqs
server.js
  ↓ app.use('/chat', chatRoutes)
chatRoutes.js
  ↓ router.get('/faqs', chatController.getFaqs)
chatController.js
  ↓ db.query(sql)
MySQL
  ↓ 결과 반환
브라우저

 

■ 채팅 요청 흐름 (Socket.io)

사용자가 메시지 입력
  ↓ socket.emit('chat message', 메시지)
chatSocket.js
  ↓ software_alias 테이블에서 SW 이름 검색
  ↓ faq 테이블에서 키워드 매칭
  ↓ FAQ 있으면 → FAQ 답변 반환
  ↓ FAQ 없으면 → OpenAI API 호출 (askLLM)
  ↓ DB에 대화 저장
  ↓ socket.emit('chat response', 답변)
사용자 화면에 답변 출력

 

※ HTTP와 Socket.io의 차이

HTTP Socket.io
클라이언트가 요청해야만 응답 가능 연결 유지 → 서버가 먼저 메시지 보낼 수 있음
단방향 (요청 → 응답 → 끝) 양방향 (언제든 주고받기 가능)
FAQ 조회, 메시지 내역 조회에 사용 실시간 채팅에 사용

 

- 왜 채팅에 Socket.io를 써야 하는지 ->

1. 일반 HTTP로 채팅 구현하면 이렇게 됨.

사용자: "신청 어떻게 하나요?" 전송
    ↓ POST /chat 요청
서버: OpenAI API 호출 중... (1~3초 걸림)
    ↓ 응답 올 때까지 기다림
서버: 응답 왔음 → 클라이언트에 반환

 

OpenAI API 응답이 빠르면 1초, 느리면 5초 이상 걸리기 때문에, HTTP는 요청 보내고 응답 올 때까지 기다려야 함. 그 사이 연결 끊기거나 타임아웃 날 수 있음.

 

2. Socket.io 쓰면:

사용자: "신청 어떻게 하나요?" 전송 (socket.emit)
     ↓ 연결 유지된 채로 서버가 처리
서버: OpenAI API 호출 중... (비동기로 처리)
    ↓ 응답 오는 순간 바로 클라이언트에 쏴줌
사용자: 답변 수신 (socket.on)

 

연결이 계속 살아있어서 서버가 준비되는 즉시 클라이언트에 쏠 수 있음. "AI 답변 생성중..." 로딩 메시지 띄워두고 답변 오면 바로 화면에 뿌려주도록 설계함.

 

※ Socket.io 추가 학습 필요
이번 프로젝트에서 Socket.io를 처음 써봤는데 이벤트 기반 통신 개념이 아직 낯설음. 추후 별도 포스팅으로 정리 예정.
공식 문서: https://socket.io/docs/v4/


5. 파일별 역할 분석

■ server.js (진입점)

모든 게 시작되는 파일임. 크게 4덩어리로 나뉨.

// 1. 패키지 로드
require('dotenv').config();  // .env 파일 로드
const express = require('express');
const { Server } = require('socket.io');
const db = require('./db');

// 2. Express 앱 + HTTP 서버 생성
const app = express();
app.use(cors());
app.use(express.json());
const server = http.createServer(app);

// 3. Socket.io 연결
const io = new Server(server, {
  cors: { origin: [...] }
});

// 4. 라우터 + 소켓 등록 + 서버 시작
app.use('/chat', chatRoutes);
require('./socket/chatSocket')(io);
server.listen(4000);

 

- 개선 필요 : server.js에 askLLM 함수가 중복으로 남아있음. services/openaiService.js로 이미 분리됐으니 server.js의 것은 제거해야 함

 

■ db.js (DB 연결)

MySQL 연결 설정 파일. .env에서 접속 정보를 가져와서 연결하고, module.exports로 내보냄.
다른 파일에서 require('../db')로 가져다 씀. Spring Boot의 application.yml에 DB 설정하고 @Autowired DataSource 쓰는 것과 동일한 개념임.

const db = mysql.createConnection({
  host:     process.env.DB_HOST,
  user:     process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME
});

db.connect((err) => { ... });
module.exports = db;

 

  routes/chatRoutes.js (URL 경로 정의)

URL과 컨트롤러 함수를 연결하는 파일임.

router.post('/',         chatController.chat);        // POST /chat
router.get('/messages',  chatController.getMessages); // GET /chat/messages
router.get('/faqs',      chatController.getFaqs);     // GET /chat/faqs

 

server.js에서 app.use('/chat', chatRoutes)로 등록했기 때문에 앞에 /chat이 자동으로 붙음.

Spring Boot의 @RequestMapping("/chat") + @GetMapping("/faqs") 조합이랑 동일함.

 

  controllers/chatController.js (요청 처리)

3개의 함수가 있음.

 

1) chat - HTTP POST로 채팅 처리하는 함수.
현재는 Socket.io로 채팅을 처리하고 있어서 실제로 안 쓰이는 함수임. 초기 버전 코드가 남아있는 상태.

2) getMessages - 채팅 내역 조회.
server.js에도 /messages API가 중복으로 있음. 정리 필요.

3) getFaqs - FAQ 목록 조회.
faq 테이블과 software 테이블을 JOIN해서 반환함. 프론트에서 FAQ 버튼 렌더링할 때 호출함.

 

exports.getFaqs = (req, res) => {
  const sql = `
    SELECT s.name AS software_name, f.question_keyword, f.answer, f.category
    FROM faq f JOIN software s ON f.software_id = s.id
    ORDER BY s.name
  `;
  db.query(sql, (err, results) => {
    res.json(results);
  });
};

 

■ services/openaiService.js (AI 호출)

OpenAI API를 호출하는 함수 하나만 있는 파일임.

async function askLLM(question) {
    const response = await client.chat.completions.create({
        model: "gpt-4.1-mini",
        messages: [
            { role: "system", content: "너는 사내 헬프데스크 챗봇이다..." },
            { role: "user",   content: question }
        ]
    });
    return response.choices[0].message.content;
}

 

system은 AI한테 역할을 부여하는 프롬프트고, user는 실제 사용자 질문임.
chatSocket.js에서 FAQ 매칭 실패 시 이 함수를 호출함. (fallback-through)

 

socket/chatSocket.js (채팅 핵심 로직)

실시간 채팅의 핵심 파일임. 메시지가 들어오면 하기 순서로 처리함.

① software_alias 테이블에서 SW 이름 검색
② faq 테이블에서 키워드 매칭
③ FAQ 있으면 → FAQ 답변 FAQ 없으면 → GPT 호출 (askLLM)
④ DB에 대화 저장
⑤ socket.emit('chat response', 답변) 으로 응답

 

+ 실제 흐름

 

1번 단계에서 sw를 찾든 못찾든 FAQ 검색은 똑같이 하는 구조. GPT 호출할 때 답변 앞에 붙는 문구만 달라짐. 즉 SW alias 검색은 GPT 답변 앞에 SW 이름 붙여주는 역할만 함.

 

* SW 찾았을 때: "IntelliJ IDEA 관련 FAQ를 찾을 수 없어 AI 답변을 제공합니다."

* SW 못 찾았을 때: "관련 FAQ를 찾을 수 없어 AI 답변을 제공합니다."

 

+ 소켓 연결/종료 이벤트

io.on('connection', (socket) => {
    console.log('사용자 소켓 연결됨');

    socket.on('chat message', (message) => {
        // 메시지 처리 로직
    });

    socket.on('disconnect', () => {
        console.log('연결 종료');
    });
});

 

Socket.io는 이벤트 기반으로 동작함. on으로 이벤트 수신, emit으로 이벤트 발신.

 

FAQ에 없는 질문 들어오면 GPT 호출하면서 동시에 io.emit으로 모든 접속자한테 "미처리 문의 들어왔어요" 알림을 쏘는 것. 원래 설계 의도는 관리자가 접속해 있으면 실시간으로 미처리 문의 확인할 수 있게 하려고 만든 기능.  ---> 아직 관리자 전용 채널이 없어서 일반 사용자에게도 다 보임. 개선하려면 관리자 소켓 룸을 따로 만들어야 함.

// 현재: 모든 접속자에게 전송
io.emit("admin-request", { software, message, createdAt });

// 개선하면: 관리자 룸에만 전송
socket.join('admin-room');
io.to('admin-room').emit("admin-request", { software, message, createdAt });

6. 느낀점 / 개선필요

1) 중복 코드 정리 필요

  • server.js에 askLLM 함수가 남아있음 → 삭제 필요
  • server.js의 /messages API와 chatController.getMessages가 중복 → 하나로 통일 필요

2) 커넥션 풀 미적용

  • mysql.createConnection은 단일 연결임. 트래픽이 많아지면 mysql.createPool로 변경해야 함

3) chatController.chat 미사용

  • HTTP 방식으로 채팅 처리하려고 만든 함수인데, Socket.io로 전환하면서 사용 안 하게 됨
  • 정리 필요

+ Spring Boot vs Node.js

 

  • Spring Boot는 어노테이션 기반으로 구조가 명확하게 강제되는 반면, Node.js는 자유도가 높아서 직접 구조를 잡아야 함
  • 그래서 중복 코드나 미사용 코드가 생기기 쉬움. 폴더 구조와 역할 분리를 처음부터 명확히 잡는 게 중요함

 

+ 보완할 점 

- Postman으로 SoftDesk AI API 테스트하기 - GET /chat/faqs → FAQ 목록 조회 - GET /messages → 채팅 내역 조회 - Collection 만들어서 공유 링크로 포트폴리오에 첨부 예정

 

- SonarLint로 코드 품질 분석하기 - 중복 코드 탐지 (server.js의 askLLM 중복 등) - 미사용 함수 탐지 (chatController.chat 등) - IntelliJ 플러그인으로 실시간 분석