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을 제작하는 경우
리얼 타임 / 실시간 기능이 필요한 app을 제작하는 경우
넷플릭스의 스트리밍이나 우버의 리얼타임을 구현해야 한다거나 자바스크립트 API를 가져와서 사용하는 경우라면 Node.js가 적합하다. 즉, 커스터마이징이 많이 필요한 경우 Node.js 사용이 권장된다!
정적 웹(Static Web Site) vs 동적 웹(Dynamic Web Site)
정적 웹이란?
언제 접속을 해도, 항상 같은 리소스를 제공하는 웹사이트
ex) 소개 페이지, 댓글이 없는 블로그 글
정적 웹의 경우 요청마다 동일한 HTML을 새로 생성하는 것은 비효율적이다. 이 경우엔 Node.js 사용이 바람직하다.
동적 웹이란?
접속할 때마다 서버에서 리소스를 가공해서 제공하는 웹사이트로, 가령 데이터베이스에서 값을 읽어 접속할 때마다 최신 정보를 제공한다.
ex) SNS
동적 웹사이트는 구동을 위해 웹 app 서버, 데이터베이스 같은 구성이 필수적이기 때문에 django가 적합하다.
Node.js 설치 및 버전 확인
brew install node
node -v
npm -v
npm이란?
자바스크립트 프로그래밍 언어의 패키지 관리자. 고퀄리티의 오픈 소스 모듈을 npm을 통해 다운로드 하는 것이 가능하다.
npm install 모듈 명
서버 생성
npm install express —save
: express 모듈 설치
touch server.js
: node.js 파일 생성
var express = require('express');
express 모듈 불러오기
var app = express();
: express 객체 생성 및 할당
var server = app.listen(포트번호, IP주소);
: 서버 생성
var server = app.listen(8000, () =>
console.log('Start Server: localhosst:8000')
);
npm install nodemon --save
: nodemon 설치
nodemon 파일명.js
: 서버 열기
라우팅하기
라우팅이란?
- 네트워크에서 주소를 이용하여, client 요청의 목적지까지의 경로를 체계적으로 결정하는 경로 선택 과정
- 쉽게 말해 client의 요청(request)이 서버의 특정 기능과 mapping될 수 있도록(대응될 수 있도록) 경로를 결정해주는 것을 말한다.
app.get('/hello', function(req,res) {
res.send('hello world')
})
DB 연결하기
npm install --save mysql2
: mysql2 모듈 설치
Promise란?
node.js에서 함수를 사용할 경우, 비동기 처리때문에 함수가 순차적으로 실행되지 않게 된다. 이러한 경우, 함수가 순서대로 처리되도록 보장해주는 기능이 바로 callback 함수였다. 하지만 callback 함수는 비동기 처리를 중첩시켜서 표현하여 에러의 원인이 되거나, 예외처리가 어려워지는 등의 문제점을 갖고 있었다. 이러한 단점을 해결하기 위해 생겨난 것이 promise다.
var mysql = require('mysql2/promise');
: mysql2 import하기
Mysql 내부 db와 connection(연결)하는 함수 생성
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()
});
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()
메서드의 인자로 쿼리문을 입력한 뒤 실행하면, 결괏값(가져온 데이터)이 const [rows, fields]
에서 변수로 지정한 rows에 할당된다. 사용자 입맛에 맞게 변수 rows를 처리하면 가져온 데이터 가공이 가능해진다. const [rows, fields] = await connection.execute('SELECT * FROM `table` WHERE `name` = ? AND `age` > ?', ['Morty', 14]);
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()
});
python의 리스트 조작 메서드인 pop()
이 javascript에서도 사용할 수 있다는 걸 알게 됐다.
python의 append()
와 비슷한 javascript의 메서드: push()
코드 실행 시간 측정 메서드
console.time();
: 측정 시작console.timeEnd();
: 측정 종료쿼리문은 되도록 단일 쿼리문으로 작성하는 것이 바람직하다. (서버에 부하를 가져오는 원인이 되기도 하므로...)
쿼리문 가독성 높이기
유지 보수적 관점 필수