Express 로 웹서버 만들기
6.1 익스프레스 프로젝트 시작
1
2
3
4
5
6
7
8
9
10
11
12
const express = require("express");
const app = express();
app.set("port", process.env.PORT || 3000);
app.get("/", (req, res) => {
res.send("Hello, Express");
});
app.listen(app.get("port"), () => {
console.log(app.get("port"), "번 포트에서 대기 중");
});
- app.set(‘port’, 값) 으로 포트 설정
- app.set(키, 값) 으로 다른 정보도 저장 가능. 나중에 app.get(키) 로 가져올 수 있음
- app.get(주소, 라우터) : 이 주소로 GET 요청이 들어올때 처리.
- app.listen() : 포트에서 요청 대기하고 서버 실행.
- res.send() : res.write(), res.end() 대응. res.status(200).send() 처럼 코드 등록 가능. 기본 200.
- res.sendFile() : 파일을 대신 보낼 수 있음. html 파일을 보낼 때 사용
- res.sendFile(path.join(__dirname, “/index.html”));
- 이 코드는 path 모듈이 있어야함.
6.2 자주 사용하는 미들웨어
- app.use() 로 등록함.
- 요청과 응답을 조작하여 기능을 추가하거나, 나쁜 요청을 걸러냄. 아래처럼 req, res, next을 받는 콜백으로 등록함.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
app.use((req, res, next) => {
console.log("모든 요청에 다 실행됩니다.");
next();
});
app.use("/abc", (req, res, next) => {
// abc로 시작하는 요청에 실행
console.log("abc로 시작하는 요청에 실행됩니다.");
next();
});
app.get("/abc", (req, res, next) => {
// abc로 시작하는 get 요청에 실행.
console.log("abc로 시작하는 get 요청에 실행됩니다.");
next();
});
app.post("/abc", (req, res, next) => {
// abc로 시작하는 post 요청에 실행.
console.log("abc로 시작하는 post 요청에 실행됩니다.");
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.get, app.post 등과 같은 것들도 next를 인자로 받아 미들웨어로 활용할 수 있음.
또 마지막 바로 위 예시에서처럼, 여러개를 등록할 수 있다. 여기선 에러가 발생하는데. 이렇게 에러가 발생시 그 아래에 에러처리 미들웨어에 전송됨.
에러처리 미들웨어 : 매개변수가 반드기 네 개여야함. 가장 아래 위치/
미들웨어는 위에서부터 아래로 순서대로 실행됨.
dotenv 패키지
- .env 파일을 읽어 process.env로 만듬. process.end.COOKIE_SECRET으로 사용가능.
6.2.1 morgan
- 요청과 응답에 대한 정보를 콘솔에 기록함.
- app.use(morgan(‘dev’)) 로 등록
- dev : 개발환경
- combined : 배포환경
- common, short, tiny 등도 있음
6.2.2 static
- 정적인 파일들을 제공하는 라우터 역할.
- 기본적으로 제공되기에 따로 설치할 필요는 없음
1
2
3
app.use("요청 경로", express.static("실제 경로"));
app.use("/", express.static(path.join(__dirname, "public")));
- 외부인이 서버 구조를 쉽게 파악하지 못하게하여 보안에 도움을 줌.
- 요청경로에 해당하는 파일이 없으면, 알아서 next를 호출함.
- 파일을 발견하면, 응답으로 파일을 보내고 next를 호출하지 않음.
6.2.3 body-parser
- 요청의 본문(body)에 있는 데이터를 해석해서 req.body 객체로 만들어줌.
- 이미지, 동영상, 파일 데이터는 처리하지 못함. (이건 multer로 해결)
- 익스프레스 4.16.0 버전부터 express 에 내장되어있기에 따로 설치하지 않고 다음처럼 사용
1
2
3
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// false이면 내장된 querystring을 사용하고, true면 npm 패키지인 qs를 사용
하지만, Raw, Text 형식의 데이터를 해석할 땐 따로 설치해줘야함.
6.2.4 cookie-parser
app.use(cookieParser(비밀키));
- 동본된 쿠키를 해석해 req.cookies 객체로 만듬.
- 첫번째 인수로 비밀키를 넣어주어 서명된 쿠키가 있는 경우, 제공한 비밀키를 통해 해당 쿠키가 서버가 만든 쿠키임을 검증할 수 있음.
- 서명된 쿠키는 req.signedCookies 객체에 들어있음.
- 쿠키를 생성/제거할때는 res.cookie(키,값,옵션), res.clearCookie() 메소드를 사용
- 쿠키를 지우려면 키와 값 외에 옵션도 정확히 일치해야 지워짐. 단, expire나 maxAge는 달라도 됨.
1
2
3
4
5
6
res.cookie("name", "zerocho", {
expires: new Date(Date.now() + 900000),
httpOnly: true,
secure: true,
});
res.clearCookie("name", "zerocho", { httpOnly: true, secure: true });
- 옵션중엔 signed 라는 옵션이 있는데, true로 설정하면 쿠키뒤에 서명이 붙음.
- 대부분은 서명옵션을 켜두는 것이 좋음. 비밀키는 .env에 넣어두면 됨.
6.2.5 express-session
- 세션 관리 시 클라이언트에 쿠키를 보냄.
1
2
3
4
5
6
7
8
9
10
11
12
app.use(
session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
},
name: "session-cookie",
})
);
- 인수로 세션에 대한 설정을 받음.
- resave : 요청이 올 때 세션에 수정사항이 생기지 않더라도 세션을 다시 저장할지 설정함.
- saveUninitialized : 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정함.
- express-session 1.5 버전 이전에는 cookie-parser 후에 둬야하는데, 이후 버전에서는 상관없음.
- 서명을 하려면 secret 옵션에 키를 넣어준다. 이때 키는 cookie-parser의 키와 같게 한다.
- cookie 옵션 : 세션 쿠키 옵션
- store : 메모리에 세션을 저장할 것이냐
- 이렇게 만든 req.session 객체에 값을 대입하거나 삭제해서 세션을 변경한다.
- req.session.name = ‘zerocho’
- req.session.destory()
6.2.6 미들웨어의 특성 활용하기
- next에 인수넣기
- “route” : 다음 라우터의 미들웨어로 바로 이동함.
- 이외의 인수 : err 미들웨어로 이동함. -> err 미들웨어의 err 매개변수가 됨.
- 다음 미들웨어로 데이터 넘겨주기
- req.data 에 넣어주면 된다.
- 다른 이름과 겹치지 않게만 해주자.
- 유용한 패턴 : 미들웨어 안에 미들웨어 넣기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
});
6.2.7 multer
- 이미지, 동영상을 비롯한 여러가지 파일들을 멀티파트 형식으로 업로드할 때 사용함.
- 멀티파트 형식 : enctype이 multipart/form-data 인 폼을 통해 업로드하는 데이터의 형식
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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.originalname, ext) + Date.now() + ext);
},
}),
limits: { fileSize: 5 * 1024 * 1024 },
});
//upload 에 있는 미들웨어 사용
// single : 하나만 업로드하는 경우.
app.post("/upload", upload.single("image"), (req, res) => {
console.log(req.file, req.body);
res.send("ok");
});
// html form에 multiple로 설정되어 여러가지가 오는 겨웅
// array로 교체
app.post("/upload", upload.array("many"), (req, res) => {
console.log(req.files, req.body);
res.send("ok");
});
//여러개를 업로드하지만, input 태그나 폼 데이터의 키가 다른 경우엔 fields 사용
app.post(
"/upload",
upload.fields([{ name: "image1" }, { name: "image2" }]),
(req, res) => {
console.log(req.files, req.body);
res.send("ok");
}
);
// 파일을 업로드하지 않고, 멀티파트 형식으로 업로드하는 경우. none 사용
app.post("/upload", upload.none(), (req, res) => {
console.log(req.body);
res.send("ok");
});
6.3 Router객체로 라우팅 분리하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//routes/user.js
const express = require("express");
const router = express.Router();
router.get("/", (req, res) => {
res.send("Hello, User");
});
module.exports = router;
// app.js
const userRouter = require("./routes/user");
app.use("/user", userRouter);
- 기본적으로 위와같이 분리하고, 연결하면 된다.
- 이때 각 폴더의 index.js 파일은 생략가능.
- 라우터는 같은 주소로 몇개를 불러도 next 만 제대로 호출해주면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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");
});
- :id 같이 매개변수를 받을 수 있는데, 같은 주소를 사용하는 라우터의 맨 뒤에 있어야함.
- 아니면 이게 와일드카드처럼 다 잡아먹음
1
2
3
4
5
6
7
router.get("/user/:id", function (req, res) {
res.send("Hello, Express");
});
// 도달불가
router.get("/user", function (req, res) {
res.send("Hello, Express");
});
- app.route 나 router.route 로 주소는 같지만 메서드가 다른 코드를 한 덩어리로 묶을 수 있음
1
2
3
4
5
6
7
8
router
.route("/abc")
.get((req, res) => {
res.send("GET /abc");
})
.post((req, res) => {
res.send("POST /abc");
});
6.4 req, res 객체 살펴보기
- express의 req, res 객체는 http 모듈의 req, res 객체를 확장한 것.
확장된 것들
- req
- req.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(데이터): 데이터와 함께 응답을 보냅니다. 데이터는 문자열일 수도 있고 HTML일 수도 있으며, 버퍼일 수도 있고 객체나 배열일 수도 있습니다.
- res.sendFile(경로): 경로에 위치한 파일을 응답합니다.
- res.set(헤더, 값): 응답의 헤더를 설정합니다.
- res.status(코드): 응답 시의 HTTP 상태 코드를 지정합니다.
- req
- req나 res 객체의 메서드는 다음과 같이 메서드 체이닝을 지원함.
1
res.status(201).cookie("test", "test").redirect("/admin");
6.5 템플릿 엔진 사용하기
템플릿 엔진 : 자바스크립트를 사용해서 HTML 을 렌더링 -> 따라서 효율적인 HTML 작성을 하게 해줌.
퍼그(pug)
- 다음과 같은 코드가 있어야함.
1 2
app.set("views", path.join(__dirname, "views")); app.set("view engine", "pug");
- views 는 템플릿 파일들이 위치한 폴더를 지정하는 것.
- res.render가 이 폴더를 기준으로 템플릿엔진을 찾아 렌더링함.
- view engine은 어떠한 종류의 템플릿 엔진을 사용할지 나타냄.
- 문법은 찾아보면서 하면 될듯.
- 변수
- res.render(주소, 변수) : 이렇게해서 변수를 전달할 수 있다.
1
res.render("index", { title: "Express" });
- 변수를 넣는 대신에 res.locsls 객체로 넣을 수도 있음.
- 이렇게하면 다른 미들웨어에서도 접근할 수 있어서 좋다.
1 2
res.locals.title = "Express"; res.render("index");
- res.render(주소, 변수) : 이렇게해서 변수를 전달할 수 있다.
- 반복문
- each A, idx in [B, C, D]
- 이런식으로 반복문을 돌릴 수 있다. 뽑는 두번째인자는 index
- 조건문
- if, else if, else 문으로 분기처리 가능.
- case-when 문으로 switch-case 가능
- include
- 다른 퍼그나 HTML 파일을 넣을 수 있음.
- extends와 block
- 레이아웃을 정활 수 있음. 공통되는 레이아웃부분을 따로 관리할 수 있다. include와 함께 사용됨.
넌적스
- 퍼그와는 달리 HTML 문법을 크게 변화하지 않는다. HTML + js 느낌.
- 연결방법은 다음과 같음
1 2 3 4 5 6 7
const nunjucks = require("nunjucks"); app.set("view engine", "html"); nunjucks.configure("views", { express: app, watch: true, });
- 변수 : 퍼그와 똑같이 날림
- 반복문 : for ~ in 으로 함
에러처리 미들웨어 : 새로운게 아니라 그냥 배운 템플릿엔진으로 에러처리 미들웨어 구현하는 것.