Chrome V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임 환경
서버 사이드 애플리케이션 개발에 사용되는 소프트웨어 플랫폼
Node.js는 브라우저 외부 환경에서 자바스크립트 애플리케이션 개발에 사용되며
이에 필요한 모듈, 파일 시스템, HTTP 등 Built-in API를 제공한다.
node.js는 Non-blocking I/O 와 단일 스레드 이벤트 루프를 통한 높은 Request 처리 성능을 가지고 있다.
데이터베이스로부터 대량의 데이터를 취득하여 웹페이지에 표시할 때
일반적으로 데이터베이스 처리에 대기시간(blocking)이 발생하기 때문에 웹페이지 표시가 지연되는 현상이 발생한다.
기본적으로 node.js의 모든 API는 비동기 방식으로 동작하여 Non-blocking I/O이 가능하고
단일 스레드 이벤트 루프 모델을 사용하여 가벼운 환경에서도 높은 Request 처리 성능을 가지고 있다.
그러므로 데이터를 실시간 처리하여 빈번하게 I/O이 발생하는 SPA(Single Page Application)에 적합하고 CPU 사용률이 높은 애플리케이션에는 권장되지 않는다.
추가로 node.js는 Socket.io 라는 실시간 통신을 실현하는 라이브러리를 사용할 수 있기에 대량의 데이터 처리와 실시간 통신을 구현할 수 있는 기능을 갖추고 있다.
REPL (Read Eval Print Loop)
입력 수행 출력 반복은 Node.js는 물론 대부분의 언어가 제공하는 가상환경으로 간단한 코드를 직접 실행하여 결과를 확인할 수 있다.
터미널에 node를 입력하면 프롬프트가 > 로 변경되면서 node.js 코드를 실행해볼 수 있다.
해당 모드를 종료하고 싶다면 ctrl+c 를 눌러준다.
HTTP Server
node.js에는 http 서버 모듈을 기본적으로 내장하고 있어서 아파치 같은 별도의 웹서버를 설치할 필요가 없다.
const http = require('http');
http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World');
}).listen(4000);
모듈화
모듈이란 애플리케이션을 구성하는 개별적 요소를 의미한다.
일반적으로 파일 단위로 분리되어 있으며 필요에 따라 애플리케이션은 명시적으로 모듈을 로드한다.
모듈은 기능별로 분리되어 작성되므로 개발효율성돠 유지보수성의 향상을 가능케한다.
CommonJS
동기 방식으로 동작
문법이 AMD에 비해 비교적 간단
AMD
비동기 방식으로 동작
RequireJS
node.js 는 모듈 단위로 각 기능을 분할하여 모듈과 파일은 1:1 대응 관계를 가지며 하나의 모듈은 자신만의 독립적인 실행 영역을 가지게 된다. 그래서 클라이언트 사이드 자바스크립트와는 달리 전역 변수의 중복 문제가 발생하지 않는다.
모듈은 module.exports 또는 exports 객체를 통하여 정의하고 외부로 공개하며
-> 공개된 모듈은 require 함수를 사용하여 import 해준다.
exports
모듈은 독립적인 파일 scope 를 가지므로 모듈 안에 선언한 모든 것들은 기본적으로 해당 모듈 내부에서만 참조가 가능하다.
모듈 안에 선언한 항목을 외부에 공개하여 다른 모듈들이 사용할 수 있게 하고 싶다면, exports 객체를 사용해야 한다.
모듈은 파일로 작성하고 -> 외부에 공개할 대상을 exports 객체의 프로퍼티 또는 메서드를 정의
-> require 전역 함수를 이용하여 모듈을 추출하는 순서로 사용해준다.
module.exports
exports 객체는 프로퍼티 또는 메서드를 여러 개 정의할 수 있었지만,
module.exports에는 하나의 값(원시 타입, 함수, 객체)를 할당할 수 있다.
npm 지역 설치와 전역 설치
npm install 명령어에는 지역 설치와 전역 설치 옵션이 있다.
옵션을 별도로 지정하지 않으면 지역으로 설치가 되며, node_modules 디렉터리에 자동 설치된다.
지역으로 설치된 패키지는 해당 프로젝트 내에서만 사용할 수 있다.
전역에 패키지를 설치하려면 -g 옵션을 지정한다. npm install -g 패키지명
즉 모든 프로젝트가 공통 사용하는 패키지는 전역에 설치해준다.
전역에 설치된 패키지는 mac의 경우, /usr/local/lib/node_modules 에 설치된다.
install -> i
--save -> -S : 개발과 배포 전부에 넣어줄 패키지
--save-dev -> -D : 개발에만 넣어줄 패키지
자주 사용하는 npm 터미널 명령어 모음
$ npm init
# 기본 설정
$ npm init -y
# 로컬 설치
$ npm install <package-name>
# 전역 설치
$ npm install -g <package-name>
# 개발 설치
$ npm install --save-dev <package-name>
# package.json의 모든 패키지 설치
$ npm install
# 로컬/개발 패키지 제거
$ npm uninstall <package-name>
# 전역 패키지 제거
$ npm uninstall -g <package-name>
# 패키지 업데이트
$ npm update <package-name>
# package.json script 프로퍼티의 start 실행
$ npm start
# package.json scripts 프로퍼티의 start 이외의 scripts 실행
$ npm run <script-name>
express
npm init -y 로 package.json 생성
npm install express 로 express 설치
const express = require('express');
const app = express();
HTTP request method
1. GET
2. POST
3. PUT
4. DELETE
etc..
body parsing
npm install body-parser
body parser 미들웨어는 페이로드(POST 요청 데이터와 같이 request message의 body에 담겨 보내진 데이터)를 request 객체의 body 프로퍼티에 바인딩한다.
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended : fasle }));
app.use(bodyParser.json());
app.all( )
모든 HTTP method 에 대응
next( ) 를 사용하면 후속 router handler로 제어를 전달할 수 있다.
Route path
문자열 또는 정규 표현식을 사용할 수 있다.
// localhost:3000/
app.get('/', (req, res) => res.send('root'));
// localhost:3000/tmp.txt
app.get('/tmp.txt', (req, res) => res.send('tmp.txt'));
// localhost:3000/<number>
app.get(/^\/[0-9]+$/, (req, res) => res.send('regexp'));
// localhost:3000/user/<userId>/item/<itemId>
app.get('/user/:userId/item/:itemId', (req, res) => {
const { userId, itemId } = req.params;
res.send(`userId: ${userId}, itemId: ${itemId}`);
});
Route handler
요청을 처리하는 콜백함수
+ error
1. request
2. response
3. next
매개변수가 3개 이하인 경우, 해당 순서로 매개변수값이 들어가고
매개변수가 4개가 되었을 때, 가장 처음에 error가 들어가진다.
Response method
// 다운로드될 파일을 전송
res.download()
// 응답 프로세스를 종료
res.end()
// JSON 응답을 전송
res.json()
// JSONP 지원을 통해 JSON 응답을 전송
res.jsonp()
// 요청 경로를 재지정 ---> 받은 요청 메서드 그대로
res.redirect()
// view engine 으로 화면을 렌더링
res.render()
// 다양한 유형의 응답을 전송
res.send()
// 파일을 옥텟 스트림(이메일이나 http에서 사용되는 content-type에서 application의 형식이
// 지정되어 있지 않은 경우, octet-stream) 형태로 전송
res.sendFile()
// 응답 상태 코드를 설정한 후 해당 코드를 문자열로 표현한 내용을 응답 본문으로서 전송
res.sendStatus()
Middleware
미들웨어 함수는 요청 오브젝트(req), 응답 오브젝트(res) 그리고 어플리케이션의 request response cycle 내에서 다음 미들웨어 함수에 대한 액세스 권한을 가지는 함수이다.
미들웨어에는 유용한 동작을 하거나 요청이 실행되는데 도움이 되는 무언가를 추가하는 pass-through 함수가 있다.
예를 들어, bodyParser와 cookieParser 는 각각 HTTP 요청 페이로드(req.body)와 쿠키 데이터(req.cookie) 를 추가해준다.
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const app = express();
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.urlencoded({ extended : true }); // parse application/x-www-form-urlencoded
app.use(cookieParser());
현재 미들웨어 함수가 요청-응답 주기를 종료하지 않는 경우 next를 호출하여 그 다음 미들웨어 함수에 제어를 전달해야한다. 그렇지 않으면 해당 요청은 정지된 채로 방치된다.
Template Engine(View Engine)
1. ejs
2. pug(jade)
3. handlebars
app.set('view engine', 'ejs');
Error 핸들링
Web application 은 에러가 발생하였을 경우 반드시 client에 에러 관련 정보를 알려야 한다.
1. Client 에러 -> 부적절한 input data
2. Server 에러 -> 코드 내의 버그
app.use((err, req, res, next) => {
console.log(err.stack);
res.status(500).send({ status: 500, message: '에러 메세지', type: 'internal' });
});
HTTP StatusCode (HTTP 응답 상태 코드)
100번대 (정보)
요청을 받았으며 프로세스(작업)를 계속한다.
100 (계속) | 요청자는 요청을 계속해야 한다. 서버는 이 코드를 제공하여 요청의 첫번째 부분을 받았으며 나머지를 기다리고 있음을 나타낸다. |
101 (프로토콜 전환) | 요청자가 서버에 프로토콜 전환을 요청했으며 서버는 이를 승인하는 중이다. |
102 (처리) | RFC 2518 |
200번대 (성공)
요청을 성공적으로 받았으며 인식하였고 수용했다.
200 (성공) | 서버가 요청을 제대로 처리했다는 뜻 주로 서버가 요청한 페이지를 제공했다는 의미 |
201 (작성됨) | 성공적으로 요청되었으며 서버가 새 리소스를 작성했다. |
202 (허용됨) | 서버가 요청을 접수했지만 아직 처리하지 않았다. |
203 (신뢰할 수 없는 정보) | 서버가 요청을 성공적으로 처리했지만 다른 소스에서 수신된 정보를 제공하고 있다. |
204 (컨텐츠 없음) | 서버가 요청을 성공적으로 처리했지만 컨텐츠를 제공하지 않는다. |
205 (컨텐츠 재설정) | 서버가 요청을 성공적으로 처리했지만 컨텐츠를 표시하지 않는다. 요청자가 문서 보기를 재설정할 것을 요구 |
206 (일부 컨텐츠) | 서버가 GET 요청의 일부만 성공적으로 처리했다. |
207 (다중 상태) | RFC 4918 |
208 (이미 보고됨) | RFC 5842 |
226 | IM Used / RFC 3229 |
300번대 (리다이렉션)
클라이언트 요청 완료를 위해 추가 작업 조치가 필요하다.
300 (여러 선택항목) | 서버가 요청에 따라 여러 조치를 선택할 수 있다. 서버가 사용자 에이전트에 따라 수행할 작업을 선택하거나 요청자가 선택할 수 있는 작업 목록을 제공한다. |
301 (영구 이동) | 요청한 페이지를 새 위치로 영구적으로 이동했고 GET 또는 HEAD 요청에 대한 응답으로 이 응답을 표시하면 요청자가 자동으로 새 위치로 전달된다. |
302 (임시 이동) | 현재 서버가 다른 위치의 페이지로 요청에 응답하고 있지만 요청자는 향후 요청 시 원래 위치를 계속 사용해야 한다. |
303 (기타 위치 보기) | 요청자가 다른 위치에 별도의 GET 요청을 하여 응답을 검색할 경우 서버는 이 코드를 표시한다. HEAD 요청 이외의 모든 요청을 다른 위치로 자동으로 전달한다. |
304 (수정되지 않음) | 마지막 요청 이후 요청한 페이지는 수정되지 않았다. 서버가 이 응답을 표시하면 페이지의 컨텐츠를 표시하지 않는다. 요청자가 마지막으로 페이지를 요청한 후 페이지가 변경되지 않으면 이 응답을 표시하도록 서버를 구성해야 한다. |
305 (프록시 사용) | 요청자는 프록시를 사용하여 요청한 페이지만 액세스 할 수 있으며 서버가 이 응답을 표시하면 요청자가 사용할 프록시를 가리키는 것이다. |
307 (임시 리다이렉션) | 현재 서버가 다른 위치의 페이지로 요청에 응답하고 있지만 요청자는 향후 요청 시 원래 위치를 계속 사용해야한다. |
308 (영구 리다이렉션) |
400번대 (클라이언트 오류)
요청의 문법이 잘못되었거나 요청을 처리할 수 없다.
400(잘못된 요청) | 서버가 요청의 구문을 인식하지 못했다. |
401(권한 없음) | 이 요청은 인증이 필요하다. 서버는 로그인이 필요한 페이지에 대해 이 요청을 제공할 수 있다. 상태 코드 이름이 권한 없음(Unauthorized)으로 되어 있지만 실제 뜻은 인증 안됨(Unauthenticated)에 더 가깝다. |
402(결제 필요) | 이 요청은 결제가 필요합니다. |
403(Forbidden, 금지됨) | 서버가 요청을 거부하고 있다. 예를 들자면, 사용자가 리소스에 대한 필요 권한을 갖고 있지 않다. |
404(Not Found, 찾을 수 없음) | 서버가 요청한 페이지(Resource)를 찾을 수 없다. 예를 들어 서버에 존재하지 않는 페이지에 대한 요청이 있을 경우 서버는 이 코드를 제공한다. |
405(허용되지 않는 메소드) | 요청에 지정된 방법을 사용할 수 없다. 예를 들어 POST 방식으로 요청을 받는 서버에 GET 요청을 보내는 경우, 또는 읽기 전용 리소스에 PUT 요청을 보내는 경우에 이 코드를 제공한다. |
406(허용되지 않음) | 요청한 페이지가 요청한 콘텐츠 특성으로 응답할 수 없다. |
407(프록시 인증 필요) | 이 상태 코드는 401(권한 없음)과 비슷하지만 요청자가 프록시를 사용하여 인증해야 한다. 서버가 이 응답을 표시하면 요청자가 사용할 프록시를 가리키는 것이기도 한다. |
408(요청 시간초과) | 서버의 요청 대기가 시간을 초과하였다. |
409(충돌) | 서버가 요청을 수행하는 중에 충돌이 발생했다. 서버는 응답할 때 충돌에 대한 정보를 포함해야 한다. 서버는 PUT 요청과 충돌하는 PUT 요청에 대한 응답으로 이 코드를 요청 간 차이점 목록과 함께 표시해야 한다. |
410(사라짐) | 서버는 요청한 리소스가 영구적으로 삭제되었을 때 이 응답을 표시한다. 404(찾을 수 없음) 코드와 비슷하며 이전에 있었지만 더 이상 존재하지 않는 리소스에 대해 404 대신 사용하기도 한다. 리소스가 영구적으로 이동된 경우 301을 사용하여 리소스의 새 위치를 지정해야 한다. |
411(길이 필요) | 서버는 유효한 콘텐츠 길이 헤더 입력란 없이는 요청을 수락하지 않는다. |
412(사전조건 실패) | 서버가 요청자가 요청 시 부과한 사전조건을 만족하지 않는다. |
413(요청 속성이 너무 큼) | 요청이 너무 커서 서버가 처리할 수 없다. |
414(요청 URI가 너무 긺) | 요청 URI(일반적으로 URL)가 너무 길어 서버가 처리할 수 없다. |
415(지원되지 않는 미디어 유형) | 요청이 요청한 페이지에서 지원하지 않는 형식으로 되어 있다. |
416(처리할 수 없는 요청범위) | 요청이 페이지에서 처리할 수 없는 범위에 해당되는 경우 서버는 이 상태 코드를 표시한다. |
417(예상 실패) | 서버는 Expect 요청 헤더 입력란의 요구사항을 만족할 수 없다. |
429(너무 많은 요청) | 사용자가 일정 시간 동안 너무 많은 요청을 보냈다. |
499(클라이언트가 요청을 닫음, Nginx) |
500번대 (서버 오류)
서버가 명백히 유효한 요청에 대해 수행하지 못했음을 나타낸다.
500(내부 서버 오류) | 서버에 오류가 발생하여 요청을 수행할 수 없다. |
501(구현되지 않음) | 서버에 요청을 수행할 수 있는 기능이 없다. 예를 들어 서버가 요청 메소드를 인식하지 못할 때 이 코드를 표시한다. |
502 (Bad Gateway, 불량 게이트웨이) | 서버가 게이트웨이나 프록시 역할을 하고 있거나 또는 업스트림 서버에서 잘못된 응답을 받았다. |
503(서비스를 사용할 수 없음) | 서버가 오버로드되었거나 유지관리를 위해 다운되었기 때문에 현재 서버를 사용할 수 없다. 이는 대개 일시적인 상태이다. |
504(게이트웨이 시간초과) | 서버가 게이트웨이나 프록시 역할을 하고 있거나 또는 업스트림 서버에서 제때 요청을 받지 못했다. |
505(HTTP 버전이 지원되지 않음) | 서버가 요청에 사용된 HTTP 프로토콜 버전을 지원하지 않는다. |
509(대역폭 제한 초과) | |
511(네트워크 인증 필요) | |
520(Unknown Error, 알 수 없음) | |
598(네트워크 읽기 시간초과 오류, 알 수 없음) 599(네트워크 연결 시간초과 오류, 알 수 없음) |
Web application 은 로그인 인등 등의 용도로 Session을 사용한다. Express 는 메모리 상(MemoryStore)에 Session data를 저장할 수 있다.
개발을 위한 MemoryStore의 사용은 문제될 것이 없지만 production 환경에서 MemoryStore의 사용은 적절하지 않으며 복수 서버 상에서의 Session data 공유도 MemoryStore에서는 불가능하다.
그러므로 production 환경에서는 Redis, MongoDB를 사용하여 영속적 Session data 관리하는 것이 일반적이다.
HTTP Stateless Protocol
http 프로토콜은 상태(state)를 유지하지 않는다.
이를 stateless protocol 이라고 한다.
HTTP 프로토콜은 request 를 전송하고 response 를 전송받은 시점에서 통신이 종료되며 어떠한 상태 정보도 남지 않는다.
즉, 특정 클라이언트에서 동일 서버에 반복하여 접속하여도 각각의 접속은 독립적인 트랜잭션으로 취급된다.
---> 로그인 화면에서 아이디, 패스워드를 입력하여 사용자 인증 과정을 거친 이후에 재차 웹사이트에 접근하면 로그인 상태임을 인식 할 수 없으므로 매번 사용자 인증 과정을 반복해야 하는 문제가 발생한다.
http 프로토콜의 상태 statelsee 문제를 보완하여 클라이언트와 서버 간의 논리적 연결을 위한 방법에는 Session, Cookie 가 있다.
Cookie
쿠키는 웹서버가 브라우저를 통해 클라이언트에 일시적으로 데이터를 저장하는 방식을 의미
웹서버에 접속한 클라이언트의 정보를 쿠키에 기입한 뒤 클라이언트에 저장하면 이후 웹서버에 전송되는 요청에 쿠키내의 정보가 같이 전송되는 방식이다. 이전 접속한 url과 다른 url에서는 쿠키를 사용할 수 없다.
쿠키는 클라이언트(브라우저)에 저장된 작은 조각(~4kb)의 텍스트 파일로서 세션에 비해 보안에 취약하므로 아이디와 비밀번호를 쿠키에 저장하는 방식은 피해야 한다.
Session
세션은 웹서버에 접속되어 있는 상태를 의미한다. 즉 브라우저를 통해 서버에 접속한 뒤 접속을 종료하는 시점까지를 의미한다.
세션은 최초 접속 시점에서 생성되어 일정 기간 유지되며 접속이 종료되면 삭제된다.
쿠키와는 달리 세션은 서버에 저장되므로 쿠키보다 보안상 안전하다.
Cookie-Based Session
쿠키에는 Session ID만을 저장하고 전송된 Session ID를 사용하여 Session 에 저장된 정보를 사용하는 방식이다.
MemoryStore 을 사용한 세션 관리
express-session 미들웨어가 express에서 분리되었으므로 별도의 npm 설치가 필요
npm install express-session 로 설치
var express = require('express');
var session = require('express-session');
var app = express();
// session 설정
app.use(session({
secret : 'Rs89I67YEA55cLMgi0t6oyr8568e6KtD',
resave: false,
saveUninitialized: true
}));
// routing 설정
app.get('/memory-store-counter', function(req, res) {
var session = req.session;
if (session && session.count) {
session.count++;
} else {
session.count = 1;
}
res.send('count is ' + session.count);
});
app.get('/session-destroy', function (req, res) {
req.session.destroy();
res.send('Session Destroyed!');
});
app.listen(3000, function() {
console.log('Express server listening on port ' + 3000);
});
session secret 는 쿠키에 저장될 Session ID 서명에 사용되며, 랜덤한 일정 길이 이상의 문자열을 사용하는 것이 좋다.
세션 데이터는 쿠키에 저장되지 않고 server-side 에 저장된다. 쿠키에는 session ID만이 저장된다.
Redis 를 사용한 세션 관리
Redis는 in-memory data structure store로 key-value 형태로 data를 저장한다.
MongoDB와 같은 NoSQL으로 세션 등의 휘발성 데이터를 저장하는 용도로 사용된다.
백업 용도로 디스크에 데이터를 주기적으로 저장하기는 실제 데이터는 메모리상에 저장되기 때문에 빠른 속도를 자랑한다.
Redis를 사용하여 세션을 관리하면 application process가 종료되어도 세션 정보를 보존할 수 있고 복수 서버 환경에서도 세션 정보 공유가 가능하게 된다.
brew install redis 설치
redis-server 로 서버를 가동
npm i connect-redis 모듈 connect-redis을 설치
var express = require('express');
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
var app = express();
// session 설정
app.use(session({
store: new RedisStore({}),
secret : 'Rs89I67YEA55cLMgi0t6oyr8568e6KtD',
resave: false,
saveUninitialized: true
}));
// routing 설정
app.get('/radis-store-counter', function(req, res) {
var session = req.session;
if (session && session.count) {
session.count++;
} else {
session.count = 1;
}
res.send('count is ' + session.count);
});
app.get('/session-destroy', function (req, res) {
req.session.destroy();
res.send('Session Destroyed!');
});
app.listen(3000, function() {
console.log('Express server listening on port ' + 3000);
});
node.js 와 MySQL 연동
const mysql = require('mysql');
const connection = mysql.createConnection({
host : 'localhost',
user : '< MySQL username >',
password : '< MySQL password >',
database : 'my_db'
});
connection.connect();
connection.query('SELECT * from Users', (error, rows, fields) => {
if (error) throw error;
console.log('User info is: ', rows);
});
connection.end();
mysql.createConnection() 메서드로 인자로 전달되는 객체에 자신의 데이터베이스 정보(host, user, password, database 등)를 입력해줘야 한다.
클라이언트 요청에 대응하는 route를 설정하기 위해
const express = require('express');
const mysql = require('mysql');
const dbconfig = require('./config/database.js');
const connection = mysql.createConnection(dbconfig);
const app = express();
app.set('port', process.env.PORT || 3000);
app.get('/', (req, res) => {
res.send('Root');
});
app.get('/users', (req, res) => {
connection.query('SELECT * from Users', (error, rows) => {
if (error) throw error;
console.log('User info is: ', rows);
res.send(rows);
});
});
app.listen(app.get('port'), () => {
console.log('Express server listening on port ' + app.get('port'));
});
'Web Study > node.js' 카테고리의 다른 글
Express 응답 - res.json, res.end, res.send (0) | 2023.08.18 |
---|---|
PostgreSQL 과 Node.js (0) | 2023.08.10 |