티스토리 뷰
자주 사용하는 미들웨어
const express = require('express');
const path = require('path');
const app = express();
app.set('port', process.env.PORT || 3000);
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됩니다.');
next();
});
app.get(
'/',
(rep, res, next) => {
// res.send('Hello, Express');
// res.sendFile(path.join(__dirname, '/index.html'));
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'), '빈 포트에서 대기 중');
});
app.use에 매개변수가 req,res,next 함수를 넣으면 된다.
미들웨어는 위에서부터 아래로 실행
주소를 첫번째 인수로 넣어주지 않는다면 미들웨어는 모든 요청에서 실행, 주소를 넣으면 그 요청에서만 실행
에러 처리 미들웨어는 매개변수가 err, req, res, next. 반드시 매개변수 4개!!
res.status로 HTTP 상태 코드 지정. default는 200(성공)
가장 아래에 위치
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);
app.use(morgan('dev'));
app.use('/', express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(
session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
},
name: 'session-cookie',
})
);
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됩니다.');
next();
});
app.get(
'/',
(rep, res, next) => {
// res.send('Hello, Express');
// res.sendFile(path.join(__dirname, '/index.html'));
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'), '빈 포트에서 대기 중');
});
dotenv : .env에서 가져옴
morgan : 요청과 응답에 대한 정보를 콘솔에 기록
인수로 dev, combined, common, short, tiny 등이 가능
저자는 개발환경에서 dev, 배포 환경에서는 combined를 애용
dev 기준으로 GET / 500 7.409 ms - 50은 각각 [HTTP 메서드] [주소] [HTTP 상태 코드] [응답 속도] - [응답 바이트] 의미
static : 정적인 파일들을 제공하는 라우터 역할(express 기본 제공)
app.use('요청 경로', express.static('실제 경로'));
public 폴더를 만들고 css, js, 이미지 파일들을 public 폴더에 넣으면 브라우저에서 접근할 수 있게 된다.
실제 서버의 폴더 경로와 요청 주소가 달라!! 보안에 도움!!
body-parser
요청의 본문에 있는 데이터를 해석해서 req.body 객체로 만들어주는 미들웨어
보통 폼 데이터나 AJAX 요청의 데이터를 처리. 단, 멀티파트(이미지, 동영상, 파일) 데이터는 처리 불가
이때는 multer 모듈 사용
app.use(express.json());
app.use(express.urlencoded({extended:false}));
body-parser를 직접 설치해야 하는 경우 : JSON, URL-encoded 형식 외에 Raw, Text 형식의 데이터 해석필요할때
const bodyParser=require('body-parser');
app.use(bodyParser.raw());
app.use(bodyparser.text());
extended가 false이면 노드의 querystring 모듈을 사용하여 쿼리스트링을 해석
true면 qs 모듈을 사용하여 쿼리스트링을 해석
※ POST와 PUT 요청의 본문을 전달받으려면 req.on('data', req.on('end')로 스트림 사용 => 이제는 편해
cookie-parser
요청에 동봉된 쿠키를 해석해 req.cookies 객체로 만든다.
app.use(cookieParser(비밀키));
name=zerocho 쿠키를 보냈다면 req.cookies는 { name: 'zerocho' }가 된다. 유효기간이 지나면 알아서 걸러낸다.
첫번째 인수로 비밀 키를 넣어줄 수 있다. 쿠키는 위조하기 쉬우므로 비밀 키를 통해 만들어낸 서명을 쿠키 값 뒤에 붙인다. 서명이 붙이면 쿠키가 name=zerocho.sign과 같은 모양이 된다. 서명된 쿠키는 req.cookies 대신 req.signedCookies 객체에 들어 있다.
쿠키를 생성/제거하려면 res.cookie, res.clearCookie 메서드 사용
옵션중에 signed라는 옵션이 있는데 이를 true로 설정하면 쿠키 뒤에 서명이 붙는다.
res.cookie('name','zerocho',{
expires;~~,
httpOnly:true,
secure:true,
});
res.clearCookie('name','zerocho',{httpOnly:true,secure:true});
express-session
세션 관리용 미들웨어이다. 세션은 사용자별로 req.session 객체 안에 유지
순서가 상관없는데 버전때문에 cookie-parser 다음에 놓는 것이 안전
express-session은 인수로 세션에 대한 설정을 받는다.
resave는 요청이 올 때 세션에 수정 사항이 생기지 않더라도 세션을 다시 저장할지 설정하고
saveUninitialized는 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정하는 것이다.
express-session은 세션 관리 시 클라이언트에 쿠키를 보낸다. 이게 세션 쿠키다.
secret은 cookie-parser의 secret과 같게
name은 이름. default는 connect.sid
쿠키 옵션은 세션 쿠키에 대한 설정. 일반적인 쿠키옵션 모두 제공. secure가 지금은 false인데 (https아니여도 가능)
배포때는 https 적용하고 secure도 true로 하자
store라는 옵션도 있다. 현재는 메모리에 세션을 저장해서 서버 재시작하면 세션이 사라진다.
배포시에는 store에 db를 연결하여 세션을 유지. 보통 레디스 사용
req.session.name='zerocho'; // 세션 등록
req.sessionId; // 세션 아이디 확인
req.session.destroy(); // 세션 모두 제거
express-session에서 서명한 쿠키 앞에는 s:이 붙는다. 실제로는 encodeURIComponent때문에 s%3A가 된다.
미들웨어의 특성 활용하기
설치한 미들웨어를 연속으로 사용하려면 next 호출해야돼. 설치한 것들이 내부적으로 next 호출해서 그렇지
next호출하지 않으면 res.send나 res.sendFile로 응답을 보내야 해
express.static 같은 미들웨어는 정적 파일을 제공할 때 res.sendFile 메서드로 응답을 보내서 다음 미들웨어는 실행x
next에 인수를 넣을 수도 있다.
route라는 문자열을 넣으면 다음 라우터의 미들웨어로 바로 이동하고, 그 외의 인수를 넣으면 바로 에러 처리 미들웨어로 이동
미들웨어 간에 데이터를 전달하는 방법도 있다. 세션을 사용한다면 req.session 객체에 데이터를 넣어도 되지만, 세션이 유지되는 동안에 데이터도 계속 유지된다는 단점이 있다. 요청이 끝날 때까지만 데이터를 유지하고 싶다면 req 객체에 데이터를 넣어두면 된다.
새로운 요청이 오면 req.data는 초기화된다.
* app.set은 전역이라서 전체의 설정을 공유할 때 사용하자
app.use((req,res,next)=>{
req.data='데이터 넣기';
next();
},(req,res,next)=>{
console.log(req.data); // 데이터 받기
next();
});
미들웨어를 사용할 때 유용한 패턴 한가지. 미들웨어 안에 미들웨어를 넣는 방식
다음 두 방식은 같은 기능을 한다.
ex1)
app.use(morgan('dev'));
// 또는
app.use((req,res,next)=>{
morgan('dev')(req,res,next);
});
ex2)
app.use((req,res,next)=>{
if(process.env.NODE_ENV==='production'){
morgan('combined')(req,res,next);
}else{
morgan('dev')(req,res,next);
}
});
multer
이미지, 동영상 등을 비롯한 여러 가지 파일들을 멀티파트 형식으로 업로드할 때 사용하는 미들웨어
기본 설정
const multer = require('multer');
const upload = multer({
storage: multer.diskStorage({
destination(req, file, done) {
done(null, 'uploads/');
},
filename(req, file, done) {
const ext = path.extname(file.originalname);
done(null, path.basename(file.originalname, ext) + Date.now() + ext);
},
}),
limits: { fileSize: 5 * 1024 * 1024 },
});
storage 속성에는 어디(destination)에 어떤 이름(filename)으로 저장할지를 넣었다.
req 매개변수에는 요청에 대한 정보, file 객체에는 업로드한 파일에 대한 정보, done 매개변수는 함수
첫번째 인수에 에러가 있다면 에러를 넣고, 두번째 인수에는 실제 경로나 파일 이름을 넣어주면 된다.
req나 file의 데이터를 가공해서 done으로 넘기는 형식이다.
limit 속성에는 업로드에 대한 제한 사항을 설정할 수 있다.
이때 uploads 폴더가 없으면 안돼요
try {
fs.readdirSync('uploads');
} catch (error) {
console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다.');
fs.mkdirSync('uploads');
}
설정이 끝나면 upload 변수가 생기는데, 여기에 다양한 종류의 미들웨어가 들어있다.
(파일을 여러 개 업로드하지만 input 태그나 폼 데이터의 키가 다른 경우에는 fields 미들웨어를 사용)
멀티파트 형식으로 업로드 하는 경우 none 미들웨어
<form id="form" action="/upload" method="post" enctype="multipart/form-data">
<input type="text" name="title"/>
<button type="submit">업로드</button>
</form>
app.post('/upload', upload.none(), (req,res)=>{
console.log(req.body);
res.send('ok');
});
req.file은 이로케 생김
{
fieldname:'img',
originalname:'nodejs.png',
encoding:'7bit',
mimetype:'image/png',
destination:'uploads/',
filename:'nodejs151419123123.png',
path:'uploads\\nodejs151419123123.png',
size:53357
}
프로젝트에는 이렇게 넣으면
app.get('upload', (req, res) => {
res.sendFile(path.join(__dirname, 'multipart.html'));
});
app.post(
'/upload',
upload.fields([{ name: 'image1' }, { name: 'image2' }]),
(req, res) => {
console.log(req.files, req.body);
res.send('ok');
}
);
<form id="form" action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="image1" />
<input type="file" name="image2" />
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
Router 객체로 라우팅 분리하기
이전에 요청 메서드, 주소별로 분기 처리를 하느라 코드가 복잡했다. 익스프레스에서는 라우팅을 깔끔하게 관리
routes/index.js
const express = require('express');
const router = express.Router();
// GET / 라우터
router.get('/', (req, res) => {
res.send('Hello, Express');
});
module.exports = router;
app.js
const indexRouter = require('./routes');
app.use('/', indexRouter);
app.use((req, res, enxt) => {
res.status(404).send('Not Found');
});
유용한 팁
라우터 주소에는 정규표현식을 비롯한 특수 패턴을 사용할 수 있다. 라우트 매개변수 살펴보자
router.get('/user/:id', function(req,res){
console.log(req.params, req.query);
}
ex)
/user/123?limit=5&skip=10 => {id:'123'} {limit:'5', skip:'10}
주소가 같지만 메서드가 다를때 하나로 합치기
router
.route('/abc')
.get((req, res) => {
res.send('GET /abc');
})
.post((req, res) => {
res.send('POST /abc');
});
req, res 객체 살펴보기
익스프레스의 req, res 객체는 http 모듈의 req, res 객체를 확장한 것이다. 기존 http 모듈의 메서드도 사용할 수 있다. 물론 잘 사용하지는 않는다.
req
req.app : req 객체를 통해 app 객체에 접근할 수 있다. req.app.get('port')
req.body : body-parser 미들웨어가 만드는 요청의 본문을 해석한 객체
req.cookies : cookie-parser 미들웨어가 만드는 요청의 쿠키를 해석한 객체
req.ip : 요청의 ip 주소가 담겨있다.
req.params : 라우트 매개변수에 대한 정보가 담긴 객체
req.query : 쿼리 스트링에 대한 정보가 담긴 객체
req.signedCookies : 서명된 쿠키들은 req.cookies 대신 여기에 담겨 있다.
req.get(헤더 이름) : 헤더의 이름ㅇ르 가져오고 싶을 때 사용하는 메서드
res
res.app : res.app 처럼 res 객체를 통해 app 객체에 접근
res.cookie(키, 값, 옵션) : 쿠키를 설정하는 메서드
res.clearCookie(키, 값, 옵션) : 쿠키를 제거하는 메서드
res.end() : 데이터 없이 응답을 보낸다.
res.json(JSON) : JSON 형식의 응답을 보낸다.
res.redirect(주소) : 리다이렉트할 주소와 함께 응답을 보낸다.
res.render(뷰, 데이터) : 다음 절에서 다룰 템플릿 엔진을 렌더링해서 응답할 때 사용하는 메서드
res.send(데이터) : 데이터와 함께 응답을 보낸다. 데이터는 문자열, HTML, 버퍼, 객체, 배열 전부 다 돼
res.sendFile(경로) : 경로에 위치한 파일을 응답한다.
res.set(헤더, 값) : 응답의 헤더를 설정
res.status(코드) : 응답 시의 HTTP 상태 코드를 지정
req나 res 객체의 메서드는 메서드 체이닝을 지원하는 경우가 많다.
res.stats(201).cookie('test','test').redirect('/admin');
템플릿 엔진 사용하기
템플릿 엔진은 자바스크립트를 사용해서 HTML을 렌더링할 수 있게 한다. 따라서 기존 HTML과는 문법이 살짝 다를 수도 있고, 자바스크립트 문법이 들어 있기도 하다.
1. 퍼그(제이드) : HTML과는 문법이 많이 달라 호불호가 갈려. 퍼그는 강아지
2. 넌적스 : 퍼그의 HTML 문법 변화에 적응하기 힘든 분에게 적합한 템플릿. Twig와 유사
npm i nunjucks
app.js
const nunjucks = require('nunjucks');
app.set('view engine', 'html');
nunjucks.configure('views', {
express: app,
watch: true,
});
configure의 첫번째 인수로 views 폴더의 경로. 두번째 인수로 옵션. 이때 express 속성에 app 객체를 연결, watch 옵션이 true이면 HTML 파일이 변경될 때 템플릿 엔진을 다시 렌더링
파일은 html 그대로 사용해도 돼. njk로 해도 되는데 그러면 view engine도 njk로 바꿔야 해
1. 변수
res.render 호출 시 보내는 변수를 넌적스가 처리
js
router.get('/', function(req,res,next){
res.render('index', { title: 'Express' });
});
넌적스
<h1>{{title}}</h1>
<button class="{{title}}" type="submit">전송</button>
<input placeholder="{{title}} 연습"/>
내부에서 변수 사용. {% set 변수 = '값' %}
{% set node = 'Node.js' %}
{% set js = 'Javascript' %}
<p>{{node}}와 {{js}}</p>
=> <p>Node.js와 Javascript</p>
HTML을 이스케이프하고 싶지 않다면 {{ 변수 | safe }}를 사용
2. 반복문
넌적스에서는 특수한 구문을 {% %} 안에 쓴다. for in문과 endfor
<ul>
{% set fruits = ['사과', '배', '오렌지', '바나나', '복숭아'] %}
{% for item in fruits %}
<li>{{item}}</li>
{% endfor %}
</ul>
인덱스 사용 loop.index
<ul>
{% set fruits = ['사과', '배', '오렌지', '바나나', '복숭아'] %}
{% for item in fruits %}
<li>{{loop.index}}번째 {{item}}</li>
{% endfor %}
</ul>
3. 조건문
{% if 변수 %} {% elif %} {% else %} {%endif %}
{% if isLoggedIn %}
<div>로그인 되었습니다.</div>
{% else %}
<div>로그인이 필요합니다.</div>
{% endif %}
4. include
다른 HTML 파일을 넣을 수 있다.
{% include "header.html" %}
<main>
<h1>메인 파일</h1>
<p>다른 파일을 include할 수 있습니다.</p?
</main>
{% include "footer.html" %}
5. extends와 block
레이아웃을 정할 수 있다. 공통되는 레이아웃 부분을 따로 관리할 수 있어 좋다. include와도 함께 사용
layout.html
<body>
<header>헤더입니다.</header>
{% block content %}
{% endblock %}
<footer>푸터입니다.</footer>
{% block script %}
{% endblock %}
</body>
body.html
{% extends 'layout.html' %}
{% block content %}
<main>
<p>내용입니다.</p>
</main>
{% endblock %}
{% block script %}
<script src="/main.js"></script>
{% endblock %}
res.locals
res.locals의 프로퍼티들은 request의 라이프 타임 동안에만 유효하다.
html/view 클라이언트 사이드로 변수들을 보낼 수 있으며, 그 변수들은 오로지 거기서만 사용할 수 있다.
layout.html
<!DOCTYPE html>
<head>
<title>{{title}}</title>
<link rel="stylesheet" href="/style.css"/>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
index.html
{% extends 'layout.html' %} {% block content %}
<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>
{% endblock %}
error.html
{% extends 'layout.html' %} {% block content %}
<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>
{% endblock %}
에러 처리 미들웨어
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
error.status = 404;
next(error);
});
app.use((err, req, res, next) => {
res.locals.message = err.message;
res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
res.status(err.status || 500);
res.render('error');
});
'책 > nodejs 교과서' 카테고리의 다른 글
8. 몽고디비 (0) | 2021.02.24 |
---|---|
7. MYSQL (0) | 2021.02.22 |
5. 패키지 매니저 (0) | 2021.02.20 |
4. http 모듈로 서버 만들기 (0) | 2021.02.20 |
3. 노드 기능 알아보기 (0) | 2021.02.19 |