반응형

# 익스프레스 웹 서버 만들기

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 서빙하기

  • sendFile 이용 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를 주석 처리하면 응답이 전송되지 않는다.

다음 미들웨어(라우터 미들웨어)로 넘어가지 않기 때문.

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'));
  • morgan 예 (dev, combined)
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 });
  • cookieParser 예
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());
  • bodyParser 예
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
  • 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>

### 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 사용

  • 아래와 같이 입력하여 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로 묶음
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>&lt;strong%gt;이스케이스&lt;/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>

### 넌적스_조건문

  • {% if %} 안에 조건문 작성
# 조건문 예_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');
});
...
반응형

+ Recent posts