익스프레스 프로젝트를 시작해보자.
항상 package.json을 제일 먼저 생성해야 한다.
자동으로 생성해주는 npm.init 명령어.
{
"name": "learn-express",
"version": "0.0.1",
"description": "익스프레스를 배우자",
"main": "app.js",
"scripts": {
"start": "nodemon app"
},
"author": "HaechanKim",
"license": "MIT"
}
script부분에 start속성은 꼭 넣어줘야 한다.
nodemon app을 하면 app.js를 nodemon으로 실행한다는 뜻
서버 코드에 수정 생길때마다 매번 서버 재시작하기 귀찮으므로 nodemon 모듈로 서버를 자동으로 재시작함.
nodemon은 개발용으로만 사용하는 것 권장.
배포 후에는 서버 코드 빈번히 변경될 일 없기때문에 nodemon 사용 안해도 됨.
서버의 역할을 할 app.js
const express = require('express');
// Express 모듈 실행해 app 변수에 할당
// 익스프레스 내부에 http모듈 내장되어 있으므로 서버 역할 할 수 있다.
const app = express();
// app.set('port', 포트)로 서버 실행될 포트 설정함
// process.env 객체에 PORT속성 있다면 그 값 사용하고
// 없다면 기본값 3000번 포트 이용
// 이렇게 app.set(키, 값) 사용해 데이터 저장 가능
// 나중에 데이처를 app.get(키)로 가져올 수 있다.
app.set('port', process.env.PORT || 3000);
// app.get(주소, 라우터)는 주소에 대한 GET요청이 올 때 어떤 동작을 할지 적는 부분
// req는 요청에 관한 정보 들어있는 객체, res는 응답
// 현재 GET / 요창 시 응답으로 hello 전송
app.get('/', (req, res) => {
// 익스프레스에서는 res.write나 res.end 대신 res.send 사용
res.send('Hello Express!!');
});
// lsiten 부분은 http 웹 서버와 동일함.
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 줃');
});
문자열 대신 html로 응답하고 싶다면 res.sendFile 메서드 사용.
단 파일의 경로를 path모듈 사용해 지정해야 함.
const express = require('express');
const path = require('path');
// app.js
// Express 모듈 실행해 app 변수에 할당
// 익스프레스 내부에 http모듈 내장되어 있으므로 서버 역할 할 수 있다.
const app = express();
// app.set('port', 포트)로 서버 실행될 포트 설정함
// process.env 객체에 PORT속성 있다면 그 값 사용하고
// 없다면 기본값 3000번 포트 이용
// 이렇게 app.set(키, 값) 사용해 데이터 저장 가능
// 나중에 데이처를 app.get(키)로 가져올 수 있다.
app.set('port', process.env.PORT || 3000);
// app.get(주소, 라우터)는 주소에 대한 GET요청이 올 때 어떤 동작을 할지 적는 부분
// req는 요청에 관한 정보 들어있는 객체, res는 응답
// 현재 GET / 요창 시 응답으로 hello 전송
app.get('/', (req, res) => {
// 익스프레스에서는 res.write나 res.end 대신 res.send 사용
//res.send('Hello Express!!');
res.sendFile(path.join(__dirname, '/index.html'));
});
// lsiten 부분은 http 웹 서버와 동일함.
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 줃');
});
이제 익스프레스 서버에 다양한 기능을 추가해보자.
간단히 말해 클라이언트에게 요청이 오고 그 요청을 보내기 위해
응답하려는 중간(미들)에 목적에 맞는 처리를 하는, 거쳐가는 함수.
예를 들어 요청-응답 도중에 시간을 콘솔 창에 남기고 싶다면
미들웨어 함수를 중간에 넣어 표시를 한 뒤 계속해서 다음 미들웨어들을 처리할 수 있도록 하면 됨.
const express = require('express');
const path = require('path');
// app.js
const app = express();
app.set('port', process.env.PORT || 3000);
// app.use에 매개변수가 req, res, next인 함수를 넣으면 됨
// 미들웨어는 요청과 응답 사이에 특별한 기능 추가 가능
// next는 다음 미들웨어로 넘어가는 함수
// next실행 안하면 다음 미들웨어 실행된지 않음.
// 주소 첫 인수로 안넣으면 미들웨어는 모든 요청에서 실행되고
// 주소 넣는다면 해당 요청에서만 실행 됨
// app.use(미들웨어) : 모든 요청에서 미들웨어 실행
// app.use('/abc', 미들웨어) : abc로 시작하는 요청에서 미들웨어 실행
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됨.');
next();
});
app.get('/', (req, res, next) => {
console.log('GET / 요청에서만 실행됨');
next(); // 호출 안하면 다음 미들웨어로 안넘어감
}, (req, res) => { // 여기서 에러 발생 해 밑의 에러 처리 미들웨어로 넘어감. 미들웨어 1개 이상 시용 가능
throw new Error('에러는 에러 처리 미들웨어로 감.');
});
// 에러 처리 미들웨어는 매개변수 다 사용 안해도 4개여야 함.
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나 app.get같은 라우터에 미들웨어 여러 개 장착 가능.
현재 app.get라우터에 미들웨어 두개 연결되어 있음
but 이때도 next 호출해야 다음 미들웨어로 넘어감
미들웨어를 통해 요청과 응답에 다양한 기능을 추가할 수 있다.
morgan, cookie-parser, express-session 미들웨어를 설치해보자.
dotenv는 process.env 관리 위해 설치.
.env 파일을 생성한다.
파일명이 .env 이고 확장자는 없다.
// .env
COOKIE_SECRET-cookiesecret
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');
// app.js
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, // client에서 쿠키 확인 못함. only http 통신 에서만. 즉 js같은 외부 프로그램은 접근 못함
secure: false, // https가 아닌 환경에서도 사용할 수 있도록.
},
name: 'session-cookie', // 세션 쿠키의 이름
}));
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됨.');
next();
});
app.get('/', (req, res, next) => {
console.log('GET / 요청에서만 실행됨');
next(); // 호출 안하면 다음 미들웨어로 안넘어감
}, (req, res) => { // 여기서 에러 발생 해 밑의 에러 처리 미들웨어로 넘어감
throw new Error('에러는 에러 처리 미들웨어로 감.');
});
// 에러 처리 미들웨어는 매개변수 다 사용 안해도 4개여야 함.
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들은 미들웨어 내부에 들어있다.
next도 내부적으로 호출하기에 다음 미들웨어로 넘어갈 수 있다.
dotenv 패키지는 .env파일 읽어서 process.env로 만든다. (dot(.) + env)
process.env.COOKIE_SECRET에 cookiesecret 값이 할당됨.
키=값 형식으로 추가하면 됨.
pricess.env를 별도 파일로 관리하는 이유는 보안과 설정 편의 때문
비밀 키들을 소스코드에 적어두면 소스코드 유출 시 비밀 키도 같이 유출된다.
따라서 env 같은 별도 파일에 비밀 키 적어두고 dotenv 패키지로 미빌 키 로딩하는 식으로 관리
=> env만 잘 지키면 됨.
각각의 미들웨어를 살펴보자.
1. morgan
morgan 연결 후 localhost:3000에 접속하면 기존 로그 외 추가 로그 볼 수 있다.
요청과 응답에 대한 정보를 콘솔에 기록.
morgan 미들웨어는 다음과 같이 사용
app.use(morgan('dev'));

dev 모드 기준으로 GET / 500 10.615ms - 44 는 각각 HTTP 메서드, 주소, HTTP 상태 코드, 응답 속도, 응답 바이트를 의미한다.
요청과 응답 한눈에 볼 수 있어 편리.
2. static
static 미들웨어는 정적 파일 제공하는 라우터 역할.
내장 미들웨어 이므로 별도 설치 필요 없음.
app.use('요청 경로', express.static('실제 경로'));
app.use('/', express.static(path.join(__dirname, 'public')));
인수로 정적 파일 담겨있는 폴더 지정하면 됨.
현재는 public 폴더 지정돼 있음.
예를 들어 public/stylesheets/style.css는
http://localhost:3000/stylesheets/style.css로 접근할 수 있다.
public폴더 만들고 그 안에 css, js, 이미지 파일 넣으면 브라우저에서 접근할 수 있다.
실제 서버 폴더 경로에는 public들어 있지만 요청 주소에는 없다는 점 주의.
서버의 폴더 경로와 요청 경로 다르므로 보안에 용이.
또한 정적 파일 알아서 제공해주므로 fs.readFile로 파일 직접 읽어 전송할 필요 없다.
3. body-parser
요청(req)의 본문에 있는 데이터 해석해서 req.body객체로 만들어주는 미들웨어.
보통 폼 데이터나 AJAX 요청의 데이터 처리.
단 멀티파트(이미지, 동영상, 파일) 데이터는 처리 못함. (대신 multer 모듈 사용.)
내장 미들웨어.
app.use(express.json());
app.use(express.urlencoded({extended: false}));
요청 데이터 종류를 살펴보자.
JSON은 JSON 형식 데이터 전발 방식이고, URL-encoded는 주소 형식으로 데이터 보내는 방식.
폼 전송은 URL-encoded 주로 사용.
URL-encoded 형식으로 name=zerocho&book=nodejs를 본문으로 보낸다면 req.body에 JSON형식으로 변환되어 들어감.
false 옵션은 querystring 모듈 사용해 쿼리스트링 해석하고,
true면 qs 모듈 사용해 해석.
qs는 내장 모듈 아닌 querystring 기능 확장한 모듈.
이전에 POST, PUT 요청의 본문 전달받으려면 req.on('data'), req.on('end')로 스트림 사용해야 했지만,
body-parser 사용하면 그럴 필요 없음.
내부적으로 스트림 처리해 req.body에 추가. (b-p 등록 안하면 body라는 프로퍼티가 req 내에 없으므로 undefined 출력됨)
4. cookie-parser
요청(req)에 동봉된 쿠키 해석해 req.cookies 객체로 만든다.
이전의 parseCookies 함수와 비슷한 기능.
app.use(cookieParser(비밀키));
app.use(cookieParser(process.env.COOKIE_SECRET)); // .env에 작성해둔 비밀 키
해석된 쿠키들은 req.cookies 객체에 들어감.
name=bluesun 쿠키를 보냈다면 req.cookies는 {name: 'bluesun'}이 됨.
서명된 쿠키가 있는 경우 비밀 키 통래 검증 가능.
쿠키는 client에서 의조 쉬우므로 비밀 키 통해 만들어낸 서명을 쿠키값 뒤에 붙임 (name=bluesun.sign).
서명된 쿠키는 req.cookies 대신 req.signedCookies 객체에 들어있음.
cookie-parser가 쿠키 생성할때 쓰이는 것은 아님.
쿠키 생성/제거에는 res.cookie, res.clearCookie 메서드 써야 함.
res.cookie(키, 값, 옵션) 형태.
지울때도 키, 값, 옵션 정확히 일치해야 함.
res.cookie('name', 'bluesun', { // 생성
expires: new Date(Date.now() + 900000),
httpOnly: true,
secure: true,
});
res.clearCookie('name', 'bluesun', { httpOnly: true, secure: true }); // 삭제
express-session은 세션 관리 시 client에 쿠키 보냄(세션 쿠키).
안전하게 쿠키 전송하려면 쿠키에 서명 추가해야 함. 이때 secret 값 필요.
cookie-parser의 secret 값과 같게 설정하는 것이 좋다.
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됨.');
next();
});
미들웨어는 req, res, next를 매개변수로 갖는 함수로서 app.use, app.get, app.poty등으로 장착.
특정 주소의 요청에만 실행되혀면 첫 인수로 주소 넣기.
동시에 여러개 장착 가능 하지만 next 호출해줘야 다음으로 넘어감.
미들웨어 사용 시 유용한 패턴중 하나로 미들웨어 안에 미들웨어 넣는 방식이 있다.
다음 두 방식을 같은 역할을 한다.
app.use(morgan('dev'));
// 또는
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.js에서 app.get같은 메서드가 라우터 부분.
라우터 많이 연결하면 app.js 코드 길어지므로 익스프레스에서는 라우터 분리 방법 제공.
routes 폴더를 만들고 index.js와 user.js를 작성해보자.
const express = require('express');
// index.js
const router = express.Router();
// GET / 라우터
router.get('/', (req, res) => {
res.send('Hello, Express!!');
});
module.exports = router;
const express = require('express');
// user.js
const router = express.Router();
// GET /user 라우터
router.get('/', (req, res) => {
res.send("Hello, User!!");
});
module.exports = router;
만든 index.js, user.js를 app.use를 통해 app.js 에 연결.
또한 에러 처리 미들웨어 위에 404상태 코드 응답하는 미들웨어 추가.
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');
// app.js
dotenv.config();
const indexRouter = require('./routes'); // 파일면 index면 생략 가능. 다른건 안됨. ./routes/index
const userRouter = require('./routes/user');
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('/', indexRouter); // http://localhost:3000
app.use('/user', userRouter); // http://localhost:3000/user
app.use((req, res, next) => { // 위 두 주소 아닌 경우
res.status(404).send('NOT FOUND!!');
})
// 에러 처리 미들웨어는 매개변수 다 사용 안해도 4개여야 함.
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'), '번 포트에서 대기중');
})
indexRouter를 ./routes로 require 할 수 있는 이유는 index.js는 생략 가능하기 때문
require('./routes/index.js')와 require('./routes')는 같다.
index.js와 user.js는 ahdid qltmtgkwlaks 서로 다른 주소의 라우터 역할.
app.use로 연결할 때의 차이 때문.
indexRouter는 use의 '/'와 get의 '/'가 합쳐셔 GET / 라우터가 되었고,
UserRouter는 use의 '/user'와 get의 '/'가 합쳐셔 GET /user 라우터가 됨.
--> app.use로 연결할 때 주소 합쳐지는 것 염두에 두자.
서버 실행 뒤 localhost:3000과 localhost:3000/user 각각 접속하면 해당하는 응답 받을 수 있다.



next함수에는 다음 라우터로 넘어가는 기능 있다.
next('route') 사용하면 라우터에 연결된 나머지 미들웨어 건너뛰고 다음 라우터로 넘어감.
const express = require('express');
// index.js
const router = express.Router();
// GET / 라우터
/*router.get('/', (req, res) => {
res.send('Hello, Express!!');
});*/
router.get('/', function(req, res, next) {
console.log('실행 됨1');
next('route');
}, function(req, res, next){
console.log('실행 안됨');
next();
});
router.get('/', function(req, res) {
console.log('실행 됨2');
res.send('Hello, Express!!')
});
module.exports = router;
위 예제처럼 같은 주소의 라우터 여러개 만들어도 됨.
라우터 주소에는 정규표현식 비록한 특수 패턴 사용 가능
자주 쓰이는 패턴 중 라우트 매개변수 라는 패턴이 있다.
router.get('user/:id', function(req, res) {
console.log(req.params, req.query);
});
주소에 :id가 있는데 문자 그대로 :id를 의미하는 것이 아니다.
이 부분에는 다른 값 넣을 수 있는데 /users/1이나 /users/123등의 요청도 이 라우터가 처리
이 방식의 장점은 :id에 해당하는 1,123 조회할 수 있다는 점이며 req.params 객체 안에 들어있다.
:id면 req.params.id로, :type이면 req.params.type로 조회할 수 있다.
단 주의할 점은 일반 라우터보다 뒤에 위치해야 다른 라우터를 방해하지 않는다.
주소에 쿼리스트링 쓰는 경우도 있다.
쿼리스트링의 키-값 정보는 req.query 객체 안에 들어있다.
/users/123?limit=5&skpi=10 이라는 주소 요청 들어왔을때
req.params와 req.query 객체는 다음과 같다.
{id: '123'}, {limit: '5', skip:'10}
라우터에서 자주 쓰이는 활용법으로 app.route나 router.route가 있다.
주소 같지만 메서드 다른 코드 있을 때 이를 한 덩어리로 줄일 수 있음.
// 이 코드를
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 메서드 체이닝 지원하는 경우 많다. 코드 양 줄일 수 있음.
대표적인 템플릿 엔진 Pug(Jade)와 Nunjucks를 살펴보자.
앞으로의 예제는 넌적스 사용 예정.
// app.js
...
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug'); // 어떤 종류의 템플릿 엔진 사용할 지
...
views는 템플릿 파일들이 위치한 폴더 지정.
res.render메서드가 이 폴더 기준으로 템플릿 엔진 찾아 렌더링 함.
res.render('index') 라면 views/index.pug 렌더링 함.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<title>익스프레스</title>
<link rel = "stylesheet" href = "/style.css" />
</head>
</html>
퍼그
doctype html
html
head
title=title
link(rel='stylesheet', jref='/stylesheets/style.css')
속성 중 아이디와 클래스 있는 경우 다음과 같이 표현
div 태그인 경우 div 문자는 생략 가능.
https://thebook.io/080229/ch06/05/01/01-01/ 참고
퍼그 부분 넘김
넌적스
넌적스는 모질라에서 만든 템플릿을 HTML문법 그대로 사용하되 js문법 사용할 수 있다.
퍼그와는 연결 방법이 다르다.
변수
res.render 호출 시 보내는 변수를 넌적스가 처리.