Life is connecting the dots .

React + Node.js + Express + MySQL 연동해서 사용해보기 본문

Programming/Node.js(express)

React + Node.js + Express + MySQL 연동해서 사용해보기

soyeori 2024. 10. 9. 10:57

프론트엔드는 React, Next.js를 사용하고, 백엔드는 node.js, express, DB는 mysql을 사용하여 연동하는 과정을 기록해 본다. 

node.js(express)를 공부한 이유는 프론트엔드 토이 프로젝트를 할 때 간단하게나마 백엔드 서버를 구축해서 모두 만들어보고 싶어서이다.

 

1. 폴더구조

프론트엔드와 백엔드를 한 번에 세팅해 본 적이 없어서 폴더구조에 고민이 많았다. 결론은

  (1) 한 폴더에 한 번에 만들어서 서버를 한 번만 띄우는 방법과

  (2) 백엔드와 프런트엔드 폴더를 각각 두어 서버를 각각 띄우는 방법으로 좁혀졌는데

최종 (2) 번을 선택해서 만들었다. 그 이유는 각 폴더 안에서 서버를 띄우는 것이 익숙하면서 개발하기에도 편했고, 에러가 발생했을 때도 빨리 확인할 수 있다고 판단했다.

 

폴더 구조

project
├── frontend
│   ├── README.md
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   └── src
└── server
    ├── config
    ├── node_modules
    ├── package-lock.json
    ├── package.json
    └── server.js

 

백엔드 기본 설치 항목 (express, nodemon 설치)

express를 사용하기 위해 백엔드 폴더(/server)에서 다음 명령어를 실행하여 설치해 주면 아주 기본적인 세팅은 끝난다.

- npm init // package.json 파일 생성
- npm install express // express 설치
- npm install nodemon --save-dev

 

이제 커맨드라인에 nodemon 명령어를 사용하여 서버 파일을 실행시킬 수 있다. 이를 npm run dev 명령어로 실행시키고자 scripts를 수정해 주었다.

 

express 하나만 설치했는데 package-lock.json에 많은 package들이 설치되는 이유

express를 설치하면 package.json파일에는 express 하나만 잘 설치된 것을 확인할 수 있는데, package-lock.json 파일이 별도로 추가되고 내가 설치하지 않은 다양한 패키지들이 설치된 것을 확인할 수 있다. package-lock.json 역할은 만약 다른 사용자가 해당 프로젝트를 설치하는 경우 express 버전뿐만 아니라 express를 실행하기 위한 의존성 패키지들의 버전도 동일하게 맞추기 위함이다. 

 

node_modules/express 설치 항목을 보면, express를 설치할 때 무수히 많은 dependencies를 설치했는데, 이 항목들을 따라가다 보면 하나의 패키지를 설치할 때 추가로 무엇이 필요해서 설치되었는지를 알 수 있다. (express를 설치할 때 accepts를 추가로 설치, accepts를 설치할 때 mime-types를 추가로 설치...)

 

2. 리액트와 노드 연결하기

백엔드 폴더에 server.js 파일을 생성하고, 본격적으로 express를 사용하기 위한 코드를 작성한다. 아래는 기본적으로 express를 사용할 수 있는 예제이다. 처음 사용해 보았을 때 아래 흐름대로 express를 사용하는구나를 익혔고, 각 항목에 대해 세부적인 부분은 docs를 참고해서 확장해 나가면 될 것 같다. 

 

  (1) express, 필요한 모듈 호출 및 포트 설정

  (2) app.use() 함수를 사용하여 미들웨어 함수 호출

  (3) 라우팅은 엔드포인트(URI)가 클라이언트 요청에 응답하는 방식으로 app객체의 메소드(HTTP메소드에 해당)를 사용해서 정의 

  (4) 지정된 호스트 및 포트에서 연결을 바인딩하고 수신

// 1. 필요한 모듈 호출
const express = require('express')

// Binds and listens for connections on the specified host and port.
const app = express()
const port = 3001

// 2. Bind application-level middleware.  
// app.use(...);

// 3. Routing
// GET method route
app.get('/', (req, res) => {
  res.send('Hello World!')
})

// POST method route
app.post('/api/signup', (req, res) => {
  ...
})

// 4. Binds and listens for connections on the specified host and port.
app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

 

프론트엔드에서는 express로 만든 라우팅 엔드포인트로 API를 호출한다.

 

3. DB(MySQL) 연동하기

데이터를 DB에 저장하기 위해 MySQL을 선택하여 mysql 설치(brew install mysql) 및 워크벤치 설치 및 백엔드 폴더에 mysql2 라이브러리도 설치(npm install mysql2) 해 주고, mysql을 실행한다.

  • mysql 서버 실행: brew services start mysql
  • 종료: brew services stop mysql
  • 접속: mysql -u root -p
  • 종료: exit

이후 워크벤치에서 쿼리문으로 데이터베이스와 테이블을 미리 만들어 주었다.

 

Connection 만들기

mysql을 사용할 때 connection을 만들어줘야 하는데 기본적으로 하나씩(one-by-one) 커넥션을 만들 수 있다. 하지만 커넥션을 맺고 끊고 하면 자원을 많이 낭비할 수 있기 때문에 커넥션 풀(pool)을 만들어서 연결을 재사용할 수 있도록 유지하는 방법을 적용했다.

// server/config/db.js

const mysql = require("mysql2");

const db = mysql.createPool({
  connectionLimit: 10,
  host: "localhost", // DB의 ip주소
  user: "...", // 사용자 이름 (*mysql에 접속한 후, SELECT user, host FROM mysql.user; 로 확인)
  password: "...", // 비밀번호
  port: 3306, // 포트번호
  database: "...", // DB이름
});

module.exports = db;

 

DB사용하기

db 연결을 완료하면 express 라우팅을 할 때 데이터 베이스 연결, 쿼리 실행, 응답 반환 관련 코드를 작성해 준다. 커넥션 풀을 사용해서 pool.getConnection() -> connection.query() -> connection.release() 흐름대로 작성한다. (참고로, pool.query()를 사용하면 자동으로 연결을 관리해 준다.)

 

4. 전체 코드

const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const db = require("./config/db"); // db connection

const app = express();

// middleware
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

const port = 3001; // react의 포트번호와 다르게 하기 위해

// routing
app.post("/api/signup", (req, res) => {
  const { id, name, age, password } = req.body;

  db.getConnection((err, connection) => {
    if (err) {
      console.log("DB connection error: ", err);
      res.status(500).send("DB connection error");
      return;
    }
    console.log("DB 연결 완료");

    connection.query(
      "insert into users (id, name, age, password) values (?,?,?,?);",
      [id, name, age, password],
      (error, result) => {
        connection.release(); // connection을 pool에 반환

        if (error) {
          console.dir("쿼리 실행 에러: ", error);
          res.status(500).send("DB connection error");
          return;
        }
        if (result) {
          console.log("result: ", result);

          // 쿼리 성공하면 리스폰스 보내고, 응답 종료
          res.json({
            ...
          });
        }
      }
    );
  });
});

// listens for connections
app.listen(port, () => {
  console.log("서버가 3001번 포트에서 실행중");
});

 

회원가입 기능을 위한 API를 만들어 보았다. 포트 번호가 다른 데서 발생하는 cors 에러를 해결하기 위한 cors 모듈 및 json 파싱을 위한 body-parser 모듈도 추가로 설치해 주었다. 해당 API를 프론트엔드에서 baseUrl("http://localhost:3001") + endpoind("/api/signup")로 fetch해와서 사용한다. 지금은 사용 방법을 익히기 위해 아주 간단하게 작성해 본 코드로 추후 프로젝트를 통해 에러 핸들링, 보안 처리, 응답 케이스 등을 다룰 계획이다.