상세 컨텐츠

본문 제목

[Node.js 1] 6장 익스프레스 웹 서버 만들기

23-24/Node.js 1

by Hetbahn 2023. 11. 17. 10:00

본문

728x90

 

 

 

 

 

 

  • 익스프레스 프로젝트 시작하기

- 익스프레스는 대표적인 웹 서버 프레임워크이다.

- npm init 명령어를 콘솔에서 호출하여 package.json을 제일 먼저 생성해야 한다. 

// package.json

{
  "name": "learn-express",
  "version": "0.0.1",
  "description": "익스프레스를 배우자",
  "main": "app.js",
  "scripts": {
    "start": "nodemon app"
  },
  "author": "ZeroCho",
  "license": "MIT"
}
// 콘솔

$ npm i express
$ npm i -D nodemon

- scripts 부분에 start 속성을 넣어줘야 한다.

- nodemon app은 app.js를 nodemon으로 실행한다는 뜻이다. 서버 코드를 수정하면 nodemon이 서버를 자동으로 재시작한다.

 

// app.js

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'), '번 포트에서 대기 중');
});

- Express 모듈을 실행해 app 변수에 할당한다.

- 익스프레스 내부에는 http 모듈이 내장되어 있으므로 서버의 역할을 할 수 있다.

- app.set('port', 포트)로 서버가 실행될 포트를 설정한다.

- app.set(키, 값)을 사용해서 데이터를 저장할 수 있으며, app.get(키) 데이터를 가져올 수 있다.

- app.get(주소, 라우터)는 주소에 대한 GET 요청이 올 때 어떤 동작을 할지 정한다.

- req는 요청에 관한 정보가 들어 있고, res는 응답에 관한 정보가 들어 있다.

- 익스프레스에서는 res.write res.end 대신 res.send를 사용한다.

// 콘솔

$ npm start

- http://localhost:3000으로 접속할 수 있다.

 

- 문자열 대신 HTML로 응답하고 싶다면 res.sendFile 메서드를 사용한다.

// app.js

const express = require('express');
const path = require('path');

const app = express();
app.set('port', process.env.PORT || 3000);
app.get('/', (req, res) => {
  // res.send('Hello, Express');
  res.sendFile(path.join(__dirname, '/index.html'));
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

 

 

 

  • 자주 사용하는 미들웨어

 

- 미들웨어(middleware)는 요청과 응답의 중간(middle)에 위치한다.

- 미들웨어는 app.use(미들웨어)의 형태로 사용된다.

// app.js

...
app.set('port', process.env.PORT || 3000);

app.use((req, res, next) => {
  console.log('모든 요청에 다 실행됩니다.');
  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.listen(app.get('port'), () => {
...

- app.use에 매개변수가 req, res, next인 함수를 넣는다.

- next는 다음 미들웨어로 넘어가는 함수이다. next를 실행하지 않으면 다음 미들웨어가 실행되지 않는다.

- 주소를 첫 번째 인수로 넣어주지 않는다면 미들웨어는 모든 요청에서 실행된다.

- 위 코드에서 app.get('/')의 두 번째 미들웨어에서 에러가 발생하고, 에러는 그 아래에 있는 에러 처리 미들웨어에 전달된다.

- 에러 처리 미들웨어는 매개변수가 err, req, res, next 반드시 네 개이다. err에는 에러에 관한 정보가 담겨 있다.

- res.status 메서드로 HTTP 상태 코드를 지정하며, 기본값은 200(성공)이다.

 

// 콘솔

$ npm i morgan cookie-parser express-session dotenv

- morgan, cookie-parser, express-session, dotenv는 자주 사용되는 패키지이다.

 

// app.js

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');

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,
    secure: false,
  },
  name: 'session-cookie',
}));

app.use((req, res, next) => {
  console.log('모든 요청에 다 실행됩니다.');
  next();
});
...

 

// .env

COOKIE_SECRET=cookiesecret

- app.js를 수정하고 .env 파일을 생성한다.

- 패키지들을 불러와 app.use에 연결한다. req, res, next는 미들웨어 내부에 들어 있으며, next는 내부적으로 호출된다.

- dotenv 패키지는 .env 파일을 읽어서 process.env로 만든다.

- process.env.COOKIE_SECRET cookiesecret 값이 할당된다.

- 비밀 키는  별도의 파일 .env에 비밀 키를 적어두고 dotenv 패키지로 비밀 키를 로딩하는 방식으로 관리한다.

 

1. morgan

- morgan 미들웨어는 요청과 응답에 대한 정보를 콘솔에 기록한다.

- morgan 미들웨어는 app.use(morgan('dev')); 와 같이 사용한다.

- GET / 500 7.409 ms - 50와 같은 정보가 출력되는데, 각각 [HTTP 메서드] [주소] [HTTP 상태 코드] [응답 속도] - [응답 바이트]를 의미한다.

 

2. static

- static 미들웨어는 정적인 파일들을 제공하는 라우터 역할을 한다.

- 기본적으로 제공되므로 express 객체에서 꺼내어 app.use('요청 경로', express.static('실제 경로'));와 같이 사용한다.

 

3. body-parser

- 요청의 본문에 있는 데이터를 해석해서 req.body 객체로 만드는 미들웨어이다. 보통 폼 데이터나 AJAX 요청의 데이터를 처리한다.

- json형식의 데이터를 해석할 때, app.use(express.json());와 같이 사용한다.

 

4. cookie-parser

- 요청에 동봉된 쿠키를 해석해 req.cookies 객체로 만드는 미들웨어이다.

- app.use(cookieParser(비밀 키));와 같이 사용한다. 비밀 키를 통해 해당 쿠키가 내 서버에서 만든 쿠키임을 검증한다.

- 쿠키는 클라이언트에서 위조하기 쉬우므로 비밀 키를 통해 만들어낸 서명을 쿠키 값 뒤에 붙인다. 서명된 쿠키는 req.signedCookies 객체에 들어있다.

- 쿠키를 생성할 때 res.cookie(키, 값, 옵션) 메서드를, 쿠키를 제거할 때 res.clearCookie 메서드를 사용한다.

 

5. express-session

- 세션 관리용 미들웨어이다. 세션은 사용자별로 req.session 객체 안에 유지된다.

- express-session은 인수로 세션에 대한 설정 객체를 받는다.

app.use(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
  name: 'session-cookie',
}));

- 세션 관리 시 클라이언트에 쿠키를 보낸다.

- 쿠키를 서명하는 데 secret의 값이 필요하며, secret: process.env.COOKIE_SECRET, 으로  cookie-parser의 secret과 같게 설정하는 것이 좋다.

- cookie 옵션은 세션 쿠키에 대한 설정이다.

req.session.name = 'zerocho'; // 세션 등록
req.sessionID; // 세션 아이디 확인
req.session.destroy(); // 세션 모두 제거

- express-session으로 만들어진 req.session 객체에 값을 대입하거나 삭제해서 세션을 변경할 수 있다.

 

6. 미들웨어의 특성 활용하기

- 미들웨어는 req, res, next를 매개변수로 갖는 함수로서 app.use app.get, app.post 등으로 장착한다.

- 다음 미들웨어로 넘어가려면 next 함수를 호출해야 한다. 내부적으로 next를 호출하는 미들웨어들도 있다.

- express.static과 같은 미들웨어는 정적 파일을 제공할 때 next 대신 res.sendFile 메서드로 응답을 보낸다.

- 미들웨어 안에 미들웨어를 넣는 방식으로 조건문에 따라 다른 미들웨어를 적용할 수도 있다.

 

7. multer

- 이미지, 동영상 등을 비롯한 여러 가지 파일을 멀티파트 형식으로 업로드할 때 사용하는 미들웨어이다.

- 멀티파트 형식이란   enctype multipart/form-data인 폼을 통해 업로드하는 데이터의 형이다.

// 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>

 

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 },
});

- multer 함수의 인수로 설정을 넣는다.

- 위 코드에서  storage 속성에는 어디에(destination) 어떤 이름으로(filename) 저장할지를 넣었다.

- req 매개변수에는 요청에 대한 정보가, file 객체에는 업로드한 파일에 대한 정보가 있다.

- req file의 데이터를 가공해서 done 함수로 넘긴다.

- 위  설정으로는 uploads라는 폴더에 [파일명+현재시간.확장자] 파일명으로 업로드한다.

- limits 속성에는 업로드에 대한 제한 사항을 설정할 수 있다.

- 위 설정을 적용하려면 서버에 uploads 폴더가 존재해야 한다.

 

- 설정이 끝나면 upload 변수가 생기는데, 여기에 다양한 종류의 미들웨어가 들어 있다.

- 파일을 하나만 업로드하는 경우에는 single 미들웨어를 사용한다. 업로드 결과는 req.file 객체 안에 들어 있다.

- 여러 파일을 업로드하는 경우 HTML의 input 태그에는 multiple을 쓰고, 미들웨어는 array를 사용한다. 업로드 결과는 req.files 배열에 들어 있다.

 

 

 

  • Router 객체로 라우팅 분리하기
// app.js

...
const path = require('path');

dotenv.config();
const indexRouter = require('./routes');
const userRouter = require('./routes/user');
...
  name: 'session-cookie',
}));

app.use('/', indexRouter);
app.use('/user', userRouter);

app.use((req, res, next) => {
  res.status(404).send('Not Found');
});

app.use((err, req, res, next) => {
...

- 위 코드는 index.js와 user.js를 app.use를 통해 app.js에 연결한다. ( index.js는 생략할 수 있다.)

- 에러 처리 미들웨어 위에 404 상태 코드를 응답하는 미들웨어를 추가한다.

// 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;

 

 /user/:id 와 같은 라우트 매개변수를 사용하여 /users/1이나 /users/123 등의 요청도 처리할 수 있다.

 

 

 

  •  req, res 객체 살펴보기

- req.app: app 객체에 접근할 수 있다. req.app.get('port')와 같은 식으로 사용할 수 있다.
- req.body: body-parser 미들웨어가 만드는 요청의 본문을 해석한 객체이다.
- req.cookies: cookie-parser 미들웨어가 만드는 요청의 쿠키를 해석한 객체이다.
- req.ip: 요청의 ip 주소가 담겨 있다.
- req.params: 라우트 매개변수에 대한 정보가 담긴 객체이다.
- req.query: 쿼리스트링에 대한 정보가 담긴 객체이다.
- req.signedCookies: 서명된 쿠키들이 담겨 있다.
- req.get(헤더 이름): 헤더의 값을 가져오고 싶을 때 사용하는 메서드이다.

 

- res.app: app 객체에 접근할 수 있다.

- res.cookie(키, 값, 옵션): 쿠키를 설정하는 메서드이다.

- res.clearCookie(키, 값, 옵션): 쿠키를 제거하는 메서드이다.

- res.end(): 데이터 없이 응답을 보낸다.

- res.json(JSON): JSON 형식의 응답을 보낸다.

- res.locals: 하나의 요청 안에서 미들웨어 간에 데이터를 전달하고 싶을 때 사용하는 객체이다.

- res.redirect(주소): 리다이렉트할 주소와 함께 응답을 보낸다.

- res.render(뷰, 데이터): 템플릿 엔진을 렌더링해서 응답할 때 사용하는 메서드이다.

- res.send(데이터): 데이터와 함께 응답을 보낸다.

- res.sendFile(경로): 경로에 위치한 파일을 응답한다.

- res.set(헤더, 값): 응답의 헤더를 설정한다.

- res.status(코드): 응답 시의 HTTP 상태 코드를 지정한다.

 

 

 

  • 템플릿 엔진 사용하기

1. 퍼그(제이드)

- 익스프레스와 연결하려면 app.js에 아래 코드가 들어 있어야 한다.

// app.js

...
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(morgan('dev'));
...

- views는 템플릿 파일들이 위치한 폴더를 지정한다. (res.render('index')는 views/index.pug를 렌더링한다.)

// 퍼그

HTML

doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    
// HTML

<!DOCTYPE html>
<html>
  <head>
    <title>익스프레스</title>
    <link rel="stylesheet" href="/style.css" />
  </head>
</html>

- 퍼그와 HTML 문법으로 작성한 위의 두 코드는 일치한다.

- 변수는 태그나 속성 뒤에 =을 붙이거나  #{변수}으로 참조한다.

- each 또는 for을 사용하여 반복문을 작성할 수 있다.

- if, else if, else를 사용하여 조건문을 작성할 수 있다.

- include 파일 경로의 형태로 다른 pug나 HTML 파일을 넣을 수 있다.

 

2. 넌적스

- HTML 문법을 그대로 사용하되, 추가로 자바스크립트 문법을 사용할 수 있다.

- 넌적스에서 변수는 {{ }}로 감싼다. 변수를 선언할 때는 {% set 변수 = '값' %}를 사용한다.

- 반복문은 {% %} 안에 쓰며, for in문과 endfor 사이에 위치시킨다.

- 조건문은 {% if 변수 %} {% elif %} {% else %} {% endif %}를 사용한다.

- {%  include 파일 경로 %}의 형태로 다른 HTML 파일을 넣을 수 있다.

 

3. 에러 처리 미들웨어

// app.js

...
app.use((req, res, next) => {
  const error =  new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, 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');
});
...

- 404 에러가 발생하면 res.locals.message는 ‘${req.method} ${req.url} 라우터가 없습니다.’가 된다.

- 에러 처리 미들웨어는 error라는 템플릿 파일을 렌더링한다. 서버를 실행하고 접속하면 에러 메시지를 확인할 수 있다.

 

 

 

 


 

빈칸 채우기 문제 - (빈칸을 드래그해서 답을 확인해 보세요)

 

1. (   app.set('port', 포트)  ) 메서드로 서버가 실행될 포트를 설정한다.

2. (  res.sendFile  ) 메서드를 사용해  단순한 문자열 대신 HTML로 응답할 수 있다.

3. 에러 처리 미들웨어는 매개변수가 (  err  ), (  req  ), (  res  ), (  next  )로 네 개이다.

4. cookie-parser는 요청에 동봉된 쿠키를 해석해 (  req.cookies  ) 객체로 만든다.

5. express-session 미들웨어를 이용하면 세션은 사용자별로 (  req.session  ) 객체 안에 유지된다.

6. 미들웨어는 (  app.use  ) (  app.get  ), (  app.post  )등으로 장착한다.

7. 대표적인 템플릿 엔진으로는 (  퍼그  )와 (  넌적스  )가 있다.

 

 

코드 문제 

 

 1. 주소 '/'에 대한 GET 요청이 올 때 'Hello World'라는 문자열을 응답하는 코드를 작성하시오.

const express = require('express');

const app = express();
app.set('port', process.env.PORT || 3000);

// 여기에 코드를 작성

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '번 포트에서 대기 중');
});

 답 :

더보기

app.get('/', (req, res) => {
  res.send(' Hello World');
});

(더보기로 확인)

 

 

 

2. 쿠키에 서명을 추가하기 위한  secret의 값을 작성하시오. ( cookie-parser의 secret과 값이 같도록 설정하시오.)

app.use(session({
  resave: false,
  saveUninitialized: false,
  secret: /* 여기에 코드를 작성 */,
  cookie: {
    httpOnly: true,
    secure: false,
  },
  name: 'session-cookie',
}));

답:

더보기

process.env.COOKIE_SECRET

(더보기로 확인)

 


출처: 조현영 ,  『Node.js 교과서』 개정판 3판, 길벗, 6.1장 ~ 6.6장

Node.js #1

Editor : 호박

728x90

관련글 더보기