이번 포스팅에서는 1. express, 2. 미들웨어, 3. 자주쓰는 패키지를 다루겠다.
책 Node.js 교과서(개정 2판) 책의 6장의 내용을 참고했다.
+모든 코드는 github주소에 있다.
express
- 서버를 제작하는 과정에서의 불편함을 해소하고 편의 기능을 추가한 웹 서버 프레임워크
- http 모듈의 요청과 응답 객체에 추가 기능을 부여
- 코드를 분리하기 쉽게 만들어 관리 용이
- http 모듈을 내장하고 있음
express project EXAMPLE )
1. ch6/express
폴더 생성
2. npm init을 통해 express, nodemon을 설치
실행결과(생성된 파일 - node_modules
, package-lock.json
, package.json
)
+package.json
에 scripts에"start": "nodemon app"
을 추가, main을 app.js
로 변경
-> 이 작업을 하면 app.js를 nodemon으로 실행
--> nodemon 모듈은 서버 코드에 수정 사항이 있으면 서버를 자동으로 재시작 함
Git [express/package.json
]
{
"name": "delay100_second",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon app"
},
"author": "delay100",
"license": "ISC",
"dependencies": {
"express": "^4.17.2"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
3. app.js 작성
app.set(키, 값)
: 데이터 저장 (ex. app.set('port', 포트)
: 서버가 실행될 포트를 설정)app.get(주소, 라우터)
: 주소에 대한 GET 요청이 올 때 어떤 동작을 할지 라우터 부분에 적음listen
: http와 동일Git [express/app.js
]
const express = require('express');
const path = require('path');
const app = express(); // express 모듈을 실행해 app 변수에 할당
app.set('port', process.envPORT || 3000); // process.env 객체에 PORT 속성이 있으면 그 값 사용, 없다면 기본값으로 3000번 포트 이용
// key: 'port', value : 'process.envPORT || 3000'
app.get('/', (req, res) => { // GET / 요청 시 응답으로 Hello, Express를 전송
// res.send('Hello, Express'); http 모듈에서 사용했던 res.write나 res.end 대신 res.send 사용
res.sendFile(path.join(__dirname, '/index.html')); // 다른 html 파일을 전송하고 싶은 경우
});
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 중')
});
Git [express/index.html
]
<html>
<head>
<meta charset="UTF-8"/>
<title>익스프레스 서버</title>
</head>
<body>
<h1>익스프레스</h1>
<p>배워봅시다.</p>
</body>
</html>
4. npm scripts 실행
입력(console) - 위에서 적은 scripts의 "start": nodemon app"
을 통해 app.js가 실행됨
npm start
결과(console)
...
Node.js v17.3.0
[nodemon] app crashed - waiting for file changes before starting...
[nodemon] restarting due to changes...
[nodemon] starting `node app.js`
3000 번 포트에서 대기 중
실행화면(웹 브라우저)
다음으로 익스프레스의 핵심인 미들웨어를 알아보자.
미들웨어
- 요청과 응답의 중간(미들(middle))에 위치
- 요청과 응답을 조작하여 기능을 추가하기도 하고, 요청을 거르기도 함
- 위에서부터 아래로 순서대로 실행되면서 요청과 응답 사이에 특별한 기능 추가 가능
- 미들웨어 장착 순서에 따라 어떤 미들웨어는 실행되지 않을 수도 있음(ex. 중간에 정적파일이 있는 경우)
핵심
app.use(미들웨어)
: 모든 요청에서 미들웨어 실행app.use('/abc', 미들웨어)
: abc로 시작하는 요청에서 미들웨어 실행app.post('/abc', 미들웨어)
: abc로 시작하는 POST 요청에서 미들웨어 실행미들웨어
err
: 에러req
: 요청res
: 응답next
: 다음 미들웨어로 넘어가는 함수(실행하지 않으면 다음 미들웨어 실행x)미들웨어 EXAMPLE )
Git [middleware/appex.js
]
const express = require('express');
const path = require('path');
const app = express();
app.set('port', process.env.PORT || 3000);
// app.use나 app.get같은 미들웨어를 여러개 장착 가능
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됩니다.');
next();
});
app.get('/',(req, res, next) => {
console.log('GET / 요청에서만 실행됩니다.');
next();
}, (req, res) => { // 요청이 오면 무조건 에러 처리
throw new Error('에러는 에러 처리 미들웨어로 갑니다.')
});
// 위에 만약 next(err)가 있는 경우 바로 이 함수가 실행됨
app.use((err, req, res, next) =>{
console.error(err);
res.status(500).send(err.message);
});
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 중');
});
출력
3000 번 포트에서 대기 중
모든 요청에 다 실행됩니다.
GET / 요청에서만 실행됩니다.
Error: 에러는 에러 처리 미들웨어로 갑니다.
...
실행결과(웹 브라우저)
미들웨어 간에 데이터를 전달하는 EXAMPLE )
Git [middleware/app.js
] 中 일부
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됩니다.');
req.data = '데이터 넣기';
next();
}, (req, res, next)=>{
console.log(req.data);
next();
});
미들웨어를 사용할 때 유용한 패턴
**기존 미들웨어의 기능 확장 가능
app.use(morgan('dev'));
// 또는
app.use((req, res, next) => {
morgan('dev')(req,res,next);
});
app.use((req, res, next) => {
if (process.env.NODE_ENV === 'production'){
morgan('combined')(req,res,next);
} else{
morgan('dev')(req, res, next);
}
});
실무에 자주 사용하는 패키지를 설치해보겠다.
설치(console)
npm i morgan cookie-parser express-session
morgan
-morgan 연결 후 localhost:3000에 다시 접속하면 기존 로그 외 추가적인 로그를 볼 수 있음
-요청과 응답에 대한 정보를 콘솔에 기록
-인수로 dev(개발 환경), combined(배포 환경), common, short, tiny 등 넣을 수 있음
morgan EXAMPLE )
입력
app.use(morgan('dev'));
출력(console)
GET / 500 13.502 ms - 50
--
cookie-parser
-요청에 동봉된 쿠키를 해석해 req.cookies 객체로 만듦
-해석된 쿠키들은 req.cookies 객체에 들어감
-서명된 쿠키: 제공한 비밀 키를 통해 해당 쿠키가 내 서버가 만든 쿠키임을 검증, req.signedCookies 객체에 name=delay100.sign 형태
-쿠키를 생성/제거할 때는 쓰지 않음(res.cookie, res.clearCookie 메소드 사용)
-parseCookies 함수와 기능이 비슷
ex) name=delay100 쿠키를 보냈다면 req.cookies는 { name: 'delay100' }가 됨 (유효 기간이 지난 쿠키는 알아서 거름)
cookie-parser EXAMPLE )
app.use(cookieParser(process.env.COOKIE_SECRET)); // 비밀키 할당, process.env.COOKIE_SECRET에 cookiesecret 값(키=값 형식)이 할당됨
GIT [middleware/.env
]
COOKIE_SECRET=cookiesecret
+res.cookie, res.clearCookie EXAMPLE )
**쿠키를 지우러면 키와 값 외에 옵션도 정확히 일치해야 함(단, expires나 maxAge 옵션은 일치할 필요가 없음, signed: true하면 서명이 붙음
// cookie 생성(res.cookie)
// 키, 값, 옵션
res.cookie('name', 'delay100', {
expires: new Date(Date.now() + 900000),
httpOnly: true,
secure: true,
});
// cookie 제거(res.clearCookie)
res.clearCookie('name', 'delay100', {httpOnly: true, secure: true});
--
express-session
-세션 관리용 미들웨어
-세션 관리 시 클라이언트에 쿠키를 보냄
-특정 사용자를 위한 데이터를 임시적으로 저장할 때 사용(ex. 로그인)
-세션은 사용자별로 req.session 객체 안에 유지됨
express-session EXAMPLE )
// express-session
// 인수: session에 대한 설정
app.use(session({
resave:false, // resave : 요청이 올 때 세션에 수정 사항이 생기지 않더라도 세션을 다시 저장할지 설정
saveUninitialized: false, // saveUninitialized : 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정
secret: process.env.COOKIE_SECRET,
cookie: { // 세션 쿠키에 대한 설정
httpOnly: true, // httpOnly: 클라이언트에서 쿠키를 확인하지 못하게 함
secure: false, // secure: false는 https가 아닌 환경에서도 사용 가능 - 배포할 때는 true로
// 코드에는 없지만, store: 데이터베이스에 연결해서 세션을 유지
},
name: 'session-cookie',
}));
--
[express 내에 존재하는 미들웨어 패키지]
static
-정적인 파일들을 제공하는 라우터 역할을 함
-외부인이 서버의 구조를 파악하기 어렵게 함
static EXAMPLE )
app.use('요청 경로', express.static('실제 경로'));
app.use('/', express.static(path.join(__dirname, 'public'));
ex) 내 서버에서 만약, public/stylesheets/style.css 폴더에 저장하면 http://localhost:3000/stylesheets/style.css 으로 브라우저에서 접근 가능, 요청 주소에는 public이 들어있지 않음!
+파일이 없으면, 알아서 내부적으로 next 호출
+파일이 있으면, 다음 미들웨어는 실행되지 않음(응답으로 파일을 보내고 next를 호출하지 않음)
--
body-parser
-요청의 본문에 있는 데이터를 해석해서 req.body 객체로 만들어주는 미들웨어
-보통 폼 데이터나 AJAX 요청의 데이터를 처리
-JSON, URL-encoded(주소 형식으로 데이터를 보내는 방식) 형식의 데이터를 해석할 수 있음 +Raw(요청의 본문이 버퍼 데이터일 때), Text(텍스트 데이터) 처리
-멀티파트(이미지, 동영상, 파일) 데이터는 처리하지 못함(multer 모듈 사용할 것)
+익스프레스 4.16.0 버전부터는 body-parser 미들웨어의 일부 기능이 익스프레스에 내장되었으므로 따로 설치할 필요가 없음
body-parser EXAMPLE )
app.use(express.json());
app.use(express.urlencoded({extended: false})); // extended 옵션이 false면 노드의 querystring 모듈을 사용하여 쿼리스트링을 해석
// extended 옵션이 true면 qs 모듈을 사용하여 쿼리스트링을 해석 - qs 모듈은 내장 모듈이 아닌 npm의 패키지(querystring 모듈의 기능을 좀 더 확장한 모듈임)
ex)
dotenv
-.env 패키지를 읽어서 process.env로 만듦- 보안과 설정의 편의성 제공
설치(console)
npm i dotenv
Git [middleware/app.js
]
const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const dotenv = require('dotenv');
const path = require('path');
dotenv.config();
const app = express();
app.set('port', process.env.PORT || 3000);
// req, res, next들은 미들웨어 내부에 들어있음
// morgan
app.use(morgan('dev'));
// static
app.use('/', express.static(path.join(__dirname, 'public')));
// body-parser
app.use(express.json());
app.use(express.urlencoded({extended: false})); // extended 옵션이 false면 노드의 querystring 모듈을 사용하여 쿼리스트링을 해석
// extended 옵션이 true면 qs 모듈을 사용하여 쿼리스트링을 해석 - qs 모듈은 내장 모듈이 아닌 npm의 패키지(querystring 모듈의 기능을 좀 더 확장한 모듈임)
// cookie-parser
app.use(cookieParser(process.env.COOKIE_SECRET)); // 비밀키 할당, process.env.COOKIE_SECRET에 cookiesecret 값(키=값 형식)이 할당됨
// // cookie 생성(res.cookie)
// 키, 값, 옵션
// res.cookie('name', 'delay100', {
// expires: new Date(Date.now() + 900000),
// httpOnly: true,
// secure: true,
// });
// // cookie 제거(res.clearCookie)
// res.clearCookie('name', 'delay100', {httpOnly: true, secure: true});
// express-session
// 인수: session에 대한 설정
app.use(session({
resave:false, // resave : 요청이 올 때 세션에 수정 사항이 생기지 않더라도 세션을 다시 저장할지 설정
saveUninitialized: false, // saveUninitialized : 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정
secret: process.env.COOKIE_SECRET,
cookie: { // 세션 쿠키에 대한 설정
httpOnly: true, // httpOnly: 클라이언트에서 쿠키를 확인하지 못하게 함
secure: false, // secure: false는 https가 아닌 환경에서도 사용 가능 - 배포할 때는 true로
// 코드에는 없지만, store: 데이터베이스에 연결해서 세션을 유지
},
name: 'session-cookie',
}));
// req.session.name = 'delay100'; // 세션 등록
// req.sessionID; //세션 아이디 확인
// req.session.destroy(); // 세션 모두 제거
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됩니다.');
req.data = '데이터 넣기';
next();
}, (req, res, next)=>{
console.log(req.data);
next();
});
app.get('/',(req, res, next) => {
console.log('GET / 요청에서만 실행됩니다.');
next();
}, (req, res) => { // 요청이 오면 무조건 에러 처리
throw new Error('에러는 에러 처리 미들웨어로 갑니다.')
});
app.use((err, req, res, next) =>{
console.error(err);
res.status(500).send(err.message);
});
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 중');
});
Git [middleware/.env
]
COOKIE_SECRET=cookiesecret
내용이 너무 길어질 것 같아, 다음 포스팅에서 'multer, Router 객체로 라우팅 분리하기'를 다루겠다.