본문 바로가기

프로그래밍/BackEnd Study - Node.js, Docker

2021-02-08 MySQL 연결

코드

/*
 * 파일명: main.js
 * 생성일: 2021-02-07
 * 최종 수정일: 2021-02-08
 * 설명: Web 백엔드 서버 개발 공부: Rest API with MySQL
 */

import express from "express";
import bodyParser from "body-parser";
import { dbConnection } from "./lib/db.js";

const server = express();
server.use(bodyParser.json());

// GET cmembers 데이터, /api/member/get-all로 접속하여 데이터 수신
server.get("/api/member/get-all", (req,
                           res) => {
    let sqlQuery = "SELECT * FROM CMembers";    // query string
    try {
        dbConnection.query                      // send query to MySQL
        (
            sqlQuery
            , function (err, rows, fields) {
                if (err) {                      // error occured
                    res.status(500).send("Internal Server Error");
                    console.log(err);
                }
                else {
                    res.send(rows);
                }
            }
        )
    } catch (e) {
        console.log(e);
    }
});

// POST cmembers 데이터, /api/member/add에 JSON 형식으로 리퀘스트를 전송해 데이터 추가
server.post("/api/member/add", (req,
                            res) => {
    console.log(req.body);

    let reqJson = req.body;             // JSON 형식으로 리퀘스트 온 데이터 변환

    let resString = {                   // 유효성을 체크하기 위한
        idValidance: "Valid",
        NameValidance: "Valid",
        MajorValidance: "Valid",
        PhoneNumberValidance: "Valid",
        EmailValidance: "Valid",
        GradeValidance: "Valid",
        ValidData: true,
        PostSucceeded: false
    };

    // 유효하지 않은 데이터를 거르기 위한 string 필터
    let nameFormat                                                  // 특수문자 제외
        = /[ `!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/;
    let majorFormat                                                 // 한글만 포함
        = /[ `!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]/;
    let phoneNumberFormat                                           // +와 숫자만 포함
        = /[ `!@#$%^&*()_\-=\[\]{};':"\\|,.<>\/?~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]/;

    // filter id
    if (reqJson['Id'] < 10000000 || reqJson['Id'] > 99999999)
    {
        resString.ValidData = false;
        resString.idValidance = "Invalid Id Taken";
    }

    // filter Name
    if (nameFormat.test(reqJson['Name']))
    {
        resString.ValidData = false;
        resString.NameValidance = "Invalid Name. No special characters allowed.";
    }

    // filter Major
    if (majorFormat.test(reqJson['Major']))
    {
        resString.ValidData = false;
        resString.MajorValidance = ("Invalid Major Name. No special characters and alphabets allowed.");
    }

    // filter PhoneNumber
    if (phoneNumberFormat.test(reqJson['PhoneNumber']))
    {
        resString.ValidData = false;
        resString.PhoneNumberValidance = ("Invalid Phone number. + is the only special character allowed.");
    }

    // filter Email
    if (!reqJson['Email'].includes("@")
        || reqJson['Email'].split('@')[0].length < 1
        || reqJson['Email'].split('@')[1].length < 1)
    {
        resString.ValidData = false;
        resString.EmailValidance = ("Invalid Email Address.");
    }

    // filter Grade
    if (reqJson['Grade'] < 1 || reqJson['Grade'] > 10)
    {
        resString.ValidData = false;
        resString.GradeValidance = ("Invalid Grade.");
    }

    if (resString.ValidData)    // 요청받은 데이터가 유효하다면
    {
        let sqlQuery =          // INSERT 쿼리를 보낸다
            "INSERT INTO CMembers (Id, Name, Major, PhoneNumber, Email, Grade, Comment) "
            + "VALUES ("
            + "\"" + reqJson['Id'] + "\"" +  ","
            + "\"" + reqJson['Name'] + "\"" + ","
            + "\"" + reqJson['Major'] + "\"" + ","
            + "\"" + reqJson['PhoneNumber'] + "\"" + ","
            + "\"" + reqJson['Email'] + "\"" + ","
            + "\"" + reqJson['Grade'] + "\"" + ","
            + "\"" + reqJson['Comment'] + "\""
            + ")";

        dbConnection.query(sqlQuery, function (err, rows, fields) {
            if (err)
            {
                console.log(err);
                resString.PostSucceeded = false;
            }
            else
            {
                console.log(rows);
                resString.PostSucceeded = true;
            }
        });
    }

    res.send(resString);
});

// 서버 시작
server.listen(3000, () => {
    console.log("Server Running");
});

결과

오류 해결 기록

ERR_NOT_SUPPORTED_AUTH_MODE

현재 서버에서 활용하고 있는 MySQL은 8.0.23-0ubuntu0.20.04.1 (Ubuntu) 버전이다. 즉 MySQL 8이다. MySQL 8부터는 SHA2 방식으로 암호화한 authentication_string을 활용해 사용자를 인증한다. 하지만 현재 설치되어 있는 Node.js는 아직 해당 기능을 지원하지 않아 위 오류가 발생한다.

다양한 해결 방법이 있는데, 다음과 같다.

  • 방법 1) 서버의 MySQL 인증 방식을 Legacy (MySQL 5.0)으로 전환한다.
  • 방법 2) 서버의 MySQL에서 사용자의 IDENTIFY 방식을 mysql_native_password에서 임의로 정한 비밀번호로 바꾼다.

이전에 같은 서버에 ASP.NET with MVC를 활용했을 때 정상적으로 작동하였으므로, node.js에서의 문제로 판단하였다. 모든 클라이언트에서 접속할 때 사용할 인증 방식을 바꾸는 것은 현재 상황에서 비합리적이라고 판단하였다. 나는 방법 2를 선택했다.

MySQL 서버 내 유저의 인증 방식 변경

새로운 유저를 생성하여 해당 유저는 임의의 비밀번호를 통해 인증하도록 할 수 있다. 과정은 다음과 같다.

// 1. 유저 생성
CREATE USER 'username'@'ip-constraint' IDENTIFIED BY 'password';

// 2. 원하는 데이터베이스의 권한 부여
GRANT ALL PRIVILEGES ON database.* to 'username'@'ip-constraint';

// 3. 권한 적용
FLUSH PRIVILEGES;

// 4. mysql_native_password로 인증되던 방식을 임의의 비밀번호를 사용한
// 인증으로 변경
ALTER USER 'username'@'ip-constraint' IDENTIFIED 
WITH mysql_native_password BY 'arbitrary-password';

// 5. 권한 적용
FLUSH PRIVILEGES;

ERR_MODULE_NOT_FOUND

추가할 모듈의 경로가 잘못되어 발생한 오류이다. lib 폴더에 저장한 db.js 파일에 접근하려 하였으나 경로를 lob로 입력하여 오류가 발생하였다.

SyntaxError: The requested module does not provide an export named 'default'

당시 추가할 모듈에서 export하는 방식을 숙지하지 못하여 발생한 오류이다. export할 변수 선언문 앞에 해당 키워드를 붙여 해결하였다.

// 수정 전
let dbConnection = mysql.createConnection({
    host: 'ip-address',
    user: 'user-id',
    password: 'password',
    database: 'db-name'
});

// 수정 후
export let dbConnection = mysql.createConnection({
    host: 'ip-address',
    user: 'user-id',
    password: 'password',
    database: 'db-name'
});

ERR_HTTP_HEADERS_SENT

이미 전송한 respond에 다시 send()를 사용하였을 경우 헤더를 다시 설정할 수 없어 발생하는 오류이다. GET 혹은 POST 이후 send()의 사용은 한 번씩만 해야 한다.

ER_PARSE_ERROR

JS Script 내에서 보낸 쿼리문이 유효하지 않을 때 발생하는 에러이다. 이 오류가 출력되면 쿼리문을 정상적으로 작성했는지 확인할 필요가 있다. 위 사례는 INSERT 시 VALUE를 큰따옴표로 묶지 않아 발생한 오류이다. string 기반 쿼리문은 오타나 형식 문제에 취약하므로 철저히 확인할 필요가 있다.