Express란 Node.js 상에서 구동되는 웹 개발 프레임워크이다.
웹 개발에 필요한 기능들로만 구성하여 가벼운 Application 개발을 가능하게 하고 jQuery와 유사하게 기존의 문법을 간결하게하여 생산력을 높인다는 장점을 가지고 있다.
최소한의 기능만 탑재하여 가벼운 어플리케이션 개발을 가능하게 한다.
키, 세션, 사용자 로그인, URL 파라미터, POST 데이터, 보안 헤더 등 사실상 필요한 기능들을 모두 미들웨어 로 구성해두었다.
미들웨어 란, OS에서 제공하는 service이외에 사용할 수있는 service를 제공하는 software를 말한다. node.js에서는 Client - Server사이에서 Server에 접근하지 않고도 데이터교환이 가능하게끔 구현되는 software, function 이다. ( 주로 구현하는 방법은 함수의 형태일 것이다. )
Http module을 interface로 구현하기 때문에 직접적인 학습이 필요하지 않다. module doc가 매우어렵다
사용법
node.js가 컴퓨터에 있을 때 사용할 directory에서 module을 다운받자.
> npm init
> npm install express
그리고 module을 사용하기 위한 가벼운 예제하나 해보자.
const express = require('express');
var app = express();
app.listen('8080');
우리는 서버객체하나 만들었다. 이걸 http 모듈로 해보자.
const http = require('http');
const server = http.creatServser((req, res)=>{
//do something on req, send res
});
server.listen('8080');
지금은 별차이 없어보이지만 개발을 시작하면서 그 차이가 극명하게 갈린다. ( express
를 python의 flask
라고 생각하면 쉽겠다. )
고려사항
express 에서 관용적으로 server객체를 app
이라고 명한다.
‘mount’
현재의 서버객체 app
이 다른 app
이나 router
에 mount될 때 발생하는 이벤트.
마운트란 use() 를 통해 해당경로에 결합되는 것을 의미한다.
var app = express();
var subApp = express();
//eventemitter와 동일하게 mount event에 대한 리스너 정의
subApp.on('mount', ()=>{
console.log('마운트 되는 시점입니다.');
});
// localhost:8080/sub 라는 경로에는 subApp을 사용.
console.log('마운트 되기 전입니다.');
app.use('/sub', subApp);
console.log('마운트 되고난 후입니다.');
/* output
마운트 되기 전입니다.
마운트 되는 시점입니다.
마운트 되고난 후입니다.
*/
app.locals
해당 app
이 사용하는 로컬변수들의 영역으로 app
의 생명주기와 동일한 생명주기를 가진다. 그러므로 server가 켜진 내내 사용될 수 있으므로 전역변수 의 영역으로 볼 수 있다.
var app = express();
app.locals.developerName = 'chanwoo';
// url 리스너에서도 사용이 가능하다.
app.get('/', function(req, res){
console.log(app.locals.developerName);
res.send();
});
console.log(app.locals.developerName);
app.listen('8080');
/*output
chanwoo
chanwoo
*/
app.mountpath
현재 서버객체가 마운트가 된 url 경로를 반환한다.
흔히 사용하는 req.baseUrl 과 다른점은 마운트되는 경로가 pattern일 때 app.mountpath 는 pattern을 반환하는 반면, req.baseUrl 은 pattern에 매칭되는 url 경로를 반환한다.
var app = express();
var subApp = express();
app.use('/sub', subApp);
console.log(subApp.mountpath);
app.listen('8080');
/* output
/sub
*/
path
경로를 정의하지 않을 경우, 모든 경로에 대한 url 리스너가 등록됨.
callback
여러 개일 경우, 정의된 순서대로 진행된다. 인자는 req, res, next 를 받으며, 다음 함수로 넘어가기위해서 인자로 받은 next() 를 호출한다.
next(‘route’) 를 통해 다른
route
로 넘어갈 수 있다.
app
객체의 설정 관련app.enable(name) & app.disable(name)
app
의 설정 중 name 이라는 이름의 boolean
타입 설정을 변경하는데 사용한다.
app.enabled(name) & app.disabled(name)
app
의 설정 중 name 이라는 이름의 boolean
타입의 현재 설정을 확인하고자 할 때 사용한다.
app
내부 프로퍼티 관련app.get(name)
app
의 내부 프로퍼티 name 을 호출할 때 사용한다.
app.set(name, value)
app
의 내부 프로퍼티 name 의 값을 설정할 때 사용한다.
app.all(path, [,callback ])
path 에 대한 모든 Method 의 요청 시, callback 함수를 url 리스너로 추가한다.
callback 함수는 반드시 다음 라우팅을 위해 인자로 받은 next 를 호출시켜야한다.
주로 해당 app
이나 router
로 요청할 경우, 반드시 선행되어야하는 함수나 미들웨어를 추가하기 위해 사용한다.
var app = express();
app.all('*', (req, res, next)=>{
console.log('모든 요청에 대해 선행되는 all method');
next();
})
app.get('/', (req, res)=>{
console.log('라우팅 되었습니다.');
});
app.listen('8080');
/* output
모든 요청에 대해 선행되는 all method
라우팅 되었습니다.
*/
app.use(path, [,callback ])
path 에 대해 1) callback 을 url 리스너로 등록하거나 2) router
, app
마운트할 때, 3) midlleware
를 추가하고자 할 때 사용한다.
callback 은 단일 함수 객체, 미들웨어, 함수들의 체인( , , ), 함수 객체의 배열 일 수 있다.
callback 함수, 미들웨어는 반드시 다음 라우팅을 위해 인자로 받은 next 를 호출시켜야한다.
var app = express();
var mymiddle = function(req, res, next){
console.log('사용되는 미들웨어입니다.');
next();
}
//유저가 구현한 미들웨어를 모든 경로에 대해 연결하였다. (경로를 작성하지 않았으므로)
app.use(mymiddle);
app.get('/', (req, res)=>{
console.log('라우팅 되었습니다.');
res.send();
});
app.listen('8080');
/* output
사용되는 미들웨어입니다.
라우팅 되었습니다.
*/
app.METHOD(path, [, callback])
path 에 대한 METHOD의 접근에 callback 을 url 리스너로 연결한다.
app
객체이므로 method chain 을 통한 다양한 METHOD에 대한 리스너 정의가 가능하다.app.route(path)
path 에 대한 모든 METHOD의 app
객체를 반환한다.
// route(), method chain을 통한 다양한 method에 다중 정의.
app.route('/creator')
.get(function(req, res, next){
console.log('크리에이터 로그인 페이지입니다.');
res.send('creator login page');
})
.post( function(req, res, next){
console.log('크리에이터 로그인 post method입니다.');
res.send('creator login page');
});
app.param([name], callback)
:(와일드카드)를 통해 생성되는 url parameter 인 name 들이 요구되는 요청이 발생되었을 때, callback 을 url 리스너로 추가한다.
[name] 인 것처럼 여러개의 인자에 대한 정의가 가능한데, 여러개의 인자의 경우, 각 하나의 인자에 바인딩 된다. (즉, 하나의 인자만 만족하더라도 callback 이 호출된다.)
호출되는 callback 함수는 인자로 받는 next 를 호출하여 req-res 주기를 종료시키지 않도록 주의한다.
name 으로 생성되는 url parameter 는 모두 req.params
에 존재하게 되므로, 접근시에 req.params.name
으로 접근할 수 있다.
동일한 형태의 경로에 대한 url 리스너를 추가할 때 : 가 존재하는 url 리스너가 req-res 주기를 종료시키기 이전에 정의해야한다. (예제 참고)
var app = express();
// id 라는 url 파라미터에 대한 url 리스너 추가.
app.param('id',(req, res, next, id)=>{
console.log('param 함수 수행');
console.log(id);
next();
});
// /:id에 대한 get 요청을 받는 url 리스너
app.get('/:id', function(req, res) {
console.log(req.params.id);
res.send('인자가 존재하는 page');
res.end();
});
// 위의 와일드카드로 req-res 주기를 종료시키므로 요청이 들어오지 못한다.
app.get('/admin', function(req, res) {
console.log(req.params.id);
res.send('인자가 존재하는 page2');
res.end();
});
app.listen('8080');
/* localhost:8080/chanwoo
param 함수 수행
chanwoo
chanwoo
*/
/* localhost:8080/admin
param 함수 수행
admin
admin
*/
app.render(view, [lacals], callback)
추후 업데이트
router
, app
마운트 가능위의 기능은 router
객체가 가지는 기능인데, mini- application 으로 불릴만큼 server 객체인 app
과 동일한 기능을 가진다. 이 객체를 사용하는 이유는 routing 의 모듈화가 가능하게끔 하기 때문이다.
모든 기능은 app
의 method 와 동일하되 객체가 router
로 변경된 것임을 명시하며 설명을 마친다.
route.all(path, [, callback])
route.use(path, [, callback])
route.METHOD(path, [, callback])
route.param([name], callback)
route.route(path)
///////////////
// router.js //
///////////////
const express = require('express');
var router = express.Router();
router.get('/creator', function(req, res, next){
console.log('크리에이터 로그인 페이지입니다.');
res.send('creator login page');
});
module.exports = router;
위의 파일처럼 단일 기능에 대한 라우팅이 구현된 router
객체를 모듈화 시킨다.
///////////////
// server.js //
///////////////
var app = express();
const loginRoute = require('./router.js');
app.get('/', function(req, res){
res.send('home page');
res.end();
});
// 모듈화로 생성된 router 객체를 use()를 통해 /login url에 대한 url 리스너로 추가(mount) 한다.
app.use('/login', loginRoute);
app.listen('8080');
모듈화된 router.js 를 불러와 사용하게 한다. 이 것을 통해 기능마다 url 경로를 세분화하여 모듈화하고 독립적인 개발환경을 구축할 수 있다.
req 객체는 http 모듈의 IncommingMessage
객체인 request
의 추상화 객체이다. 그러므로 http 모듈에 대한 학습은 조금 미뤄두도록 하자. (하지만 공부할 수록 필요성이..)
client - server의 통신 절차 중에서 첫 절차는 두 객체 간의 형식을 결정한다. 이러한 결정을 위해서 req 는 타입 체크 관련 method와 property를 가지고 있다.
req 객체의 경우, server 입장에서는 읽어들어야할 data의 집합이므로 method보다는 property가 많다.
HTTP request Header의 형식을 공부할 필요가 있다.
현재 개발에 필요한 수준까지의 학습을 정리하고자 한다.
req.app
req 을 받는 app
이나 router
객체를 반환한다. 이것을 통해 app
의 property나 method를 callback 함수 내에서 사용할 수 있다.
req.protocol
req 가 server에 전달되는 프로토콜명
req.hostname
req 가 server에 전달되는 호스트명
req.orignalUrl
req 가 server에 전달되는 url 풀 주소
mount 된 지점의 url 까지 모두 결합된 주소를 반환한다.
req.baseUrl
req 가 server에 전달되는 url 주소
app
이나route
가 mount 되는 시점부터의 url 주소
지금까지 url 관련 property의 예제를 살펴보자.
///////////////
// router.js //
///////////////
const express = require('express');
var router = express.Router();
router.route('/')
.get(function(req, res, next){
console.log(req.originalUrl);
console.log(req.baseUrl);
res.send('login main page');
})
router.route('/creator')
.get(function(req, res, next){
console.log(req.protocol);
console.log(req.hostname);
console.log(req.originalUrl);
console.log(req.baseUrl);
res.send('creator login page');
})
module.exports = router;
///////////////
// server.js //
///////////////
const express = require('express');
const loginRoute = require('./router.js');
var app = express();
app.get('/', function(req, res) {
res.send('Admin page');
res.end();
});
app.get('/user/:id', function(req, res){
console.log(req.protocol);
console.log(req.hostname);
console.log(req.originalUrl);
console.log(req.baseUrl);
res.send('로그인시 page');
res.end();
});
app.use('/login', loginRoute);
app.listen('8080');
/* localhost:8080/user/chanwoo
http (protocol)
localhost (hostname)
/user/chanwoo (originalUrl)
(baseUrl)
*/
/* localhost:8080/login
/login (originalUrl)
/login (baseUrl)
*/
/* localhost:8080/login/creator
http (protocol)
localhost (hostname)
/login/creator (originalUrl)
/login (baseUrl)
*/
req.params
url parameter 들을 key-value 형태로 들고있는 객체를 가리킨다.
req.query
get 방식의 url 에 추가되는 쿼리값들을 key-value 형태로 들고있는 객체를 가리킨다.
주의할 것은 이 value들에 대해 값이 정확한 것인지 체크하는 순서가 존재해야한다.
const express = require('express');
var app = express();
//param은 와일드카드를 통한 패턴일치에 리스너를 추가시킴.
//그러므로 callback을 호출하지 않음.
app.param('id',(req, res, next, id)=>{
console.log('param 함수 수행');
console.log(id);
next();
});
app.get('/', function(req, res, next){
console.log(req.query);
res.send('메인페이지');
})
app.listen('8080');
// 디폴트는 {} 이다.
/* localhost:8080/
{}
*/
/* localhost:8080/?id=chanwoo&pw=1234
{ id: 'chanwoo', pw: '1234' }
*/
req.body
body_parser middleware 사용시 파싱할 수 있는 body 를 반환한다.
body_parser 미들웨어 학습 이후 재 작성필요.
req.fresh
req.stale
req.cookies
req.signedCookies
req.accept(types)
HTTP header 중에서 ‘Accept’ 의 필드값에 가장 잘 매칭되는 type 을 반환한다.
req.get(field)
HTTP header 필드값을 반환한다. (case-insentive match)
req.is(type)
HTTP header 중에서 ‘Content-Type’ 의 필드값에 type 이 적절한가 확인한다.
server에서 req 에 대한 응답으로 browser에 전송하는 객체이다.
browser는 전달받은 res 에 대한 행위를 정의해야 하는데 이 것을 정의하기 위해 res 의 형식인 MIME type 을 미리 약속해 두었다. 이 타입을 res 의 header 중 ‘Content-Type’ 의 값으로 정의함으로 browser는 res 를 적절히 사용할 수 있다.
MINE type
http 상에서 통신되는 문서의 다양성을 미리 정의해둔 것을 말한다. 주로 사용하는 것들만 몇 개 소개하고자한다.
application/ octet-stream
이진파일을 위한 기본값으로 ‘Content-Type’ 의 필드값이 attachment
로 설정된다.
text/ plain
텍스트 파일의 기본값
text/ html
html 파일의 기본값이자 기본 콘텐츠 타입으로 res.send(body) 를 사용할 때 default 값으로 정의된다.
res 객체는 browser가 필요한 1) header 를 정의하거나 변경하는 method, 2) data 의 관련 method, 마지막으로 3) rendering 을 위한 method 까지 property 보다는 method가 많다.
res.locals
app.locals 가 가진 cycle lifetime(req-res cycle) 과 동일한 생명주기를 가지는 변수 영역.
res.headerSent
현재 res 객체의 HTTP response header 가 제출되어 전송 중인지의 여부를 확인한다.
res.send(body)
‘Content-Type’ 의 값은 ‘text/ html’ 으로 text 파일이나 정적인 html 파일 을 전달한다.
res.redirect([status], url)
url 로 리다이렉팅 시킨다.
res.status(code)
response.statusCode 의 값을 code 로 변경한다. 해당 코드를 통해 browser는 통신의 상태를 확인할 수 있다.
res.set(field, [, values] )
res 의 HTTP response header 의 field 값을 values 로 변경한다. 다수의 field 값을 변경하고자 할 때는 object 형식으로 values 를 전달할 수 있다.하지만 직접 사용할 일은 거의없다.
res.append(field, [, values] )
res.set() 과 동일한 기능을 가졌다. 둘 간의 차이 추후 추가
res.type(type)
‘Content-Type’ 의 값을 type 으로 변경한다.
res.cookies(name, value, [, option] )
cookie-parser 라는 미들웨어 사용시 사용하는 method이다.
res.json([status], json)
응답에 대해 json 객체를 전달하는 것으로 대체한다.
res.attchment([filename])
HTTP response header 의 field 중 ‘Content-Disposition’ field 값은 “attachment” 로 변경하여 다운로드 가능한 파일이 존재함을 browser에 알린다.
Content-Disposition: attachment; filename="logo.png"
의 형태로 파일명이 추가된다.res.sendfile(path, [option], [callback])
path 파일을 읽어 browser가 다운로드하는 것으로 응답을 대체한다.
router
로 전달되게끔 req-res cycle 을 종료하는 역할을 한다.res.render(view, [locals], callback)
파일의 경로인 view 를 locals 와 함께 rendering하여 전달한다.
미리 정의해둔 engine 을 통해 view 를 rendering 하게 된다. 이 engine 은 template engine 으로 flask 상에서 사용하던 jinja2 와 동일한 기능을 수행하게 된다.
callback(err, html) 을 정의하게 되면, 반드시 res.send(html) 을 통해 명시적으로 res 에 전달해 주어야한다.
이 글은 express document인 http://expressjs.com/ko/4x/api.html 을 번역하고 예제를 추가하여 이해를 돕고자 하였다. 아직은 추가하지 못한 예제도 많고 공부해야할 것들도 많아서 개발하면서 유사한 기능이 생길 때마다 추가할 예정이다.
이제 공부해야할 것들을 나열해보았다.
2019.04.10