express 웹 서버 만들기 -1(express, middleware, 자주쓰는 패키지)

백지연·2022년 1월 17일
4

NodeJS

목록 보기
9/26
post-thumbnail

이번 포스팅에서는 1. express, 2. 미들웨어, 3. 자주쓰는 패키지를 다루겠다.
책 Node.js 교과서(개정 2판) 책의 6장의 내용을 참고했다.

+모든 코드는 github주소에 있다.


1. express

express

  • 서버를 제작하는 과정에서의 불편함을 해소하고 편의 기능을 추가한 웹 서버 프레임워크
  • http 모듈의 요청과 응답 객체에 추가 기능을 부여
  • 코드를 분리하기 쉽게 만들어 관리 용이
  • http 모듈을 내장하고 있음

express project EXAMPLE )

1. ch6/express폴더 생성

2. npm init을 통해 express, nodemon을 설치

실행결과(생성된 파일 - node_modules, package-lock.json, package.json)
+package.json에 scripts에"start": "nodemon app"을 추가, main을 app.js로 변경
-> 이 작업을 하면 app.js를 nodemon으로 실행
--> nodemon 모듈은 서버 코드에 수정 사항이 있으면 서버를 자동으로 재시작 함

Git [express/package.json]

{
  "name": "delay100_second",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon app"
  },
  "author": "delay100",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.2"
  },
  "devDependencies": {
    "nodemon": "^2.0.15"
  }
}

3. app.js 작성

  • app.set(키, 값): 데이터 저장 (ex. app.set('port', 포트): 서버가 실행될 포트를 설정)
  • app.get(주소, 라우터): 주소에 대한 GET 요청이 올 때 어떤 동작을 할지 라우터 부분에 적음
  • listen: http와 동일

Git [express/app.js]

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

const app = express(); // express 모듈을 실행해 app 변수에 할당
app.set('port', process.envPORT || 3000); // process.env 객체에 PORT 속성이 있으면 그 값 사용, 없다면 기본값으로 3000번 포트 이용
                                          // key: 'port', value : 'process.envPORT || 3000'

app.get('/', (req, res) => { // GET / 요청 시 응답으로 Hello, Express를 전송
    //  res.send('Hello, Express');  http 모듈에서 사용했던 res.write나 res.end 대신 res.send 사용
    res.sendFile(path.join(__dirname, '/index.html')); // 다른 html 파일을 전송하고 싶은 경우
});

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

Git [express/index.html]

<html>
    <head>
        <meta charset="UTF-8"/>
        <title>익스프레스 서버</title>
    </head>
    <body>
        <h1>익스프레스</h1>
        <p>배워봅시다.</p>
    </body>
</html>

4. npm scripts 실행

입력(console) - 위에서 적은 scripts의 "start": nodemon app"을 통해 app.js가 실행됨

 npm start

결과(console)

...
Node.js v17.3.0
[nodemon] app crashed - waiting for file changes before starting...
[nodemon] restarting due to changes...
[nodemon] starting `node app.js`
3000 번 포트에서 대기 중

실행화면(웹 브라우저)


2. 미들웨어

다음으로 익스프레스의 핵심인 미들웨어를 알아보자.

미들웨어

  • 요청과 응답의 중간(미들(middle))에 위치
  • 요청과 응답을 조작하여 기능을 추가하기도 하고, 요청을 거르기도 함
  • 위에서부터 아래로 순서대로 실행되면서 요청과 응답 사이에 특별한 기능 추가 가능
  • 미들웨어 장착 순서에 따라 어떤 미들웨어는 실행되지 않을 수도 있음(ex. 중간에 정적파일이 있는 경우)

핵심

  • app.use(미들웨어): 모든 요청에서 미들웨어 실행
  • app.use('/abc', 미들웨어): abc로 시작하는 요청에서 미들웨어 실행
  • app.post('/abc', 미들웨어): abc로 시작하는 POST 요청에서 미들웨어 실행

미들웨어

  • err : 에러
  • req : 요청
  • res : 응답
  • next : 다음 미들웨어로 넘어가는 함수(실행하지 않으면 다음 미들웨어 실행x)

미들웨어 EXAMPLE )

Git [middleware/appex.js]

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

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

// app.use나 app.get같은 미들웨어를 여러개 장착 가능
app.use((req, res, next) => {
    console.log('모든 요청에 다 실행됩니다.');
    next();
});
app.get('/',(req, res, next) => {
    console.log('GET / 요청에서만 실행됩니다.');
    next();
}, (req, res) => { // 요청이 오면 무조건 에러 처리
    throw new Error('에러는 에러 처리 미들웨어로 갑니다.') 
});

// 위에 만약 next(err)가 있는 경우 바로 이 함수가 실행됨 
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'), '번 포트에서 대기 중');
});

출력

3000 번 포트에서 대기 중
모든 요청에 다 실행됩니다.
GET / 요청에서만 실행됩니다.
Error: 에러는 에러 처리 미들웨어로 갑니다.
...

실행결과(웹 브라우저)

미들웨어 간에 데이터를 전달하는 EXAMPLE )
Git [middleware/app.js] 中 일부

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

미들웨어를 사용할 때 유용한 패턴
**기존 미들웨어의 기능 확장 가능

app.use(morgan('dev'));
// 또는
app.use((req, res, next) => {
  morgan('dev')(req,res,next);
});
  • 패턴 사용 EXAMPLE )
app.use((req, res, next) => {
  if (process.env.NODE_ENV === 'production'){
  	morgan('combined')(req,res,next);
  } else{
  	morgan('dev')(req, res, next);
  }
});

3. 자주쓰는 패키지

실무에 자주 사용하는 패키지를 설치해보겠다.

1. 미들웨어 패키지

설치(console)

npm i morgan cookie-parser express-session
  • morgan
    -morgan 연결 후 localhost:3000에 다시 접속하면 기존 로그 외 추가적인 로그를 볼 수 있음
    -요청과 응답에 대한 정보를 콘솔에 기록
    -인수로 dev(개발 환경), combined(배포 환경), common, short, tiny 등 넣을 수 있음

morgan EXAMPLE )
입력

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

출력(console)

GET / 500 13.502 ms - 50

--

  • cookie-parser
    -요청에 동봉된 쿠키를 해석해 req.cookies 객체로 만듦
    -해석된 쿠키들은 req.cookies 객체에 들어감
    -서명된 쿠키: 제공한 비밀 키를 통해 해당 쿠키가 내 서버가 만든 쿠키임을 검증, req.signedCookies 객체에 name=delay100.sign 형태
    -쿠키를 생성/제거할 때는 쓰지 않음(res.cookie, res.clearCookie 메소드 사용)
    -parseCookies 함수와 기능이 비슷

ex) name=delay100 쿠키를 보냈다면 req.cookies는 { name: 'delay100' }가 됨 (유효 기간이 지난 쿠키는 알아서 거름)

cookie-parser EXAMPLE )

app.use(cookieParser(process.env.COOKIE_SECRET)); // 비밀키 할당, process.env.COOKIE_SECRET에 cookiesecret 값(키=값 형식)이 할당됨

GIT [middleware/.env]

COOKIE_SECRET=cookiesecret

+res.cookie, res.clearCookie EXAMPLE )
**쿠키를 지우러면 키와 값 외에 옵션도 정확히 일치해야 함(단, expires나 maxAge 옵션은 일치할 필요가 없음, signed: true하면 서명이 붙음

// cookie 생성(res.cookie)
            // 키, 값, 옵션
res.cookie('name', 'delay100', {
    expires: new Date(Date.now() + 900000),
    httpOnly: true,
    secure: true,
});
// cookie 제거(res.clearCookie)
res.clearCookie('name', 'delay100', {httpOnly: true, secure: true});

--

  • express-session
    -세션 관리용 미들웨어
    -세션 관리 시 클라이언트에 쿠키를 보냄
    -특정 사용자를 위한 데이터를 임시적으로 저장할 때 사용(ex. 로그인)
    -세션은 사용자별로 req.session 객체 안에 유지됨

express-session EXAMPLE )

// express-session
// 인수: session에 대한 설정
app.use(session({
    resave:false, // resave : 요청이 올 때 세션에 수정 사항이 생기지 않더라도 세션을 다시 저장할지 설정
    saveUninitialized: false, // saveUninitialized : 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정
    secret: process.env.COOKIE_SECRET,
    cookie: { // 세션 쿠키에 대한 설정
        httpOnly: true, // httpOnly: 클라이언트에서 쿠키를 확인하지 못하게 함
        secure: false, // secure: false는 https가 아닌 환경에서도 사용 가능 - 배포할 때는 true로 
                        // 코드에는 없지만, store: 데이터베이스에 연결해서 세션을 유지 
    },
    name: 'session-cookie',
}));

--
[express 내에 존재하는 미들웨어 패키지]

  • static
    -정적인 파일들을 제공하는 라우터 역할을 함
    -외부인이 서버의 구조를 파악하기 어렵게 함

static EXAMPLE )

app.use('요청 경로', express.static('실제 경로'));

app.use('/', express.static(path.join(__dirname, 'public'));

ex) 내 서버에서 만약, public/stylesheets/style.css 폴더에 저장하면 http://localhost:3000/stylesheets/style.css 으로 브라우저에서 접근 가능, 요청 주소에는 public이 들어있지 않음!
+파일이 없으면, 알아서 내부적으로 next 호출
+파일이 있으면, 다음 미들웨어는 실행되지 않음(응답으로 파일을 보내고 next를 호출하지 않음)

--

  • body-parser
    -요청의 본문에 있는 데이터를 해석해서 req.body 객체로 만들어주는 미들웨어
    -보통 폼 데이터나 AJAX 요청의 데이터를 처리
    -JSON, URL-encoded(주소 형식으로 데이터를 보내는 방식) 형식의 데이터를 해석할 수 있음 +Raw(요청의 본문이 버퍼 데이터일 때), Text(텍스트 데이터) 처리
    -멀티파트(이미지, 동영상, 파일) 데이터는 처리하지 못함(multer 모듈 사용할 것)

+익스프레스 4.16.0 버전부터는 body-parser 미들웨어의 일부 기능이 익스프레스에 내장되었으므로 따로 설치할 필요가 없음

body-parser EXAMPLE )

app.use(express.json());
app.use(express.urlencoded({extended: false})); // extended 옵션이 false면 노드의 querystring 모듈을 사용하여 쿼리스트링을 해석
                                                // extended 옵션이 true면 qs 모듈을 사용하여 쿼리스트링을 해석 - qs 모듈은 내장 모듈이 아닌 npm의 패키지(querystring 모듈의 기능을 좀 더 확장한 모듈임)

ex)

  • JSON 형식으로 { name: 'zerocho', book: 'nodejs' }를 본문으로 보내면 req.body에 그대로 들어간다.
  • URL-encoded 형식으로 name=zerocho&book=nodejs를 본문으로 보내면 req.body에 { name: 'zerocho', book: 'nodejs' }가 들어간다.

2. process.env를 관리하기 위한 패키지

  • dotenv
    -.env 패키지를 읽어서 process.env로 만듦
  • 보안과 설정의 편의성 제공

설치(console)

npm i dotenv

+패키지 전체 코드

Git [middleware/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);

// req, res, next들은 미들웨어 내부에 들어있음
// morgan
app.use(morgan('dev')); 
// static
app.use('/', express.static(path.join(__dirname, 'public'))); 
// body-parser
app.use(express.json());
app.use(express.urlencoded({extended: false})); // extended 옵션이 false면 노드의 querystring 모듈을 사용하여 쿼리스트링을 해석
                                                // extended 옵션이 true면 qs 모듈을 사용하여 쿼리스트링을 해석 - qs 모듈은 내장 모듈이 아닌 npm의 패키지(querystring 모듈의 기능을 좀 더 확장한 모듈임)
// cookie-parser
app.use(cookieParser(process.env.COOKIE_SECRET)); // 비밀키 할당, process.env.COOKIE_SECRET에 cookiesecret 값(키=값 형식)이 할당됨
// // cookie 생성(res.cookie)
//               키, 값, 옵션
// res.cookie('name', 'delay100', {
//     expires: new Date(Date.now() + 900000),
//     httpOnly: true,
//     secure: true,
// });
// // cookie 제거(res.clearCookie)
// res.clearCookie('name', 'delay100', {httpOnly: true, secure: true});

// express-session
// 인수: session에 대한 설정
app.use(session({
    resave:false, // resave : 요청이 올 때 세션에 수정 사항이 생기지 않더라도 세션을 다시 저장할지 설정
    saveUninitialized: false, // saveUninitialized : 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정
    secret: process.env.COOKIE_SECRET,
    cookie: { // 세션 쿠키에 대한 설정
        httpOnly: true, // httpOnly: 클라이언트에서 쿠키를 확인하지 못하게 함
        secure: false, // secure: false는 https가 아닌 환경에서도 사용 가능 - 배포할 때는 true로 
                        // 코드에는 없지만, store: 데이터베이스에 연결해서 세션을 유지 
    },
    name: 'session-cookie',
}));

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

app.use((req, res, next) => {
    console.log('모든 요청에 다 실행됩니다.');
    req.data = '데이터 넣기';
    next();
}, (req, res, next)=>{
    console.log(req.data);
    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'), () => {
    console.log(app.get('port'), '번 포트에서 대기 중');
});

Git [middleware/.env]

COOKIE_SECRET=cookiesecret

내용이 너무 길어질 것 같아, 다음 포스팅에서 'multer, Router 객체로 라우팅 분리하기'를 다루겠다.

profile
TISTORY로 이사중! https://delay100.tistory.com

0개의 댓글