TIL - Node.js

김영훈·2021년 5월 26일
0

ETC

목록 보기
15/34

# Django vs Node.js(express)

  • Node.js를 공부하다가 문득 Django와 Node.js의 차이점이 궁금해졌다. 호기심을 해결하고자 몇 가지 정보를 찾아봤는데 요약해보면 다음과 같다.

  • 속도

    • Node.js의 속도가 더 빠르다. Node.js는 고성능 네트워크 서버로, 기본적으로 싱글 스레드(Single Thread)를 기반으로 비동기 IO 처리를 하는 구조로 이뤄져 있다. 비동기 IO 처리의 장점은 다수의 요청(request)을 동시에 처리할 수 있는 점이다. 일반적인 동기 방식에선 하나의 요청에 대한 처리가 완료되기 전까지 다음 순서의 요청을 처리할 수 없지만, 비동기 처리에선 동시 처리가 가능하기 때문에 작업 효율성이 높다고 한다. (아직 나도 완전히 이해한 개념은 아니지만, 핵심은 대충 이렇다...)

    • 싱글 스레드(Single thread)에 대해 간략하게 설명하자면 하나의 작업 프로세스에 대해 하나의 스레드만을 사용하는 것을 말하며, 이는 프로세스를 일정 단위로 나누어 여러 개의 스레드에서 실행하는 멀티 스레드(Multi thread) 방식의 반대 개념에 해당한다. 멀티 스레드 방식의 경우 동기화에 신경을 써야 하고, 각 스레드마다 리소스가 필요하기 때문에 서버에 부담을 주는 문제점을 지니고 있다. 하지만 Node.js는 싱글 스레드를 기반으로 하고 있기 때문에 이러한 문제에서 자유롭다. 상식적으로 생각해보면... 스레드가 하나밖에 없는데 어떻게 다수의 client로부터 받는 요청을 효율적으로 처리하는 것이 가능한지에 대해 의문을 품을 수 있다. 이 의문에 대한 해답이 앞에서 설명했던 비동기 IO 처리이다. Node.js는 비동기 IO를 통해 다수의 client 요청을 동시에 처리하는 것이 가능하여 싱글 스레드만으로 효율성을 높일 수 있는 것이다.

  • 프레임워크 선택의 기준

    • CRUD 처리가 중요한 app을 제작하는 경우

      • CRUD(Create, read, update, delete) 처리에 특화된 django를 사용하는 게 바람직하다. CRUD 처리가 많은 서비스는 인스타그램이 대표적이다. 이미지 업로드, 불러오기, 수정 및 삭제하는 작업이 서비스에서 많은 부분을 차지한다.
    • 리얼 타임 / 실시간 기능이 필요한 app을 제작하는 경우

      넷플릭스의 스트리밍이나 우버의 리얼타임을 구현해야 한다거나 자바스크립트 API를 가져와서 사용하는 경우라면 Node.js가 적합하다. 즉, 커스터마이징이 많이 필요한 경우 Node.js 사용이 권장된다!

    • 정적 웹(Static Web Site) vs 동적 웹(Dynamic Web Site)

      • 정적 웹이란?

        • 언제 접속을 해도, 항상 같은 리소스를 제공하는 웹사이트

        • ex) 소개 페이지, 댓글이 없는 블로그 글

        • 정적 웹의 경우 요청마다 동일한 HTML을 새로 생성하는 것은 비효율적이다. 이 경우엔 Node.js 사용이 바람직하다.

      • 동적 웹이란?

        • 접속할 때마다 서버에서 리소스가공해서 제공하는 웹사이트로, 가령 데이터베이스에서 값을 읽어 접속할 때마다 최신 정보를 제공한다.

        • ex) SNS

        • 동적 웹사이트는 구동을 위해 웹 app 서버, 데이터베이스 같은 구성이 필수적이기 때문에 django가 적합하다.

# Node.js(express) 사용법

  • Node.js 설치 및 버전 확인

    • Node.js 설치: brew install node
    • Node.js 버전 확인 : node -v
    • npm 버전 확인 : npm -v

      npm이란?
      자바스크립트 프로그래밍 언어의 패키지 관리자. 고퀄리티의 오픈 소스 모듈을 npm을 통해 다운로드 하는 것이 가능하다.

    • 모듈 설치 방법: npm install 모듈 명
      • 모듈 설치가 완료될 경우, Node.js의 모듈 관리 폴더 및 package_lock.json 파일이 현재 경로에 생성된다.
  • 서버 생성

    • npm install express —save : express 모듈 설치

    • touch server.js : node.js 파일 생성

    • var express = require('express'); express 모듈 불러오기

    • var app = express(); : express 객체 생성 및 할당

    • var server = app.listen(포트번호, IP주소); : 서버 생성

      • ex)
        var server = app.listen(8000, () => 
            console.log('Start Server: localhosst:8000')
        );
    • npm install nodemon --save : nodemon 설치

      • 모듈 nodemon은 코드가 변경될 때마다 서버자동으로 재시작해준다.
      • nodemon 설치는 package_lock.json이 생성된 프로젝트 폴더에서 이뤄져야 error 발생을 막을 수 있으니 유의하도록 하자.
    • nodemon 파일명.js : 서버 열기

  • 라우팅하기

    라우팅이란?

    • 네트워크에서 주소를 이용하여, client 요청의 목적지까지의 경로를 체계적으로 결정하는 경로 선택 과정
    • 쉽게 말해 client의 요청(request)이 서버의 특정 기능과 mapping될 수 있도록(대응될 수 있도록) 경로를 결정해주는 것을 말한다.
    • 라우팅 객체 생성
      app.get('/hello', function(req,res) {
          res.send('hello world')
      })
  • DB 연결하기

    • npm install --save mysql2 : mysql2 모듈 설치

      • 모듈 mysql2는 모듈 mysql과 다르게 promise를 지원하는 장점이 있으므로, mysql2 사용을 추천한다.

        Promise란?
        node.js에서 함수를 사용할 경우, 비동기 처리때문에 함수가 순차적으로 실행되지 않게 된다. 이러한 경우, 함수가 순서대로 처리되도록 보장해주는 기능이 바로 callback 함수였다. 하지만 callback 함수는 비동기 처리를 중첩시켜서 표현하여 에러의 원인이 되거나, 예외처리가 어려워지는 등의 문제점을 갖고 있었다. 이러한 단점을 해결하기 위해 생겨난 것이 promise다.

    • var mysql = require('mysql2/promise'); : mysql2 import하기

    • Mysql 내부 db와 connection(연결)하는 함수 생성

      • async/await 를 사용하기 위해 함수 db()를 생성하였지만, 특별히 async/await 기능을 사용하지 않는 경우라면, 함수 생성을 생략한 채 mysql.createConnection(); 메서드를 사용해도 무방하다.
      • mysql2 모듈 사용에 관한 내용 참고: mysql2 공식 문서
      • async/await

        async/await란?
        async와 await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다. 기존의 비동기 처리 방식인 콜백 함수프로미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와준다. 또한 동기와 비동기에서 발생하는 모든 에러try/catch를 통해서 처리할 수 있게 한다.

      var express = require('express');
      var app = express();
      var mysql = require('mysql2/promise');
      
      var server = app.listen(8000, () => 
          console.log('Start Server: localhosst:8000')
      );
      
      app.get('/hello', function(req,res) {
          // Mysql 내부 db와 연결하는 함수 생성
          const db = async () => {
              // try/catch로 예외처리
              try {
                  var connection = await mysql.createConnection({
                      host            : 'localhost',
                      user            : 'root',
                      password        : 'password',
                      database        : '데이터베이스 name',
                      multipleStatements: true
                  });
      
                  res.send('hello')
              } catch (e) {
                  console.log(e)
              }
          }
          // 함수 실행
          db()  
      });
      

# async/await 사용법

  • Mysql db와의 연결에서 async/await가 필요한 이유

    • Node.js에서 Mysql db와 연결하는 목적은, 쿼리문 실행시켜 조건에 맞는 데이터를 가져오기 위해서다. 쿼리문을 실행시기 위해선 mysql2 모듈에서 제공하는 execute(); 메서드를 사용해야하는데, 여기서 한 가지 문제점이 발생한다. 문제점이란 해당 메서드가 비동기 처리로 인해, 순서에 맞게(입력한 코드의 순서대로) 데이터를 가져오지 못하는 경우를 말한다. 이러한 문제를 해결하려면 async/await활용해야 한다. async/await 쿼리문 실행 메서드가 순차적으로 실행될 수 있도록 해주며, 더불어 가독성을 높이고, 예외처리를 쉽게 해준다.

    • mysql2 모듈에서 async/await를 사용하는 방법은 공식 문서에 잘 나와 있다.

async function main() {
  // mysql2 모듈 가져오기
  const mysql = require('mysql2/promise');
  // Mysql 내부 DB와 연결하기
  const connection = await mysql.createConnection({host:'localhost', user: 'root', database: 'test'});
  // execute() 메서드로 데이터 가져오기
  const [rows, fields] = await connection.execute('SELECT * FROM `table` WHERE `name` = ? AND `age` > ?', ['Morty', 14]);
}
  • execute()로 데이터 가져오기
    • execute() 메서드의 인자로 쿼리문을 입력한 뒤 실행하면, 결괏값(가져온 데이터)이 const [rows, fields]에서 변수로 지정한 rows에 할당된다. 사용자 입맛에 맞게 변수 rows를 처리하면 가져온 데이터 가공이 가능해진다.
  const [rows, fields] = await connection.execute('SELECT * FROM `table` WHERE `name` = ? AND `age` > ?', ['Morty', 14]);

# 내가 작성한 code

var express = require('express');
var app = express();
var mysql = require('mysql2/promise');

var server = app.listen(8000, () => 
    console.log('Start Server: localhosst:8000')
);

app.get('/subway', function(req,res) {
    const db = async () => {
        console.time();
        try {
            var connection = await mysql.createConnection({
                host            : 'localhost',
                user            : 'root',
                password        : 'password',
                database        : 'subway',
                multipleStatements: true
            });

            var name = req.query.station;

            if (!name) {res.send(JSON.stringify({message : 'NO DATA'}))}

            var sql = `SELECT sub_lines.name AS line, stations.*, names.name AS name, station_time_info.*, \
            names.name as name2 FROM subway_lines AS sub_lines, subway_stations AS stations, station_names \
            AS names, station_time_information AS station_time_info WHERE sub_lines.id = stations.subway_line_id \
            AND stations.station_id = station_time_info.station_id AND names.station_id = stations.station_id \
            AND (names.name LIKE '%${name}%' OR sub_lines.name LIKE '%${name}%' OR stations.station_code = \
            '${name}');`;

            var [station_data_list, fields] = await connection.query(sql)

        } catch (e) {
            console.log(e)
        }
        try {

            result = []

            for (var i in station_data_list) {

                if (station_data_list[i].is_transfer==1) { var is_transfer="Y";} else { var is_transfer='N';}
                if (station_data_list[i].is_upper == 1) { var is_upper = '상행선';} else { var is_upper = '하행선';}
                if (station_data_list[i].day_type == 1) { var day_type = '평일';} else if (station_data_list[i].day_type == 2) 
                { var day_type = '토요일';} else { var day_type = '일요일';}
                
                var sql = `SELECT DISTINCT names.name FROM station_names AS names JOIN station_time_information AS time_info ON \
                names.station_id =  '${station_data_list[i].first_train_terminus_id}' OR names.station_id  = '${station_data_list[i].last_train_terminus_id}' \
                WHERE names.language_type=1 AND time_info.station_id='${station_data_list[i].station_id}';`

                var [station_name, fields] = await connection.query(sql)

                if (station_data_list[i].first_train_terminus_id == '0000') {var first_train_terminus=null;} else {var first_train_terminus=station_name[0].name}
                if (station_data_list[i].last_train_terminus_id == '0000') {var last_train_terminus=null;} else {var last_train_terminus=station_name.pop().name}

                station_data = { 
                    subway_line       : station_data_list[i].line,
                    station_name      : station_data_list[i].name,
                    station_code      : station_data_list[i].station_code,
                    is_transfer       : is_transfer,
                    station_time_info : {
                        is_upper             : is_upper,
                        day_type             : day_type,
                        first_train_time     : station_data_list[i].first_train_time,
                        first_train_terminus : first_train_terminus,
                        last_train_time      : station_data_list[i].last_train_time,
                        last_train_terminus  : last_train_terminus
                    }
                };
                result.push(station_data)
            
            }
        } catch (e) {
            console.log(e)
        }
        try {
            
            console.log(result)
            console.timeEnd();
            res.send(JSON.stringify(result))

        } catch (e) {
            console.log(e)
        }
    }
    db()
});

# review

  • python의 리스트 조작 메서드인 pop()이 javascript에서도 사용할 수 있다는 걸 알게 됐다.

  • python의 append()와 비슷한 javascript의 메서드: push()

  • 코드 실행 시간 측정 메서드

    • console.time(); : 측정 시작
    • console.timeEnd(); : 측정 종료
  • 쿼리문은 되도록 단일 쿼리문으로 작성하는 것이 바람직하다. (서버에 부하를 가져오는 원인이 되기도 하므로...)

  • 쿼리문 가독성 높이기

    • 테이블 이름, 컬럼 이름을 가독성 높은 변수명으로 Alias 지정하기
    • 쿼리 문법과 관련된 코드(SELECT, WHERE, AND, OR, FROM 등)는 대문자작성하기
  • 유지 보수적 관점 필수

    • 데이터베이스 테이블을 생성할 때나, 코드를 짤 때 항상 추후 유지 및 보수가 쉽게 이뤄질 수 있는 코드인지따져보는 자세가 필요함
    • 테이블 생성 시 반드시 확장성을 고려해야 한다. 확장성이란 쉽게 말해, 추후 데이터의 수정, 삭제, 추가가 쉽게 이뤄질 수 있는 정도를 말한다. 확장성이 부족한 테이블 관계는 언제 터질지 모르는 시한폭탄과 같다. 이는 코드 작성에도 동일하게 적용되는 기준이니 명심하자!
profile
Difference & Repetition

0개의 댓글