Posts 노드 스터디 6장
Post
Cancel

노드 스터디 6장

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 형식의 데이터를 해석할 땐 따로 설치해줘야함.

  • 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나 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");
        
    • 반복문
      • 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 으로 함
  • 에러처리 미들웨어 : 새로운게 아니라 그냥 배운 템플릿엔진으로 에러처리 미들웨어 구현하는 것.

This post is licensed under CC BY 4.0 by the author.