# 익스프레스 웹 서버 만들기
1. express 서버 사용해보기
- http 모듈로 웹 서버를 만들 때 코드가 보기 좋지 않고, 확장성도 떨어짐( 프레임워크로 해결. )
- 대표적인 것이 Express(익스프레스) / Koa(코아) / Hapi(하피)
- 코드 관리도 용이하고, 편의성이 많이 높아졌다.
## package.json 생성
- 직접 만들거나(직접 입력 후 npm i 해줘야 package-lock.json, node_modules 생성됨), npm init 명령어 사용하여 생성.
- nodemon : 소스 코드 변경 시 알아서 서버를 재시작해준다.
$ npm i express
$ npm i -D nodemon
const express = require('express');
const app = express();
app.set('port', process.env.PORT || 3000);
app.post('/', (req, res) => {
res.send('hello express');
});
app.get('/about', (req, res) => {
res.send('hello express');
});
app.listen(app.get('port'), () => {
console.log('익스프레스 서버 실행');
});
- package.json에서 scripts에 "start" : "nodemon app"이 등록되어있다면, npm start 입력하여 실행 가능. (그럼 자동으로 nodemon app 실행 됨)
2. express로 html 서빙하기
const express = require('express');
const path = require('path');
const app = express();
app.set('port', process.env.PORT || 3000);
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.post('/', (req, res) => {
res.send('hello express');
});
app.get('/about', (req, res) => {
res.send('hello express');
});
app.listen(app.get('port'), () => {
console.log('익스프레스 서버 실행');
});
3. 미들웨어 사용
- 메서드와 주소가 있는 형태를 라우터라고 부름.
- 익스프레스는 기본적으로 위 > 아래로 실행하지만, 미들웨어는 next를 해줘야 다음걸로 넘어간다.
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('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.post('/', (req, res) => {
res.send('hello express');
});
app.get('/about', (req, res) => {
res.send('hello express');
});
app.listen(app.get('port'), () => {
console.log('익스프레스 서버 실행');
});
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('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.post('/', (req, res) => {
res.send('hello express');
});
app.get('/category/Javascript', (req, res) => {
res.send('hello Javascript');
});
app.get('/category/:name', (req, res) => {
res.send(`hello ${req.params.name}`);
});
app.get('/about', (req, res) => {
res.send('hello express');
});
app.get('*', (req, res) => {
res.send('와일드카드 * 는 모든 GET은 여길 실행하겠다는 것.');
});
app.listen(app.get('port'), () => {
console.log('익스프레스 서버 실행');
});
4. 미들웨어 특성 이해
- req, res, next를 매개변수로 가지는 함수
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됩니다.');
next();
});
- 익스프레스 미들웨어들도 다음과 같이 축약 가능.
순서가 중요함
static 미들웨어에서 파일을 찾으면 next를 호출 안 하므로 json, urlencoded, cookieParser는 실행되지 않음.
app.use(
morgan('dev'),
express.static('/', path.join(__dirname, 'public')),
express.json(),
express.urlencoded({ extended : false }),
cookieParser(process.env.COOKIE_SECRET),
);
요청과 응답의 중간에 위치하여 미들웨어
app.use(미들웨어)로 장착
위에서 아래로 순서대로 실행됨.
미들웨어는 req, res, next가 매개변수인 함수.
req : 요청 / res : 응답 조작 가능
next()로 다음 미들웨어로 넘어감
app.get('/about', (req, res) => {
res.send('hello express');
});
에서..
1. 함수는
app.get(); 부분
2. 미들웨어는
함수 안에있는
(req, res) => {
res.send('hello express');
} 부분
- 아래와 같이 미들웨어를 여러개 동시에 넣어줄수 있음. (연달아 사용 가능)
app.use((req, res, next) => {
console.log('모든 요청에 실행하고 싶어.');
next();
}, (req, res, next) => {
console.log('모든 요청에 실행하고 싶어.');
next();
}, (req, res, next) => {
console.log('모든 요청에 실행하고 싶어.');
next();
});
- 에러 처리 미들웨어 : 에러가 발생하면 에러 처리 미들웨어로.
err, req, res, next까지 매개변수가 4개
첫 번째 err에는 에러에 관한 정보가 담김
res.status 메서드로 HTTP 상태 코드를 지정 가능하다. (기본값은 200)
에러 처리 미들웨어를 연결하지 않아도 익스프레스가 에러를 알아서 처리해주긴 함.
특별한 경우가 아니면 아래에 위치하도록 한다.
- 에러 처리는 매우 중요. 에러 처리 로직의 경우 가장 마지막에 작성
// 에러 미들웨어의 경우 err, req, res, next 모두 들어있어야 함(반드시!!).
app.use((err, req, res, next) => {
console.error(err);
})
// 아래와 같이.. a와 b는 다름
1. a
app.use((err, req, res, next) => {
console.error(err);
})
2. b
app.use((err, req, res) => {
console.error(err);
})
a 와 b는 다른함수로 취급 됨. next 생략 하지 말 것.
- 라우터 뒤에 아래와 같이 처리 가능 (401, 403, 404 등 관련 코드를 다르게 해줄 수도 있음)
app.get((req, res, next) => {
res.status(200).send('200이지렁!!');
});
app.get((req, res, next) => {
res.status(404).send('404이지렁!!');
});
// 이렇게 401이지만 다른 문구 등이 표시되도록 할 수 있음. (해커들에게 정보 노출을 숨길수 있음)
app.get((req, res, next) => {
res.status(401).send('404이지렁!!');
});
- 하나의 라우터에서 아래와 같이 여러번 하면 에러 발생 가능.
app.get('/', (req, res, next) => {
res.sendFile(path.join(__dirname, 'index.html'));
res.send('hi');
res.json({ hello: 'zeroro' });
});
=> 에러문구
Cannot set headers after they are sent to the client
- 미들웨어의 특성 : req, res, next를 매개변수로 가지는 함수.
app.use((req, res, next) => {
console.log('모든 요청에 다 실행 됨');
next();
});
- 익스프레스 미들웨어들도 다음과 같이 축약 가능하다.
순서가 중요함.
static 미들웨어에서 파일을 찾으면 next를 호출 안 하므로 json, urlencoded, cookieParser는 실행되지 않는다.
app.use(
morgan('dev'),
express.static('/', path.join(__dirname, 'public')),
express.json(),
express.urlencoded({ extended: false }),
cookieParser(process.env.COOKIE_SECRET),
);
morgan, cookie-parser, express-session 설치.
app.use로 장착한다.
내부에서 알아서 next를 호출해서 다음 미들웨어로 넘어간다.
5. next 활용법
next를 주석 처리하면 응답이 전송되지 않는다.
다음 미들웨어(라우터 미들웨어)로 넘어가지 않기 때문.
next에 인수로 값을 넣으면 에러 핸들러로 넘어간다. ('route'인 경우 라우터로 넘어간다.)
- res.json은 응답을 보낼 뿐이지 함수 자체를 종료하는게 아님. 그래서 아래와 같이 작성 시 console.log 실행됨.
app.get('/', (req, res, next) => {
res.json({ hello: 'zeroro' });
console.log('실행 안되겠지?');
});
- 보통 에러 처리는 next 이용. next에 에러를 넣어서 에러 처리 미들웨어로 넘긴다.
app.use((req, res, next) => {
console.log('1 요청을 실행하고 싶어요.');
next();
}, (req, res, next) => {
try {
console.log(asdsada);
} catch (error) {
next(error);
}
});
app.use((err, req, res, next) => {
console.error(err);
res.status(200).send('에러 났지렁!');
});
- next('route')의 경우 다음 라우터로 넘어감.
app.get('/', (req, res, next) => {
res.sendFile(path.join(__dirname, 'index.html'));
next('route'); // 다음 라우터로 넘어감.
}, (req, res) => {
console.log('실행되나?'); // 실행 x
});
app.get('/', (res, req, next) => {
console.log('실행됨'); // 실행됨.
});
// 아래와 같이 if문에 따라 어떤 미들웨어 실행할지 가능. (next 분기처리)
app.get('/', (req, res, next) => {
res.sendFile(path.join(__dirname, 'index.html'));
if (true) {
next('route'); // if문이 true면 다음 라우터(실행됨)로 넘어감.
} else {
next(); // if문이 false면 (실행되나?)로 넘어감.
}
}, (req, res) => {
console.log('실행되나?');
});
app.get('/', (res, req, next) => {
console.log('실행됨');
});
### 미들웨어 간 데이터 전달하기
- req나 res 객체 안에 값을 넣어 데이터 전달 가능
- app.set과의 차이점 : app.set은 서버 내내 유지, req, res는 요청 하나 동안만 유지
- req.body나 req.cookies 같은 미들웨어의 데이터와 겹치지 않게 조심해야 함.
app.use((req, res, next) => {
req.data = '데이터 넣기';
next();
}, (req, res, next) => {
console.log(req.data); // 데이터 받기
next();
});
# 미들웨어 간 데이터 전달 예_1
app.use((req, res, next) => {
req.session.data = 'zerocho 비번';
});
app.get('/', (req, res, next) => {
req.session.data // 'zerocho 비번'
res.sendFile(path.join(__dirname, 'index.html'));
});
=> 계속 유지되고 싶은 데이터일때 사용.
단점 : 다음 요청때도 데이터가 남아있게 됨.
# 미들웨어 간 데이터 전달 예_2
app.use((req, res, next) => {
req.data = 'zerocho 비번';
});
app.get('/', (req, res, next) => {
req.data // 'zerocho 비번'
res.sendFile(path.join(__dirname, 'index.html'));
});
=> 요청 한번만 할 경우 사용.
### 미들웨어 확장하기
방법1)
app.use(morgan('dev'));
방법2)
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);
}
});
app.use('/', express.static(path.join(__dirname, 'public'))); 를 예로..
# 미들웨어 확장법 이용, 로그인 한 대상에게만 express.static 실행되도록 하려면
app.use('/', (req, res, next) => {
if (req.session.id) { // 로그인 시.
express.static(path.join(__dirname, 'public')))(req, res, next);
} else {
next();
}
});
6. morgan, bodyParser, cookieParser
### morgan
- 서버로 들어온 요청과 응답을 기록해주는 미들웨어이다.
- 로그의 자세한 정도 선택 가능(dev, tiny, short, common, combined)
app.use(morgan('dev'));
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const app = express();
app.set('port', process.env.PORT || 3000);
// 개발 시 사용.
app.use(morgan('dev'));
// 실무에서 사용(배포 시?)
// app.use(morgan('combined'));
app.use(cookieParser());
### cookie-parser
- 요청 헤더의 쿠키를 해석해주는 미들웨어
- parseCookies 함수와 기능이 비슷.
- req.cookies 안에 쿠키들이 들어있다.
# app.js
app.use(cookieParser(비밀키));
비밀키로 쿠키 뒤에 서명을 붙여 내 서버가 만든 쿠키임을 검증할 수 있다.
- 실제 쿠키 옵션들을 넣을 수 있다. (expires, domain, httpOnly, maxAge, path, secure, sameSite 등, 지울 때는 clearCookie로 (expires와 maxAge를 제외한 옵션들이 일치해야 함))
res.cookie('name', 'zerocho', {
expires : new Date(Date.now() + 900000),
httpOnly : true,
secure : true,
});
res.ClearCookie('name', 'zerocho', { httpOnly : true, secure : true });
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const app = express();
app.set('port', process.env.PORT || 3000);
// 개발 시 사용.
app.use(morgan('dev'));
// 실무에서 사용
// app.use(morgan('combined'));
app.use(cookieParser());
// cookieParser 이용 예.
app.get('/', (req, res) => {
req.cookies // { mycookie: 'test' 등으로 }알아서 파싱됨
//'Set-Cookie' : `name=${encodeURIComponent(name)}; Expires=${expires.toGMYString()}; HttpOnly; Path=/`,
res.cookie('name', encodeURIComponent(name), {
expires: new Date(),
httpOnly: true,
path: '/',
})
// 쿠키 삭제 시.
res.clearCookie('name', encodeURIComponent(name), {
httpOnly: true,
path: '/',
})
res.sendFile(path.join(__dirname, 'index.html'));
});
- cookieParser 엄호화? 서명된 쿠키 사용 예 (req.signedCookies 이용)
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const app = express();
app.set('port', process.env.PORT || 3000);
// 개발 시 사용.
app.use(morgan('dev'));
// 실무에서 사용
// app.use(morgan('combined'));
app.use(cookieParser(abcdefg));
app.get('/', (req, res) => {
req.cookies // { mycookie: 'test' 등으로 }알아서 파싱됨
req.signedCookies // 서명된 쿠키.
//'Set-Cookie' : `name=${encodeURIComponent(name)}; Expires=${expires.toGMYString()}; HttpOnly; Path=/`,
res.cookie('name', encodeURIComponent(name), {
expires: new Date(),
httpOnly: true,
path: '/',
})
// 쿠키 삭제 시.
res.clearCookie('name', encodeURIComponent(name), {
httpOnly: true,
path: '/',
})
res.sendFile(path.join(__dirname, 'index.html'));
});
### body-parser
- 요청의 본문을 해석해주는 미들웨어
- 폼 데이터나 AJAX 요청의 데이터 처리
- json 미들웨어는 요청 본문이 json인 경우 해석, urlencoded 미들웨어는 폼 요청 해석
- put이나 patch, post 요청 시에 req.body에 프런트에서 온 데이터를 넣어준다.
app.use(express.json());
app.use(express.urlencoded({ extended: false });
- 버퍼 데이터나 text 데이터일 때는 body-parser를 직접 설치해야 함.
콘솔
$ npm i body-parser
const bodyParser = require('body-parser');
app.use(bodyParser.raw());
app.use(bodyParser.text());
app.use(express.json());
app.use(express.urlencoded({ extended : true }));
// 클라이언트에서 form submit 할때_form 파싱 (extended는 쿼리스트링 처리관련. true일 경우 qs / false면 쿼리스트링 모듈 사용)
위 2개를 넣어주면 아래와 같이 사용 가능.
req.body.name
- Multipart 데이터(이미지, 동영상 등)인 경우는 다른 미들웨어를 사용해야 함.
7. static 미들웨어
- 정적인 파일들을 제공하는 미들웨어
- 인수로 정적 파일의 경로를 제공
- 파일이 있을 때 fs.readFile로 직접 읽을 필요 없음.
- 요청하는 파일이 없으면 알아서 next를 호출해 다음 미들웨어로 넘어간다.
- 파일을 발견했다면 다음 미들웨어는 실행되지 않는다.
// app.use('요청 경로', express.static('실제 경로'));
app.use('/', express.static(path.join(__dirname, 'public')));
- 컨텐츠 요청 주소와 실제 컨텐츠의 경로를 다르게 만들 수 있음. 이로 인해 서버의 구조를 파악하기 어려워져서 보안에 도움이 된다.
예_1)
요청경로 : localhost:3000/zerocho.html
실제경로 : learn-express/public-3030/zerocho.html
예_2)
요청경로 : localhost:3000/hello.css
실제경로 : learn-express/public-3030/hello.css
=>
예1, 예2 처럼 사용하면 서버 구조를 예측할 수 없어서 좋음.
- static의 경우 해당하는 파일을 찾은 경우 next를 진행하지 않음. 그래서 미들웨어 간의 순서가 중요함. (위에서 실행될 경우 밑에 미들웨어들이 실행되지 않을 수 있음)
8. express-session 미들웨어
app.use(session({
resave: false,
saveUninitialized: false,
secret:'zeropassword',
cookie: {
httpOnly: true, // 자바스크립트로 공격당하지 않기 위해 항상 설정.
secure: false,
},
name: 'connect.sid', // name의 기본값 : connect.sid(변경가능, 서명되어있어서 읽을수 없는 문자열로 바뀜)
}));
세션 쿠키에 대한 설정(secret: 쿠키 암호화, cookie: 세션 쿠키 옵션)
세션 쿠키는 앞에 s%3A가 붙은 후 암호화되어 프런트에 전송됨.
resave : 요청이 왔을 때 세션에 수정사항이 생기지 않아도 다시 저장할지 여부
saveUninitialized : 세션에 저장할 내역이 없더라도 세션을 저장할지
req.session.save로 수동 저장도 가능하지만 할 일 거의 없음.
- express-session은 요청마다 개인의 저장 공간을 만들어주는 것과 같음.
const session = require('express-session');
const app = express();
app.use(session());
app.get('/', (req, res, next) => {
req.session.id = 'hello'; // 이렇게 할 경우 요청을 보낸 사람의 id만 hello가 됨
res.sendFile(path.join(__dirname, 'index.html'));
})
9. 멀티파트 데이터 형식 (multer 사용하기)
- form 태그의 enctype이 multipart/form-data인 경우
- body-parser로는 요청 본문을 해석할 수 없음
- multer 패키지 필요.
# 콘솔
$ npm i multer
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="image" />
<input type="text" name="title" />
<button type="submit">업로드</button>
</form>
### multer 미들웨어들
- single과 none, array, fields 미들웨어 존재.
- single은 하나의 파일을 업로드 할 때, none은 파일은 업로드하지 않을 때
- req.file 안에 업로드 정보 저장
# upload.single
app.post('/upload', upload.single('image'), (req, res) => {
console.log(req.file, req.body);
res.send('ok');
});
# upload.none
app.post('/upload', upload.none(), (req, res) => {
console.log(req.body);
res.send('ok');
});
- array와 fields는 여러 개의 파일을 업로드 할 때 사용.
- array는 하나의 요청 body 이름 아래 여러 파일이 있는 경우.
- fields는 여러 개의 요청 body 이름 아래 파일이 하나씩 있는 경우.
- 두 경우 모두 업로드된 이미지 정보가 req.files 아래에 존재.
app.post('/upload', upload.array('many'), (req, res) => {
console.log(req.files, req.body);
res.send('ok');
});
10. dotenv 사용
# 콘솔
$ npm i dotenv
- 별도의 .env 파일을 생성하여 db 패스워드 등 비밀키 저장.
# .env 파일 예시
COOKIE_SECRET=zerochocookiesecret
DB_PASSWORD=nodejsbook
- .env 파일의 경우 드라이브 or 클라우드에 절대 올리면 안됨. 그리고 권한별로 알 수 있는 비밀키를 다르게 해줘야 함. (보안 위험)
11. Router 객체로 라우터 분리하기.
### express.Router
- app.js가 길어지는 것을 막을 수 있다.
- userRouter의 get은 /user 와 / 가 합쳐져서 GET /user/ 가 됨.
# routes/index.js
const express = require('express');
const router = express.Router();
// GET / 라우터
router.get('/', (req, res) => {
res.send('Hello Express');
});
module.exports = router;
# routes/user.js
const express = require('express');
const router = express.Router();
// GET /user 라우터
router.get('/', (req, res) => {
res.send('Hello user');
});
module.exports = router;
# app.js
...
const path = require('path');
dotenv.config();
const indexRouter = require('./routes');
const userRouter = require('./routes/user');
...
app.use('/', indexRouter);
app.use('/user', userRouter);
app.use((req, res, next) => {
res.status(404).send('Not Found');
});
...
### 라우트 매개변수
- :id를 넣으면 req.params.id로 받을 수 있음
- 동적으로 변하는 부분을 라우트 매개변수로 만듦
req.get('/user/:id', function(req, res) {
console.log(req.params, req.query);
});
req.get('/user/:id', function(req, res) {
console.log('얘만 실행됨');
});
req.get('/user/like', function(req, res) {
console.log('전혀 실행되지 않음');
});
- /users/123?limit=5&skip=10 주소 요청일 경우 아래와 같이 콘솔 출력
# 콘솔
{ id: '123' } { limit: '5', skip: '10' }
### 404 미들웨어
- 요청과 일치하는 라우터가 없는 경우를 대비해 404 라우터 만들기.
req.get((req, res, next) => {
res.status(404).send('Not Found');
});
- 이게 없으면 단순히 Cannot GET 주소 라는 문자열이 표시됨.
### 라우터 그룹화
req.get('/abc', (req, res) => {
res.send('GET /abc');
});
req.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.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 : req.app 처럼 res 객체를 통해 app 객체에 접근할 수 있다.
- res.cookie(키, 값, 옵션) : 쿠키를 설정하는 메서드.
- res.clearCookie(키, 값, 옵션) : 쿠키를 제거하는 메서드.
- res.end() : 데이터 없이 응답을 보냄.
- res.json(JSON) : JSON 형식의 응답을 보냄
- res.redirect(주소) : 리다이렉트할 주소와 함께 응답을 보냄
- res.render(뷰, 데이터) : 템플릿 엔진을 렌더링해서 응답할 때 사용하는 메서드.
- res.send(데이터) : 데이터와 함께 응답을 보냄. 데이터는 문자열 or HTML or 버퍼 or 배열일 수 있음
- res.sendFile(경로) : 경로에 위치한 파일을 응답함
- res.set(헤더, 값) : 응답의 헤더를 설정함
- res.status(코드) : 응답 시의 HTTP 상태 코드를 지정함
### 기타
res
.status(201)
.cookie('test', 'test')
.redirect('/admin')
응답을 여러 번 보내는 경우 아래와 같은 에러 발생 가능.
Can't set headers after they are sent.
12. 템플릿 엔진
- 템플릿 엔진은 HTML의 정적인 단점을 개선.
- 반복문, 조건문, 변수 등을 사용할 수 있음.
- 동적인 페이지 작성 가능
- PHP, JSP와 유사함.
### Pug (구 Jade)
- 문법이 Ruby와 비슷해 코드 양이 많이 줄어듬
- HTML과 많이 달라 호불호가 갈림.
- 익스프레스에 app.set으로 퍼그 연결
...
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(morgan('dev'));
...
### Pug_변수
- res.render에서 두 번째 인수 객체에 Pug 변수를 넣음
router.get('/', function(req, res, next) {
res.render('index', { title : 'Express' });
});
- res.locals 객체에 넣는 것도 가능(미들웨어간 공유됨)
router.get('/', function(req, res, next) {
res.locals.title = 'Express';
res.render('index');
});
- = 이나 #{ } 으로 변수 렌더링 가능 (= 뒤에는 자바스크립트 문법 사용 가능)
- 퍼그 파일 안에서 변수 선언 가능. (- 뒤에 자바스크립트 사용)
- 변수 값을 이스케이프 하지 않을 수 있음 (자동 이스케이프)
- for in 또는 each in으로 반복문 사용 가능 (값과 인덱스 가져올 수 있음)
- if else if else문, case when문 사용 가능
### Pug_include
- 퍼그 파일에 다른 퍼그 파일을 넣을 수 있음
- 헤더, 푸터, 내비게이션 등의 공통 부분을 따로 관리할 수 있어 편리함.
- include로 파일 경로 지정
### Pug_extends와 block
- 레이아웃을 정할 수 있음.
- 공통되는 레이아웃을 따로 관리할 수 있어 좋음. include와도 같이 사용.
13. 넌적스 템플릿 엔진
- Pug의 문법에 적응되지 않는다면 넌적스를 사용하면 좋다.
- 아래와 같이 콘솔에 입력하여 넌적스 설치.
# 콘솔
$ npm i nunjucks
- 확장자는 html 또는 njk(view engine을 njk로)
...
const path = require('path');
const nunjucks = require('nunjucks');
dotenv.config();
const indexRouter = require('./routes');
const userRouter = require('./routes/user');
const app = express();
app.set('port', process.env.PORT || 3000);
app.set('view engine', 'html');
nunjucks.configure('views', {
express: app,
watch: true,
});
app.use(morgan('dev'));
...
### 넌적스_변수
<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>
<button class="{{title}}" type="submit">전송</button>
<input placeholder="{{title}} 연습"/>
- 내부 변수 선언 가능 {%set 자바스크립트 구문}
# 변수 예_1
# 넌적스
{% set node = 'Node.js' %}
{% set js = 'Javascript' %}
<p>{{node}}와 {{js}}</p>
# html
<p>Node.js와 Javascript</P>
# 변수 예_2
# 넌적스
<p>{{'<strong>이스케이프</strong>'}}</P>
<p>{{'<strong>이스케이프 하지 않음</strong>' | safe }}</p>
# html
<p><strong%gt;이스케이스</strong%gt;</p>
<p><strong>이스케이프 하지 않음</strong></p>
### 넌적스_반복문
{% %} 안에 for in 작성 (익덱스는 loop 키워드)
# 반복문 예_1
# 넌적스
<ul>
{% set fruits = ['사과', '배', '오렌지', '바나나', '복숭아'] %}
{% for item in fruits %}
<li>{{item}}</li>
{% endfor %}
</ul>
# html
<ul>
<li>사과</li>
<li>배</li>
<li>오렌지</li>
<li>바나나</li>
<li>복숭아</li>
</ul>
# 반복문 예_2
# 넌적스
<ul>
{% set fruits = ['사과', '배', '오렌지', '바나나', '복숭아'] %}
{% for item in fruits %}
<li>{{loop.index}}번째 {{item}}</li>
{% endfor %}
</ul>
# html
<ul>
<li>1번째 사과</li>
<li>2번째 배</li>
<li>3번째 오렌지</li>
<li>4번째 바나나</li>
<li>5번째 복숭아</li>
</ul>
### 넌적스_조건문
# 조건문 예_1
# 넌적스
{% if isLoggedIn %}
<div>로그인 되었습니다.</div>
{% else %}
<div>로그인이 필요합니다.</div>
{% endif %}
# html
<!-- isLoggedIn이 true 일 때 -->
<div>로그인 되었습니다.</div>
<!-- isLoggedIn이 false 일 때 -->
<div>로그인이 필요합니다.</div>
# 조건문 예_2
# 넌적스
{% if fruit === 'apple' %}
<p>사과 입니다.</p>
{% elif fruit === 'banana' %}
<p>바나나 입니다.</p>
{% elif fruit === 'orange' %}
<p>오렌지 입니다.</p>
{% else %}
<p>사과도 바나나도 오렌지도 아닙니다.</P>
{% endif %}
# html
<!-- fruite이 apple일 때 -->
<p>사과 입니다.</p>
<!-- fruite이 banana일 때 -->
<p>바나나 입니다.</p>
<!-- fruite이 orange일 때 -->
<p>오렌지 입니다.</p>
<!-- 기본값 -->
<p>사과도 바나나도 오렌지도 아닙니다.</P>
### 넌적스_include
- 파일이 다른 파일을 불러올 수 있음
- include에 파일 경로 넣어줄 수 있음
# 넌적스
## header.html
<header>
<a href="/">Home</a>
<a href="/about">About</a>
</header>
## footer.html
<footer>
<div>푸터 입니다.</div>
</footer>
## main.html
{% include "header.html" %}
<main>
<h1>메인 파일</h1>
<p>다른 파일을 include할 수 있습니다.</p>
</main>
[% include "footer.html" %}
# html
<header>
<a href="/">Home</a>
<a href="/about">About</a>
</header>
<main>
<h1>메인 파일</h1>
<p>다른 파일을 include할 수 있습니다.</p>
</main>
<footer>
<div>푸터 입니다.</div>
</footer>
### 넌적스_레이아웃
- 레이아웃을 정할 수 있음. 공통되는 레이아웃을 따로 관리할 수 있어서 좋다. (include와 같이 사용)
# 넌적스
## layout.html
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
<link rel="stylesheet" href="/style.css">
{% block style %}
{% endblock %}
</head>
<body>
<header>헤더입니다.</header>
{% block content %}
{% endblock %}
<footer>푸터입니다.</footer>
{% block script %}
{% endblock %}
</body>
</html>
## body.html
{% extends 'layout.html' %}
{% block content %}
<main>
<p>내용입니다.</p>
</main>
{% endblock %}
{% block script %}
<script src="/main.js"></script>
{% endblock %}
### 에러 처리 미들웨어
- 에러 발생 시 템플릿 엔진과 상관없이 템플릿 엔진 변수를 설정하고 error 템플릿을 렌더링함
- res.locals.변수명으로도 템플릿 엔진 변수 생성 가능
- process.env.NODE_ENV는 개발환경인지, 배포환경인지 구분해주는 속성.
...
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
error.status = 404;
next(error);
});
app.use((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');
});
...