dev

Node.js Express 앱에 Gemini AI 연동하기

quinton84 2025. 7. 11. 09:38
반응형
반응형

 

 

 

 

📝 오늘 배운 것: Node.js Express 앱에 Gemini AI 연동하기 학습 기록

오늘은 웹 브라우저에서 질문을 입력하면 Gemini AI가 답변을 해주는 간단한 웹 애플리케이션을 만들고 실행하는 과정을 배우셨습니다. 이 과정을 통해 웹 개발의 기초와 AI API 연동의 핵심 개념을 익히셨습니다.


1. 💻 Express.js 프로젝트 생성하기

웹 애플리케이션을 만들기 위한 기본적인 뼈대(구조)를 만드는 작업입니다.

  • 개념: Express.js는 Node.js 환경에서 웹 애플리케이션을 더 쉽고 빠르게 만들 수 있도록 도와주는 프레임워크(Framework)입니다. 미리 정해진 규칙과 도구들을 제공해서 복잡한 웹 서버 코드를 직접 다 작성할 필요 없이 효율적으로 개발할 수 있게 해줍니다.
  • 사용 도구: Express 제너레이터(Express Generator)를 사용했습니다. 이것은 Express 프로젝트의 기본 폴더와 파일들을 자동으로 만들어주는 도구입니다.
  • 명령어:
    npx express --view=ejs express-gemini-app
    • npx: Node Package Execute의 줄임말로, 로컬에 설치되지 않은 Node.js 패키지(여기서는 Express 제너레이터)도 바로 실행할 수 있게 해주는 편리한 도구입니다.
    • express: Express 제너레이터를 호출하는 명령어입니다.
    • --view=ejs: 웹 페이지의 디자인을 담당하는 템플릿 엔진으로 EJS (Embedded JavaScript)를 사용하겠다고 지정하는 옵션입니다. EJS는 HTML 코드 안에 자바스크립트 코드를 넣어 동적인 웹 페이지를 만들 수 있게 해줍니다.
    • express-gemini-app: 여러분이 만들 웹 애플리케이션 프로젝트의 이름입니다. 이 이름으로 새로운 폴더가 생성되고, 그 안에 모든 프로젝트 파일들이 들어갑니다.
  • 오늘 배운 점 (오류 해결):
      [cite_start]
    • npm error could not determine executable to run: 이 오류는 npxexpress-generator를 찾지 못할 때 발생할 수 있습니다. [cite: 5]
    • 해결책: npm install -g express-generator 명령어로 Express 제너레이터를 시스템에 글로벌(-g)하게 미리 설치해주면 해결됩니다.
    • [cite_start]
    • package.json 파일 누락: 프로젝트 생성 명령이 제대로 완료되지 않으면 package.json 파일이 없을 수 있습니다. [cite: 5] 이 파일은 프로젝트의 설정과 필요한 부품(패키지) 목록을 담고 있습니다.
    • 해결책: 기존에 불완전하게 생성된 프로젝트 폴더를 삭제하고, npx express 명령을 다시 실행하여 프로젝트를 깔끔하게 재설정합니다.
    • [cite_start]
    • 'Express\express-gemini-app' 인수를 허용하는 위치 매개 변수를 찾을 수 없습니다.: cd 명령어로 폴더 이동 시, 폴더 이름에 공백이 포함되어 있으면 발생하는 오류입니다. [cite: 5]
    • [cite_start]
    • 해결책: cd "C:\Users\김광수\Desktop\파이썬\1.4 Express\express-gemini-app" 와 같이 경로 전체를 큰따옴표로 묶어주면 공백이 포함된 경로도 정확히 인식합니다. [cite: 5]

2. 📦 프로젝트에 필요한 부품(패키지) 설치하기

만들어진 웹 애플리케이션이 동작하기 위해 필요한 추가적인 부품들을 설치하는 작업입니다.

  • 개념: Node.js 프로젝트는 다양한 기능을 제공하는 패키지(Package)들을 사용합니다. 이 패키지들은 npm(Node Package Manager)이라는 도구를 통해 설치하고 관리합니다. package.json 파일에는 우리 프로젝트가 어떤 패키지들을 필요로 하는지 목록이 적혀 있습니다.
  • 명령어:
    npm install
    • 이 명령어는 package.json 파일을 읽어서 프로젝트에 필요한 모든 패키지들을 node_modules라는 폴더 안에 자동으로 다운로드하여 설치해줍니다.
  • 오늘 배운 점 (오류 해결):
      [cite_start]
    • npm error code EJSONPARSE: 이 오류는 package.json 파일의 JSON 문법이 잘못되었을 때 발생합니다. [cite: 1] [cite_start]특히 JSON 파일 안에는 주석(// 또는 /* */)을 사용할 수 없습니다. [cite: 1]
    • 해결책: package.json 파일 안에 있는 모든 주석을 제거해야 합니다. [cite_start]예를 들어, "@google/generative-ai": "^0.1.3" // 이 부분을 추가하거나 변경합니다. 와 같은 줄이 있다면 // 이 부분을 추가하거나 변경합니다.를 지워야 합니다. [cite: 1]
    • [cite_start]
    • Error: Cannot find module 'dotenv': dotenv 모듈을 찾을 수 없다는 오류입니다. [cite: 2] dotenv.env 파일(환경 변수 파일을 만드는 용도)에 있는 중요한 정보(API 키 등)를 우리 프로그램이 읽을 수 있게 해주는 패키지입니다.
    • [cite_start]
    • 해결책: npm install dotenv 명령어로 dotenv 패키지를 설치해주면 됩니다. [cite: 2]

3. ⚙️ .env 파일로 중요 정보(API 키) 관리하기

우리 프로그램에서 사용할 중요한 정보인 Gemini API 키를 안전하게 보관하고 불러오는 방법입니다.

  • 개념: 환경 변수(Environment Variable)는 프로그램이 실행되는 환경에 따라 값이 달라질 수 있는 변수입니다. API 키와 같은 민감한 정보는 코드 안에 직접 넣는 대신, .env 파일이라는 별도의 파일에 저장하고 환경 변수로 불러와 사용하는 것이 일반적입니다. 이렇게 하면 코드를 다른 사람과 공유해도 API 키가 노출되지 않아 안전합니다.
  • 작업:
    1. 프로젝트의 가장 바깥 폴더(express-gemini-app) 안에 .env라는 이름의 파일을 만듭니다. (파일 탐색기에서 직접 만들면 됩니다.)
    2. .env 파일을 열고 다음과 같이 Gemini API 키를 저장합니다.
      GOOGLE_API_KEY=여러분이_발급받은_실제_Gemini_API_키
      • = 양쪽에 공백이 없도록 주의해야 합니다.
      • 여러분이_발급받은_실제_Gemini_API_키는 구글 AI 스튜디오 등에서 직접 발급받은 문자열입니다.

4. 🚀 웹 애플리케이션 실행하고 확인하기

우리가 만든 웹 애플리케이션이 웹 서버로서 제대로 동작하는지 확인하는 작업입니다.

  • 명령어:
    npm start
    • 이 명령어를 실행하면 Node.js 환경에서 Express 웹 서버가 시작됩니다. 서버는 기본적으로 3000번 포트에서 클라이언트의 요청을 기다립니다.
  • 확인:
    1. 웹 브라우저를 열고 주소창에 http://localhost:3000을 입력하여 접속합니다.
    2. [cite_start]
    3. Gemini Chat App이라는 제목과 함께 프롬프트를 입력할 수 있는 공간(Prompt:)과 Submit 버튼이 보이면 성공입니다. [cite: 3]
  • 오늘 배운 점 (오류 해결):
      [cite_start]
    • ERR_CONNECTION_REFUSED (사이트에 연결할 수 없음): 웹 서버가 실행되지 않았거나, 방화벽 등의 이유로 연결이 거부될 때 나타납니다. [cite: 4]
    • 해결책:
      1. 터미널에 npm start 실행 후 오류 메시지가 없는지 확인: 서버가 제대로 시작되었는지 먼저 확인합니다.
      2. [cite_start]
      3. 방화벽 해제 또는 규칙 추가: Windows 방화벽3000번 포트로의 접속을 막고 있을 가능성이 큽니다. [cite: 4]
          [cite_start]
        • 임시 해제 후 테스트: Windows 방화벽을 임시로 사용 안 함으로 설정한 후 접속을 시도하여 문제가 방화벽 때문인지 확인합니다. [cite: 4]
        • 인바운드 규칙 추가: 테스트 후 방화벽을 다시 활성화하고, 인바운드 규칙을 추가하여 TCP 포트 3000에 대한 연결을 허용하도록 설정합니다.

5. 🤖 Gemini AI 연동 코드 수정하기 (routes/index.js)

이제 웹 애플리케이션의 핵심인 AI 기능을 추가하는 작업입니다. 사용자로부터 받은 질문을 Gemini AI에 보내고, 답변을 받아 웹 페이지에 표시하는 코드를 작성합니다.

  • 개념: Express 앱에서는 라우터(Router)라는 것을 사용하여 특정 웹 주소(GET /, POST / 등)로 들어오는 요청을 처리합니다. 우리는 routes 폴더 안의 index.js 파일에서 이러한 요청들을 처리하는 코드를 작성했습니다.
  • 주요 코드 (핵심!):
    
    var express = require('express');
    var router = express.Router();
    
    // Gemini API를 위한 Google Generative AI SDK import
    const { GoogleGenerativeAI } = require('@google/generative-ai');
    // dotenv 라이브러리를 사용하여 .env 파일의 환경 변수를 로드
    require('dotenv').config();
    
    // .env 파일에서 Gemini API 키 가져오기
    const GEMINI_API_KEY = process.env.GOOGLE_API_KEY;
    
    // GoogleGenerativeAI 인스턴스 초기화
    // 사용 가능한 모델 이름(예: gemini-pro, gemini-2.0-flash 등)을 확인하여 지정
    const genAI = new GoogleGenerativeAI(GEMINI_API_KEY);
    const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" }); // 사용 가능한 모델로 변경
    
    /* GET home page. */
    router.get('/', function(req, res, next) {
      res.render('index', { 
        title: 'Gemini Chat App', 
        question: null, 
        result: null 
      });
    });
    
    /* POST request from form. */
    router.post('/', async function(req, res, next) {
      const prompt = req.body["prompt"];
      let resultText = null;
    
      if (prompt) {
        try {
          resultText = await accessGeminiAPI(prompt);
        } catch (error) {
          // 여기에서 상세 오류 로깅을 수행합니다.
          console.error("Gemini API 호출 중 상세 오류:", error); 
          if (error.response) { // error.response 객체가 존재하는지 확인 
            console.error("HTTP 상태 코드:", error.response.status); 
            console.error("오류 응답 데이터:", error.response.data); 
            if (error.response.data && error.response.data.error && error.response.data.error.message) {
              console.error("Gemini API 오류 메시지:", error.response.data.error.message);
            }
          } else if (error.message) { // 일반적인 JavaScript 에러 메시지 
            console.error("일반 오류 메시지:", error.message);
          }
          resultText = "죄송합니다. AI 응답을 가져오는 중 오류가 발생했습니다. 터미널을 확인하세요.";
        }
      }
    
      res.render('index', { 
        title: 'Gemini Chat App', 
        question: prompt, 
        result: resultText 
      });
    });
    
    // Gemini API 접근 함수
    async function accessGeminiAPI(userPrompt) {
      const chat = model.startChat({
        history: [], // 여기서는 이전 대화 없이 단일 요청으로 처리합니다.
        generationConfig: {
          maxOutputTokens: 200, // 최대 응답 토큰 수 설정
        },
      });
    
      const result = await chat.sendMessage(userPrompt);
      const response = await result.response;
      const text = response.text();
      return text;
    }
    
    module.exports = router;
                
  • 오늘 배운 점 (오류 해결):
      [cite_start]
    • title is not defined: index.ejs 템플릿에 title 변수를 넘겨주지 않아서 발생하는 오류입니다. [cite: 5]
    • 해결책: res.render('index', { title: 'Gemini Chat App', ... }); 와 같이 title 값을 index.js에서 명시적으로 전달합니다.
    • API key not valid. [cite_start]Please pass a valid API key.: Gemini API 키가 유효하지 않거나 잘못 입력되었을 때 발생합니다. [cite: 6]
    • 해결책: Google AI Studio에서 API 키를 다시 발급받거나, .env 파일에 정확하게 복사되었는지 꼼꼼히 확인합니다.
    • [cite_start]
    • Error fetching from ... [400 Bad Request]: API 호출이 유효하지 않거나, 사용하려는 모델(gemini-pro 등)이 현재 API 키로 접근할 수 없는 모델일 수 있습니다. [cite: 6]
    • 해결책: Google AI Studio에서 현재 사용 가능한 모델 이름을 확인하고, index.js 파일의 genAI.getGenerativeModel({ model: "사용_가능한_모델명" }); 부분을 정확히 수정합니다.

6. 🎨 웹 페이지 디자인 파일 수정하기 (views/index.ejs)

사용자가 질문을 입력하고 답변을 볼 수 있는 웹 페이지의 모습을 만드는 작업입니다.

  • 개념: EJS 템플릿 파일(.ejs)은 HTML 코드와 자바스크립트 코드를 함께 사용하여 동적인 웹 페이지를 만듭니다. 서버(index.js)에서 전달받은 title, question, result 같은 값들을 이 파일에서 받아서 웹 페이지에 표시합니다.
  • 주요 코드:
    <!DOCTYPE html>
    <html lang="ko">
    <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <title><%= title %></title>
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.css"
            rel="stylesheet" crossorigin="anonymous">
      <link rel='stylesheet' href='/stylesheets/style.css' />
    </head>
    <body class="container">
      <h1 class="display-6 py-2"><%= title %></h1>
    
      <form method="POST" action="/">
        <div>
          <label for="prompt">Prompt:</label>
          <textarea id="prompt" name="prompt"
                    class="form-control"></textarea>
        </div>
        <center class="py-3">
          <input type="submit" value="Submit"
                 class="btn btn-primary">
        </center>
      </form>
    
      <% if (question != null) { %>
        <p class="border border-2 p-3 h6 mt-3"><%= question %></p>
      <% } %>
    
      <% if (result != null) { %>
        <p class="border border-2 p-3 h6 mt-3"><%= result %></p>
      <% } %>
    </body>
    </html>
  • 오늘 배운 점:
    • <form> 태그: 사용자가 데이터를 입력하고 서버로 전송할 수 있는 양식을 만듭니다. method="POST"action="/"을 통해 index.jsrouter.post 부분으로 데이터가 전송되도록 합니다.
    • <textarea id="prompt" name="prompt">: 여러 줄의 텍스트를 입력할 수 있는 공간입니다. name="prompt" 속성은 서버에서 이 입력값을 req.body.prompt로 가져올 수 있게 해줍니다.
    • <input type="submit" value="Submit">: 양식에 입력된 데이터를 서버로 제출하는 버튼입니다.
    • <%= 변수명 %>: EJS에서 서버(index.js)로부터 전달받은 변수명의 값을 웹 페이지에 표시할 때 사용하는 문법입니다.
    • <% if (조건) { %> ... <% } %>: EJS에서 특정 조건이 참일 때만 해당 HTML 코드를 표시하도록 하는 제어문입니다. 질문과 답변이 있을 때만 화면에 나타나게 하는 용도로 사용되었습니다.

오늘의 학습 목표를 달성하셨습니다! 이제 이 정리 내용을 티스토리에 잘 기록해 두시면 훌륭한 학습 자료가 될 것입니다. 코딩 여정을 계속 응원하겠습니다!

반응형