티스토리 뷰
6장 익스프레스 웹 서버 만들기
📌 익스프레스
→ 서버를 제작하는 과정에서의 불편함을 해소하고 편의 기능을 추가한 웹 서버 프레임워크
→ http 모듈의 요청과 응답 객체에 추가 기능들을 부여
→ 코드를 분리하기 쉽게 만들어 관리하기 용의
const express = require('express');
const app = express(); // Express 모듈을 실행해 변수에 할당 (익스프레스 내부에 http 모듈이 내장되어 있으므로 서버 역할Ok)
app.set(키, 값) // 데이터 저장 (app.get(키)로 데이터를 가져올 수 있음)!
app.set('port', 포트) // 서버가 실행될 포트 설정
app.get(주소, 라우터) // 주소에 대한 GET 요청이 올 때 라우터 설정
app.post(주소, 라우터)
app.put(주소, 라우터)
app.patch(주소, 라우터)
app.delete(주소, 라우터)
app.options(주소, 라우터)
app.listen(app.get('port'), 콜백 함수) // listen 하는 부분
res.send() // 응답
res.sendFile() // 파일로 응답 (path 모듈로 파일 경로 지정)
res.state(상태코드) // HTTP 상태 코드 지정
📌 미들웨어
→ 미들웨어는 요청과 응답의 중간에 위치함 (익스프레스의 핵심)
→ 미들웨어는 요청과 응답을 조작하여 기능을 추가하기도 하고, 나쁜 요청을 걸러내기도 함
→ 라우터와 에러 핸들러 또한 미들웨어의 일종
app.use(미들웨어) // 모든 요청에서 미들웨어 실행
app.use('/user', 미들웨어) // user로 시작하는 요청에서 미들웨어 실행
app.post('/user', 미들웨어) // user로 시작하는 POST 요청에서 미들웨어 실행
→ 미들웨어는 매개 변수가 req, res, next인 함수 (next는 다음 미들웨어로 넘어가는 함수)
→ 미들웨어는 위에서부터 아래로 순서대로 실행
→ app.use나 app.get 같은 라우터에 미들웨어를 여러 개 장착할 수 있음 (next를 호출해야 다음 미들웨어로 넘어감)
→ 에러 처리 미들웨어는 매개변수가 err, req, res, next인 함수
→ 에러 처리 미들웨어는 특별한 경우가 아니면 가장 아래에 위치
app.use(
morgan('dev'),
express.static('/', path.join(__dirname, 'public')), //public 폴더에 정적 파일들이 있음
express.json(),
express.urlencoded({ extended: false }),
cookieParser(process.env.COOKIE_SECRET),
); // 동시에 여러 개의 미들웨어 장착 가능 (다음 함수로 넘어가려면 next호출)
→ next를 호출하지 않는 미들웨어는 res.send나 res.sendFile 등의 메서드로 응답을 보내야 함
→ 미들웨어 장착 순서에 따라 어떤 미들웨어는 실행되지 않을 수도 있음
→ next함수에 route라는 문자열을 인수로 넣으면 다음 라우터의 미들웨어로 바로 이동하고, 그 외의 인수를 넣으면 에러 처리 미들웨어의 err 매개변수가 됨
* 미들웨어 간의 데이터 전달 방법
1) req.session 객체에 데이터 넣기 → 세션이 유지되는 동안 데이터도 계속 유지
2) req.data에 데이터 넣기 → 요청이 끝날 때까지 데이터 유지
3) app.set → 익스프레스에 전역적으로 데이터 저장 (앱 전체의 설정 공유에 사용)
→ 미들웨어 안에 미들웨어를 넣을 수 있음
app.use(morgan('dev'));
app.use((req, res, next) => {
morgan('dev')(req, res, next);
}); // 두 코드가 같음
6.2 자주 사용하는 미들웨어 (패키지)
→ npm으로 설치하는 미들웨어는 보통 req, res, next를 내부에 포함하고 있음 (next도 내부적으로 호출)
npm i [패키지] [패키지] ...
📌 dotenv
→ process.env를 관리하기 위한 패키지 (.env 파일을 읽어서 process.env로 만듬)
→ 앞으로 소개할 패키지 중 얘만 미들웨어 X
const dotenv = require('dotenv');
dotenv.config();
→ 이 패키지를 설치하면 .env 파일을 생성 (파일명이 .env이고 확장자 X)
→ .env 파일에 비밀키들을 적어둠
// .env
COOKIE_SECRET=cookiesecret
→ 보안과 설정의 편의성을 위해 별도의 파일을 생성하는 것
📌 morgan
→ 요청과 응답에 대한 정보를 콘솔에 기록 (ex. GET / 500 7.409 ms - 50)
const morgan = require('morgan');
app.use(morgan('dev')); // 인수로 dev, common, short, tiny 등 가능!
* 개발 환경에서는 dev, 배포 환경에서는 combined 추천!
📌 static
→ 정적인 파일들을 제공하는 라우터 역할 (기본적으로 제공되기에 따로 설치X)
app.use('요청 경로', express.static('실제 경로')); // 인수로 정적 파일들이 담겨 있는 폴더 지정
app.use('/', express.static(path.join(__dirname, 'public')));
→ 해당하는 파일이 없으면 알아서 내부적으로 next 호출
→ 해당하는 파일이 있으면 다음 미들웨어는 실행 X
📌 body-parser
→ 요청의 본문에 있는 데이터를 해석해서 req.body 객체로 만들어주는 미들웨어 (보통 폼 데이터나 AJAX 요청의 데이터를 처리)
→ 멀티 파트(이미지, 동영상, 파일) 데이터는 처리하지 못함 (multer 모듈 사용)
→ 익스프레스 4.16.0 버전부터 body-parser 미들웨어의 일부 기능이 익스프레스에 내장
→ 만약, JSON과 URL-encoded 형식의 데이터 외에 Raw, Text 형식의 데이터를 해석하려면 직접 설치!
app.use(express.json());
app.use(express.urlencoded({ extended: false })); // 폼 전송 데이터에 주로 사용
// extended 옵션이 true면 노드의 querystring 모듈 사용, false면 qs모듈 사용
→ 내부적으로 body를 받으면 스트림 처리를 해서 res.body에 추가 (req.on('data')를 할 필요가 없어짐)
📌 cookie-parser
→ 요청에 동봉된 쿠키를 해석해 req.cookies 객체로 만듬 (parseCookies와 유사)
const cookieParser = require('cookie-parser');
app.use(cookieParser(비밀키));
→ 유효 기간이 지난 쿠키는 알아서 걸러냄
→ 쿠키를 생성/제거하기 위해서는 res.cookie(키, 값, 옵션), res.clearCookie 메서드 사용
res.cookie('name', 'zerocho', {
expires: new Date(Date.now() + 900000),
httpOnly: true,
secure: true,
}); // 옵션에는 domain, expires, httpOnly, maxAge, path, secure ...
res.clearCookie('name', 'zerocho', { httpOnly: true, secure: true }); // 쿠키를 지울려면 키와 값 외의 옵션도 일치해야 됨
→ 서명된 쿠키(req.signedCookies)의 경우, 제공한 비밀 키를 통해 해당 쿠키가 내 서버가 만든 쿠키임을 검증할 수 있음
→ 쿠키는 클라이언트에서 위조하기 쉬우므로 비밀 키를 통해 만들어낸 서명을 쿠키 값 뒤에 붙임(ex. name=zerocho.sign)
→ 옵션 중에 signed: true로 설정하면 쿠키 뒤에 서명이 붙음 (서명을 위한 비밀 키는 cookieParser 미들웨어에 인수로 넣은 process.env.COOKIE_SECRET)
📌 express-session
→ 세션 관리용 미들웨어 (로그인 또는 특정 사용자를 위한 데이터를 임시적으로 저장해둘 때 유용!)
→ 세션은 사용자별로 req.session 객체 안에 유지
const session = require('express-session');
app.use(session({
resave: false, // 요청이 올 때 세션에 수정 사항이 생기지 않으면 다시 저장 X
saveUninitialised: false, // 세션에 저장할 내역이 없으면 처음부터 세션을 생성 X
secret: process.env.COOKIE_SECRET, // cookie-parser의 secret과 같게 설정하는 것이 좋음
cookie: {
httpOnly: true, // 클라이언트에서 쿠키 확인 X
secure: false, // https가 아닌 환경에서도 사용 O
},
name: 'session-cookie', // 세션 쿠키의 이름
}));
* 배포 시에는 store라는 옵션을 통해 데이터베이스를 연결하여 세션을 유지 (레디스가 자주 사용됨)
req.session.name='' // 세션 등록
req.sessionID // 세션 아이디 확인
req.session.destroy() // 세션 모두 제거
→ 일반적으로 요청이 끝날 때 세션을 저장하는 메서드가 자동 호출!
📌 multer
→ 이미지, 동영상 등을 비롯한 여러 가지 파일들을 멀티파트(enctype="multipart/form-data") 형식으로 업로드할 때 사용하는 미들웨어
// 기본적인 설정
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.originame, ext) + Date.now() + ext); // 파일명+현재시간.확장자
}, // 어떤 이름으로
}),
limits: { fileSize: 5 * 1024 * 1024 }, // 파일 사이즈를 5MB로 제한
});
→ 서버에 uploads 폴더가 꼭 존재해야 됨 (없다면 직접 만들어주거나 fs모듈을 사용해서 서버를 시작할 때 생성) (p.246-247)
→ 미들웨어를 라우터 미들웨어 앞에 넣어두면, multer 설정에 따라 파일 업로드 후 req.file 또는 req.files 객체가 생성
// 파일을 하나만 업로드
app.post('/upload', upload.single('image'), (req, res) => { // single()의 인수는 input 태그의 name이나 폼 데이터의 키와 일치
console.log(req.file, req.body);
res.send('ok')
})
// 여러 파일 업로드
app.post('/upload', upload.array('many'), (req, res) => {
console.log(req.files, req.body);
res.send('ok')
})
// 여러 파일 업로드 (input 태그나 폼 데이터의 키가 다른 경우)
app.post('/upload',
upload.fields([{ name: 'image1' }, { name: 'image2' }]),
(req, res) => {
console.log(req.files, req.body);
res.send('ok')
}) // 업로드 결과는 req.files.image1, req.files.image2에 존재
// 파일 업로드 X
app.post('/upload', upload.none(), (req, res) => {
console.log(req.body);
res.send('ok')
})
6.3 Router 객체로 라우팅 분리하기
→ app.js에서 app.get()같은메서드가 라우터
→ 라우터를 분리하려면 라우터를 모아놓을 routes 폴더를 먼저 생성!
// app.js
// ...
const indexRouter = require('./routes'); // './routes/index'와 같음. index.js는 생략 가능!
const userRouter = require('./routes/user');
// ...
app.use('/', indexRouter);
app.use('/user', userRouter);
app.user((req, res, next) => {
res.status(404).send('Not Found');
}); // 미들웨어에 일치하는 라우터가 없을 때
// ...
// routes/index.js
const express = require('express');
const router = express.Router();
router.route('/')
.get((req, res) => {
res.send('GET /');
})
.post((req, res) => {
res.send('POST /');
})
module.exports = router;
// routes/user.js
const express = require('express');
const router = express.Router();
router.route('/')
.get((req, res) => {
res.send('GET /user/');
})
.post((req, res) => {
res.send('POST /user/');
})
module.exports = router;
→ 라우터 주소에는 정규표현식을 비롯한 특수 패턴을 사용할 수 있음
router.get('/user/:id', (req, res) => {
console.log(req.params, req.query); // :id는 req.params.id로 조회 가능
})
* 이 패턴을 쓸 때는 일반 라우터보다 뒤에 위치해야 함!
/users/123?limit=5&skip=10이라는 주소의 요청이 들어왔을 때
req.params = { id: '123' }
req.query = { limit: '5', skip: '10'}
📌 req, res 객체
// req
req.app // req 객체를 통해 app 객체에 접근
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.cookie(키, 값, 옵션) // 쿠키를 설정하는 메서드
res.clearCookie(키, 값, 옵션) // 쿠키를 제거하는 메서드
res.end() // 데이터 없이 응답
res.json(JSON) // JSON 형식의 응답을 보냄
res.render(뷰, 데이터) // 템플릿 엔진을 렌더링해서 응답할 떄 사용
res.send(데이터) // 데이터와 함께 응답을 보냄 (데이터는 문자열, HTML, 버퍼, 객체, 배열...)
res.sendFile(경로) // 경로에 위치한 파일 응답
res.set(헤더, 값) // 응답의 헤더를 설정
res.status(코드) // 응갑 시의 HTTP 상태 코드 지정
// 메서드 체이닝
res
.status(201)
.cookie('test', 'test')
.redirect('/admin');
6.5 템플릿 엔진 사용하기
→ 템플릿 엔진은 자바스크립트를 사용해서 HTML을 렌더링할 수 있게 해줌
📌 퍼그(제이드)
📌 넌적스
'백엔드 > Nodejs' 카테고리의 다른 글
[Nodejs] 패키지 매니저 (0) | 2021.11.15 |
---|---|
[Nodejs] http 모듈로 서버 만들기 (0) | 2021.11.15 |
[Nodejs] 노드 기능 알아보기 (0) | 2021.11.13 |
[Nodejs] 알아두어야 할 자바스크립트 (0) | 2021.11.11 |
[Nodejs] 노드 시작하기 (0) | 2021.11.10 |