이번 포스팅에서는 1. multer, 2. 라우팅 분리(feat. Router 객체), 3. req, res 객체, +전체 코드를 다루겠다. express 웹 서버 만들기 -1에서 사용한 app.js 파일을 마저 사용하겠다.
책 Node.js 교과서(개정 2판) 책의 6장의 내용을 참고했다.
+모든 코드는 github주소에 있다.
multer
- 이미지, 동영상 등을 비롯한 여러 가지 파일들을 멀티파트 형식으로 업로드할 때 사용
**멀티파트 형식: enctype이 multipart/form-data인 폼을 통해 업로드하는 데이터의 형식- 미들웨어의 종류
npm i multer
multer의 동작은 아래에 [multer 전체 코드]의 주석으로 설명해두었다.
+파일을 하나만 업로드하는 방법
app.post('/upload', upload.single('image'), (req, res) => {
console.log(req.file, req.body);
res.send('ok');
});
Git [middleware/multipart.html
]
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="image" />
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
upload.single('image')
미들웨어에 의해 multer 설정에 따라 파일 uploadreq.file
객체 생성req.file
객체는 아래와 같음{
fieldname: 'img',
originalname: 'nodejs.png',
encoding: '7bit',
...
filename: 'nodejs1514197844339.png',
path: 'uploads\\nodejs1514197844339.png',
size: 53357
}
+여러 파일을 업로드하는 방법1, 2
// 여러 파일을 업로드하는 방법
app.post('/upload', upload.array('many', (req,res) => {
console.log(req.files, req.body);
res.send('ok');
}))
// 여러 파일을 업로드하는 방법2
app.post('/upload', upload.fields([{name: 'image1'}, {name: 'image2'}]),(req, res) => {
console.log(req.files, req.body);
res.send('ok');
},
);
Git [middleware/multipart2.html
]
<html>
<head></head>
<body>
<!-- 여러 파일을 업로드하는 방법 -->
<form id="form" action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="many" multiple/>
<!-- 여러 파일을 업로드하는 방법2 -->
<!-- <input type ="file" name="image1"/>
<input type="file" name="image2"/> -->
<input type="text" name="title" />
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
</body>
</html>
+파일을 업로드하지 않고 멀티파트 형식으로 업로드 방법
<form id="form" action="/upload" method="post" enctype="multipart/form-data">
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
Git [middleware/multipart3.html
]
<html>
<head></head>
<body>
<!-- 파일을 업로드하지 않고 멀티파트 형식으로 업로드 방법 -->
<form id="form" action="/upload" method="post" enctype="multipart/form-data">
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
</body>
</html>
Git [middleware/app.js
] 中 일부
// multer
const multer = require('multer');
const fs = require('fs');
try {
fs.readdirSync('uploads'); // sync로 upload 폴더 읽음
}catch(error){
console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다');
fs.mkdirSync('uploads'); // upload 폴더가 없으면 upload가 불가능하므로 폴더 생성
}
const upload = multer({ // multer의 인수로 설정을 넣음
storage: multer.diskStorage({ // storage(저장 위치) 속성
destination(req, file, done){ // 어디?(요청 정보, 업로드한 파일 정보, 함수) -> req, file 데이터를 가공 후 done으로 넘김
done(null, 'uploads/'); // done(에러가 있으면 에러를 넣음, 실제 경로(or 파일 이름))
},
filename(req, file, done){ // 파일 이름
const ext = path.extname(file.originalname);
done(null, path.basename(file.originalname, ext) + Date.now() + ext); // 이름이 겹치지 않도록 이름에 현재 시간을 저장
// 결국, upload라는 폴더에 [파일명+현재시간.확장자]로 업로드
},
}),
limits: {fileSize: 5 * 1024 * 1024}, // 업로드 제한 사항:{파일 크기: 5mb}
});
app.get('/upload', (req, res)=> {
res.sendFile(path.join(__dirname, 'multipart2.html'));
});
app.post('/upload', upload.fields([{name: 'image1'},{name: 'image2'}]),
(req, res) => {
console.log(req.files, req.body);
res.send('ok');
},
);
// 파일을 하나만 업로드하는 방법 (multipart.html)
// app.post('/upload', upload.single('image'), (req, res) => {
// console.log(req.file, req.body);
// res.send('ok');
// });
// 여러 파일을 업로드하는 방법 (multipart2.html)
// app.post('/upload', upload.array('many', (req,res) => {
// console.log(req.files, req.body);
// res.send('ok');
// }))
// 여러 파일을 업로드하는 방법2 (multipart2.html)
// app.post('/upload', upload.fields([{name: 'image1'}, {name: 'image2'}]),(req, res) => {
// console.log(req.files, req.body);
// res.send('ok');
// },
// );
// 파일을 업로드하지 않고 멀티파트 형식으로 업로드 방법 (multipart3.html)
// app.post('/upload', upload.none(), (req, res)=> {
// console.log(req.body); // 파일을 업로드하지 않았으므로 req.body만 존재
// res.send('ok');
// });
// req.session.name = 'delay100'; // 세션 등록
// req.sessionID; //세션 아이디 확인
// req.session.destroy(); // 세션 모두 제거
Git [middleware/multipart2.html
]
<html>
<head></head>
<body>
<!-- 여러 파일을 업로드하는 방법 -->
<form id="form" action="/upload" method="post" enctype="multipart/form-data">
<!-- <input type="file" name="many" multiple/> -->
<!-- 여러 파일을 업로드하는 방법2 -->
<input type ="file" name="image1"/>
<input type="file" name="image2"/>
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
</body>
</html>
실행화면(웹 브라우저) - http://127.0.0.1:3000/upload
실행화면(console) - 업로드 후
[Object: null prototype] {
image1: [
{
fieldname: 'image1',
originalname: '표지 8.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'uploads/',
filename: '표지 81642486558893.png',
path: 'uploads\\표지 81642486558893.png',
size: 1059878
}
],
image2: [
{
fieldname: 'image2',
originalname: '표지 7.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'uploads/',
filename: '표지 71642486559197.png',
path: 'uploads\\표지 71642486559197.png',
size: 1055004
}
]
} [Object: null prototype] { title: 'ㅎㅎ' }
POST /upload 200 394.497 ms - 2
전체 파일 - uploads 폴더가 생성되며, 업로드한 파일의 이름이 현재 시간이 추가되어 서버에 저장됨
// 라우터 분리
const indexRouter = require('./routes');
const userRouter = require('./routes/user');
// 라우터 경로 설정
app.use('/', indexRouter); // localhost:3000
app.use('/user', userRouter); // localhost:3000/user
...
app.use((req, res, next) => {
res.status(404).send('Not Found'); // 일치하는 라우터가 없을 때 404 상태 코드를 응답
});
실행화면(웹 브라우저)
+라우터에 연결된 나머지 미들웨어를 건너뛰고 싶은 경우
router.get('/', function(req, res, next) {
next('route'); // 주소가 일치하는 다음 라우터로 넘어감
}, function(req, res, next) { // 건너뛰어짐
console.log('실행되지 않습니다.');
next();
}, function(req, res, next) {
console.log('실행되지 않습니다.');
next();
});
router.get('/', function(req, res) { // 여기부터 실행
console.log('실행됩니다.');
res.send('Hello, Express');
});
+라우트 매개변수라고 불리는 패턴
**일반 라우터보다 뒤에 위치해야 함!(다양한 라우터를 아우르므로 다른 라우터를 방해하지 않게하기 위함)
router.get('/user/:id', function(req, res){ // :id는 /user/1, /user/123 등의 요청 처리(req.params.id로 조회 가능, 만약 :type이면 req.params.type으로 조회 가능, user, type 같은 변수는 마음대로 가능)
console.log(req.params, req.query);
});
+주소에 쿼리스트링을 쓰는 경우
ex)
주소 요청
/users/123?limit=5&skip=10
req.params와 req.query객체 console
{ id: '123 } { limit: '5', skip: '10' }
+주소는 같지만 메서드가 다른 코드가 있을 때 하나로 줄이는 방법
줄이기 전
router.get('/abc', (req, res) => {
res.send('GET /abc');
});
router.post('/abc', (req, res) => {
res.send('POST /abc');
});
줄인 후
router.route('/abc')
.get((req, res) => {
res.send('GET /abc');
})
.post((req, res) => {
res.send('POST /abc');
});
req, res는 메서드 체이닝을 지원하는 경우가 많음
메서드 체이닝 EXAMPLE )
res
.status(201)
.cookie('test', 'test')
.redirect('/admin');
-req.app
: req 객체를 통해 app 객체에 접근할 수 있음(ex. 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.app
: req.app처럼 res 객체를 통해 app 객체에 접근할 수 있음
-res.cookie(키, 값, 옵션)
: 쿠키를 설정하는 메서드
-res.clearCookie(키, 값, 옵션)
: 쿠키를 제거하는 메서드
-res.end()
: 데이터 없이 응답을 보냄
-res.json(JSON)
: JSON 형식의 응답을 보냄
-res.redirect(주소)
: 리다이렉트할 주소와 함께 응답을 보냄
-res.render(뷰, 데이터)
: 템플릿 엔진을 렌더링해서 응답할 때 사용하는 메서드
-res.sendFile(경로)
: 경로에 위치한 파일을 응답
-res.set(헤더, 값)
: 응답의 헤더를 설정
-res.status(코드)
: 응답 시의 HTTP 상태 코드를 지정
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 indexRouter = require('./routes');
const userRouter = require('./routes/user');
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',
}));
// 라우터 경로 설정
app.use('/', indexRouter); // localhost:3000
app.use('/user', userRouter); // localhost:3000/user
// multer
const multer = require('multer');
const fs = require('fs');
try {
fs.readdirSync('uploads'); // sync로 upload 폴더 읽음
}catch(error){
console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다');
fs.mkdirSync('uploads'); // upload 폴더가 없으면 upload가 불가능하므로 폴더 생성
}
const upload = multer({ // multer의 인수로 설정을 넣음
storage: multer.diskStorage({ // storage(저장 위치) 속성
destination(req, file, done){ // 어디?(요청 정보, 업로드한 파일 정보, 함수) -> req, file 데이터를 가공 후 done으로 넘김
done(null, 'uploads/'); // done(에러가 있으면 에러를 넣음, 실제 경로(or 파일 이름))
},
filename(req, file, done){ // 파일 이름
const ext = path.extname(file.originalname);
done(null, path.basename(file.originalname, ext) + Date.now() + ext); // 이름이 겹치지 않도록 이름에 현재 시간을 저장
// 결국, upload라는 폴더에 [파일명+현재시간.확장자]로 업로드
},
}),
limits: {fileSize: 5 * 1024 * 1024}, // 업로드 제한 사항:{파일 크기: 5mb}
});
app.get('/upload', (req, res)=> {
res.sendFile(path.join(__dirname, 'multipart2.html'));
});
app.post('/upload', upload.fields([{name: 'image1'},{name: 'image2'}]),
(req, res) => {
console.log(req.files, req.body);
res.send('ok');
},
);
// 파일을 하나만 업로드하는 방법 (multipart.html)
// app.post('/upload', upload.single('image'), (req, res) => {
// console.log(req.file, req.body);
// res.send('ok');
// });
// 여러 파일을 업로드하는 방법 (multipart2.html)
// app.post('/upload', upload.array('many', (req,res) => {
// console.log(req.files, req.body);
// res.send('ok');
// }))
// 여러 파일을 업로드하는 방법2 (multipart2.html)
// app.post('/upload', upload.fields([{name: 'image1'}, {name: 'image2'}]),(req, res) => {
// console.log(req.files, req.body);
// res.send('ok');
// },
// );
// 파일을 업로드하지 않고 멀티파트 형식으로 업로드 방법 (multipart3.html)
// app.post('/upload', upload.none(), (req, res)=> {
// console.log(req.body); // 파일을 업로드하지 않았으므로 req.body만 존재
// res.send('ok');
// });
// req.session.name = 'delay100'; // 세션 등록
// req.sessionID; //세션 아이디 확인
// req.session.destroy(); // 세션 모두 제거
app.use((req, res, next) => {
res.status(404).send('Not Found'); // 일치하는 라우터가 없을 때 404 상태 코드를 응답
});
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 [routes/index.js
]
const express = require('express');
const router = express.Router();
// GET / 라우터
router.get('/', (req, res) =>{
res.send('Hello, Express');
});
module.exports = router;
Git [middleware/routes/user.js
]
const express = require('express');
const router = express.Router();
// GET /user 라우터
router.get('/', (req, res) =>{
res.send('Hello, User');
});
module.exports = router;
Git [middleware/multipart2.html
]
<html>
<head></head>
<body>
<!-- 여러 파일을 업로드하는 방법 -->
<form id="form" action="/upload" method="post" enctype="multipart/form-data">
<!-- <input type="file" name="many" multiple/> -->
<!-- 여러 파일을 업로드하는 방법2 -->
<input type ="file" name="image1"/>
<input type="file" name="image2"/>
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
</body>
</html>
Git [middleware/package.json
]
{
"name": "delay100_middleware",
"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": {
"cookie-parser": "^1.4.6",
"dotenv": "^14.1.0",
"express": "^4.17.2",
"express-session": "^1.17.2",
"morgan": "^1.10.0",
"multer": "^1.4.4"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
Git [middleware/.env
]
COOKIE_SECRET=cookiesecret