[6장] Express

2022. 5. 5. 00:42책/Node.js 교과서

1. 프로젝트 시작

익스프레스는 http모듈의 불편함을 개선한 웹 서버 프레임워크이다(라이센스 : MIT) 이를 이용해서 서버를 만들어볼건데 그에 앞서 Node.js에서 프로젝트를 시작할 땐 무조건 package.json 파일을 만들고 시작하자. 그 후 Express 패키지를 다운받아준다. 책에선 CommonJS를 사용했지만 임의로 ESM으로 바꿔봤다.

 

1.1 기본

// CommonJS 대신 ESM을 사용하기로 했다 (내 맘)
// __dirname은 ESM에선 없음

import express from 'express';
import path from 'path';

// path.resolve( [path] )
// 인수로 전달받은 경로들을 절대경로로 변환한다
// 만약 전달받은 인수가 없으면 현재 작업중인 디렉토리값을 반환
const __dirname = path.resolve();

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

// GET 요청에 대한 라우트
// 당연히 POST, PUT, PATCH, DELTE, OPTIONS에 대한 라우터도 있음
app.get('/', (req, res) => {
	// 파일을 응답한다
    res.sendFile(path.join(__dirname, '/index.html'));
});

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

 

1.2 req, res객체

익스프레스에서도 http모듈과 똑같이 req, res 객체를 사용하는데, 실제로 http모듈의 req, res 객체를 확장한 것에 불과하다. 즉, 기존 http 모듈의 메서드도 사용 가능하며 익스프레스가 추가한 메서드/속성 또한 사용할 수 있다. 이거저거 많긴 하지만 자주 쓰이는 것 위주로만 알아보자. (참고로 두 객체 모두 메서드 체이닝이 가능하다)

 

1) req객체

req.app app객체에 접근
req.body request의 body부분을 body-parser 미들웨어가 파싱해 담아놓은 객체
req.cookies request에 담긴 쿠키를 cookie-parser 미들웨어가 파싱해 담아놓은 객체
req.ip 라우트 매개변수에 대한 정보가 담긴 객체
req.params 쿼리스트링에 대한 정보가 담긴 객체
req.signedCookies 서명된 쿠키가 담겨있는 객체
req.get( 헤더이름 ) 헤더의 값을 가져온다

 

2) res객체

res.app app 객체에 접근
res.cookie(키, 값, 옵션) 쿠키를 설정
res.clearCookie(키, 값, 옵션) 쿠키를 제거
res.end( ) 데이터 없이 응답을 보냄
res.json( JSON ) JSON 형식의 응답을 보냄
res.redirect( 주소 ) 전달한 주소로 리다이렉트시킴
res.render( 뷰, 데이터 ) 템플렛 엔진을 렌더링
res.send( 데이터 ) 데이터와 함께 응답을 보냄 ( 데이터 : <string> <buffer> <obj> <array> <file>
res.sendFile( 경로 ) 경로에 위치한 파일을 보냄
res.set( 헤더, 값 ) 응답의 헤더를 설정
res.status( 코드 ) 응답 시의 HTTP 상태 코드를 지정

 

2. 미들웨어

요청과 응답 중간에서 특정 기능을 수행하거나 나쁜 요청을 걸러내는 등의 역할을 한다. 뒤에서 나오겠지만 라우터나 에러 핸들러 또한 미들웨어의 일종인데, 사실상 이 미들웨어를 비교적 자유롭게 쓸 수 있다는 점이 Express의 장점이자 전부이다(물론 Koa 같은걸 더 좋아하는 사람도 있긴 하지만)

 

미들웨어는 여러개를 사용할 수 있으며, 등록한 순서대로 큐처럼 쌓여서 실행된다. next( ) 함수를 호출하면 다음 미들웨어로 넘어가며, next('router')를 호출하면 다음 라우터로 넘어간다. 그 외 next( 아무거나 ) 함수에 인수로 아무 값이나 넣게되면 에러 핸들러로 넘어가며, 넣어준 인수는 에러 핸들러의 err 인수로 들어간다.

ex) next( myObj ) 호출하면 → [에러핸들러] app.use( (err, req, ... ) => { ... } )의 err 자리에 myObj가 들어간다는 뜻

 

2.1 미들웨어 기본

# app.use( [ path,] callback [, callback...] )
미들웨어 함수를 등록한다. 만약 path를 입력하면 해당 경로로 request가 왔을때만 미들웨어를 실행할 수 있다.
  - path : 단순 string이나 정규표현식, array 전부 입력 가능
  - callback : 미들웨어 함수를 등록한다. 여러개 등록할 수 있으며, 라우터도 일종의 미들웨어이므로 당연히 등록이 가능하다. 

 

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

콘솔창
모든 요청에 대해 실행된다
GET 요청중 / 경로에대한 요청에서만 실행
Error: 에러는 에러 처리 미들웨어로

※ 미들웨어 간 데이터 전달하기

A, B, C라는 미들웨어를 장착해서 쓰고있다고 하자. 만약 A에서 전달받은 데이터를 B, C에게도 전달해주고 싶다면 어떻게 할까? 그럴땐 req객체에 공간을 하나 만들어서 데이터를 쑤셔박자! 그러면 미들웨어를 타고 해당 데이터가 쭉쭉 내려간다. (ex: req.myData = "이 데이터는 유지되어야한다" ... 이러면 미들웨어간 데이터 공유 끝!)

 

2.2 실제로 자주쓰는 미들웨어

※ dotenv (.env) : process.env를 관리하기 위한 라이브러리. 미들웨어는 아닌데 무조건 쓰는 라이브러리라 추가했다.  최상단에 import 'dotenv/config'로 호출해주기만 하면 끝.

 

2.2.1 Express 자체 내장 미들웨어

1) express.static( root, [options] )
 - root : 제공할 파일들이 위치한 디렉토리
 - options : 이거저거 있는데 나중에 궁금해지면 여기를 참고하자

정적인 파일들을(예를 들어 html, css, js같은) 제공하는 라우터 역할의 미들웨어. 만약 요청 경로에 파일이 없으면 404 응답코드 대신 next( )를 호출해 다음 미들웨어로 넘어간다. 보통 'public' 폴더 안에 제공할 파일들을 집어넣고 root를 public으로 잡아준다. 이 미들웨어의 장점은

 

  • 서버측에선 public폴더 안에서 파일을 꺼내주지만 요청경로엔 public같은 경로가 없으므로 외부인이 보기에 서버의 구조 파악이 어렵다
  • 정적 파일을 알아서 제공해주므로 fs.readFile 같은 메서드를 쓸 필요가 없다
  • 파일을 발견 못했다치더라도 404로 바로 에러를 띄우는게 아니라 next( )를 호출하므로 유연하게 대처할 수 있다

2) express.json( [options] )
Express 내장 미들웨어. request body에 담긴 JSON 형태의 내용을 파싱한 뒤 req.body에 객체 형태로 담아 리턴한다. 단, request 헤더의 Content-type이 맞는 경우에만.

 

3) express.urlencoded( [options] )

Express 내장 미들웨어. request body에 담긴 URLEncdoed 형식의 내용을 파싱한 뒤 req.body에 객체 형태로 담아 리턴한다. options으로 6개를 지정해줄 수 있는데, 그중 extended <Boolean>옵션은 어떤 모듈을 사용해 파싱할지 정하는 옵션이다. ( extended : false면 querystring모듈, true는 qs모듈을 씀 )

 

2.2.2 외부 라이브러리 미들웨어

1) morgan

HTTP request 관련 로그사항을 기록해주는 미들웨어 라이브러리. 귀찮게 생각할거 없이 걍 개발단계에선 morgan('dev'), 배포단계에선 morgan('combined')쓰자. 2년전에 마지막 업데이트된 라이브러리인데 뭐 그리 중요한가.

더보기

# moargan( format, options )
  - format : 3개의 인수(tokens, req, res)를 받는다. 요청/응답에 대하여 어떤 내용을 출력해줄건지(token) 정한다고 보면 됨
  - options : 이거저거 있는데, 사전에 정의된 포맷을 출력하도록 해주는 옵션이 메인이 (dev, combined, common, short, tiny)

 

app.use(morgan('dev');
app.get('/', (req, res) => res.sendFile(path.join(__dirname, '/index.html'));

콘솔창
GET / 200 15.184 ms - 337

HTTP 메서드 : GET 요청
요청경로 : '/'
응답코드 : 200
응답시간 : 15.184ms
응답바이트 : 337Byte

 

2) body-parser

 

 

 

3) cookie-parser

request에 담긴 쿠키 내용을 파싱하여 req.cookies에 객체 형태로 담아 반환한다. 쿠키의 유효 기간이 지나면 알아서 걸러준다. 쿠키에 비밀 키를 넣어 서명을 추가할 수도 있는데, 이를 통해 클라이언트측에서 쿠키를 위조하는걸 방지하고 내 서버가 만든 쿠키임을 검증할 수 있다.

 

여기서 착각하지 말아야할 것은 쿠키를 '설정' 하는 것이 아니라, 쿠키를 담은 요청이 들어왔을 때 해당 쿠키를 '읽기 편하게' 파싱해준다는 것이다. 쿠키를 설정하고 클라이언트에 보내는 역할은 express의 response.cookie 메서드가 담당한다.

 

더보기

# cookieParser( [ secret ] , option )
 - secret <string>|< array > : 쿠키에 서명을 추가하기 위한 string이나 array를 전달받는다.
 - options : 쿠키에 담긴 내용을 어떤 형식으로 디코딩 할 것인지 전달. Default: decodeURIComponent

 

# res.cookie( name, value [, options] )
 - name=value 형식으로 쿠키값을 설정한다
 - options : 너무많아서 생략

 

1) 비밀키(secret) 없이 쿠키 보내고 읽기

app.use(cookieParser()); // 따로 secret 인수 안넣어줌
app.get('/home', (req, res, next) => {
    res.cookie('name', 'gyul', {
        maxAge: 3600,
        httpOnly: true,
        secure: true,
    });
    res.send('실험중');

    console.log(req.cookies);
});

콘솔창
{ name: 'gyul' }

2) 비밀키 설정하기

app.use(cookieParser(process.env.COOKIE_SECRET));
app.get('/home', (req, res, next) => {
    res.cookie('name', 'gyul', {
        maxAge: 3600,
        httpOnly: true,
        secure: true,
        signed: true,
    });
    res.send('실험중');

    console.log(req.signedCookies);
});

 

 

4) express-session

세션 관리용 미들웨어. 기본적으로 세션은 req.session 객체 안에 담기게 된다. 1.5.0버전 이후부턴 더이상 내부적으로 cookie-parser 미들웨어를 사용하지 않게 됐다. 이제 session모듈 내부적으로 쿠키를 read/write할 수 있게됐으며, cookie-parser와 함께 사용하고 싶다면 서명 추가에 주의해야한다(secret 부분 설정할 때 주의)

 

※ 참고 : cookie-parser와 함께 사용할 땐 cookieParser 다음 순서에 미들웨어를 달아주도록 하자

 

더보기

# session( options )
세션에 추가할 옵션을 지정할 수 있다. 옵션이 너무 많아서 중요한거 몇가지만 추려보기로 하자.
 - cookie : 세션 쿠키에 설정들을 달아준다. 항상 보던 expires, httpOnly, secure 같은 옵션들을 설정할 수 있다. (참고 : cookie.secure 권장 옵션이나 https 프로토콜에서만 사용 가능하며, http 프로토콜로 만들면 적용되지 않는다)
 - resave : 요청이 올 때 세션에 수정사항이 없어도 세션을 다시 저장할지 설정
 - saveUninitialized : 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정
 - secret : 쿠키에 추가할 서명. cookie-parser와 함께 사용중이라면 거기서 써준 secret값과 같아야한다
 - name : 세션 ID를 설정한다. default 값은 'connect.sid'
 - store : 세션을 저장할 store 객체를 설정한다. 지금은 DB를 안배워서 따로 설정은 안해줬지만 이후 자세히 알아보도록 하자(주로 레디스가 자주 쓰인다)

 

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


5) multer

파일 처리와 관련된 미들웨어인데 얘는 내용이 많아서 따로 정리하겠음

' > Node.js 교과서' 카테고리의 다른 글

[6장] Express / Router  (0) 2022.05.07
[6장] Express/multer  (0) 2022.05.06
[5장] npm 패키지 매니저  (0) 2022.05.04
[4장] 서버 만들기 (cluster)  (0) 2022.05.03
[4장] 서버 만들기(쿠키와 세션)  (0) 2022.05.03