6 익스프레스 웹 서버 만들기 (1)

Bor·2022년 1월 2일
0

Node.js

목록 보기
10/11

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

이제 본격적으로 익스프ㄹ스 프로젝트를 시작해보장. 먼저 learn-express 폴더를 만든다. 항상 package.json을 제일 먼저 생성해야. package.json을 생성해주는 npm init 명령어를 콘솔에서 호출해도 된다.

scipts 부분에 start 속성은 꼭 넣어줘야 한다. nodemon app을 하면 app.js를 nodemon으로 실행한다는 뜻. 서버 코드에 수정 사항이 생길 때마다 매번 서버를 재시작하기는 귀찮으니 nodemon 모듈로 서버를 자동으로 재시작. 앞으로 서버 코드를 수정하면 nodemon이 서버를 자동으로 재시작하기는 귀찮으므로 nodemon 모듈로 서버를 자동으로 재시작. 앞으로 서버 코드를 수정하면 nodemon이 서버를 자동으로 재시작하며 nodemon이 실행되는 콘솔에 rs를 입력해서 수동으로 재시작할 수도 있다.

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', 포트)로 서버가 실행될 포트를 설정. process.env 객체에 PORT 속성이 있다면 그 값을 사용하고 없다면 기본적으로 3000번 포트를 이용. 이렇게 app, set(키, 값)을 사용해서 데이터를 저장할 수 있습니다. 나중에 데이터를 app.get(키)로 가져올 수 있다.

app.get(주소, 라우터)는 주소에 대한 GET 요청이 올 때 어떤 동작을 할지 적는 부분. 메개 변수 req는 요청에 관한 정보가 들어 있는 객체. res는 응답에 관한 정보가 들어 있는 객체. 현재 GET / 요청 시 응답으로 Hello, Express를 전송. 익스프레스에서는 res.write나 res.end 대신 res.send를 사용하면 된다.

GET 요청 외에도 POST, PUT, PATCH, DELETE, OPTIONS에 대한 라우터를 위한 app.post, app.put, app.patch, app.delete 메서드가 존재한다. listen을 하는 부분은 http 웹 서버와 동일. 4장에서 서버를 구동했던 것과 동일하게 포트를 연결하고 서버를 실행. 포트는 app.get('port')로 가져왔다.


단순한 문자열 대신 HTML로 응답하고 싶다면 res.sendFile 메서드를 사용하면 된다. 단, 파일의 경로를 path 모듈을 사용해서 지정해야 한다.


6.2 자주 사용하는 미들웨어

미들웨어는 익스프레스의 핵심. 요청과 응답의 중간(미들(middle))에 위치하여 미들웨어라고 부른다. 뒤에 나오는 라우터와 에러 핸들러 또한 미들웨어의 일종이므로 미들웨어가 익스프레스의 전부라고 해도 과언이 아임. 미들웨어는 요청과 응답을 조작하여 기능을 추가하기도, 나쁜 요청을 걸러내기도 한다.

미들웨어는 app.use와 함께 사용된다. app.use(미들웨어)형태

app.use에 매개변수가 req, res, next인 함수를 넣으면 된다. 미들웨어는 위에서부터 아래로 순서대로 실행되며 요청과 응답 사이에 특별한 기능을 수행할 수 있다. 이번에는 next라는 세번째 매개변수를 사용했는데, 다음 미들웨어로 넘어가는 함수. next를 실행하지 않으면 다음 미들웨어가 실행되지 않는다.

주소를 첫 번째 인수로 넣어주지 않으면 미들웨어는 모든 요청에서 실행되고, 주소를 넣는다면 해당하는 요청에서만 실행된다.

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

app.use 나 app.get 같은 루우터에 미들웨어 여러 개 장착할 수 있다. 현재 app.get 라우터에 미들웨어가 두 개 연결되어 있다. 다만 이때도 next를 호출해야 다음 미들웨어로 넘어갈 수 있다.

에러 처리 미들웨어는 매개변수가 err, req, res, next로 네 개. 모든 매개변수를 사용하지 않더라도 매개변수가 반드시 네 개여야 한다. 첫 번째 매개변수 err에는 에러에 관한 정보가 담겨 있다. res.status 메서드로 HTTP 상태 코드를 지정할 수 있다. 기본값은 200(성공). 에러 처리 미들웨어를 직접 연결하지 앟아도 기본적으로 익스프레스가 에러 처리를 하긴 한다. 하지만 실무에서는 직접 에러 처리 미들웨어를 연결해주는 것이 좋다. 에러 처리 미들웨어는 특별한 경우가 아니면 가장 아래 위치하도록!

dotenv 패키지는 .env 파일을 읽어서 process.env로 만든다. dotenv 패키지의 이름이 dot(점) + env 인 이유. process.env.COOKIE_SECRET에 cookiesecret 값이 할당. 키=값 형식으로 추가하면 된다. process.env를 별도 파일로 관리하는 이유는 보안과 설정의 편의성.

  • 비밀 키들을 소스 코드에 그대로 적어두면 소스 코드가 유출되었을 때 키도 같이 유출. 따라서 .env 같은 별도의 파일에 비밀 키를 적어두고 dotenv 패키지로 비밀 키를 로딩하는 방식으로 관리하곤 한다. 소스 코드가 유출되더라도 .env 파일만 잘 관리하면 비밀 키는 지킬 수 있다.

6.2.1 morgan

morgan 연결 후 localhost:3000에 다시 접속하면 기존 로그 외에 추가적인 로그.

morgan 미들웨어는 다음과 같이 사용한다.

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

인수로 dev외에 combined, common, short, tiny 등을 넣을 수 있다. 인수를 바꾸면 로그도 달라진다. dev 모드 기준으로 GET / 500 7.409 ms - 50은 각각 HTTP 메서드, 주소, HTTP 상태코드, 응답속도- 응답 바이트를 의미. 요청과 응답을 한 눈에 볼 수 있어 편리


6.2.2 static

static 미들웨어는 정적인 파일들을 제공하는 라우터 역할을 한다. 기본적으로 제공되기에 따로 설치할 필요 없이 express 객체 안에서 꺼내 장착하면 된다. 다음과 같이 사용.

app.use('요청 경로', express.static('실제 경로'));
app.use('/', express.static(path.join(__dirname,'public')));

함수의 인수로 정적 파일들이 담겨 있는 폴더를 지정하면 된다. 현재 public 폴더가 지정되어 있다. 예를 들어 public/stylesheets/style.css는 http://localhost:3000/stylesheets/style.css로 접근할 수 있다. public 폴더를 만들고 css나 js, 이미지 파일들을 public 폴더에 넣으면 브라우저에 접근 가능.

실제 서버의 경로에는 public이 들어 있지만 요청 주소에는 public이 들어 있지 않다! 서버의 폴더 경로와 요청 경로가 다르므로 외부인이 서버의 구조를 쉽게 파악할 수 없다. 이는 보안에 큰 도움이 된다.

또한, 정적 파일들을 알아서 제공해주므로 4.3절처럼 fs.readFile로 파일을 직접 읽어서 전송할 필요가 없다. 만약 요청 경로에 해당하는 파일이 없으면 내부적으로 next를 직접 호출. 만약 파일을 발견했다면 다음 미들웨어는 실행되지 않는다. 응답으로 파일을 보내고 next를 호출하지 않으니.


6.2.3 body-parser

  • 본문에 있는 데이터를 해석해서 req.body 객체로 만들어주는 미들웨어로 폼 데이터나 AJAX 요청 데이터를 처리
  • 단, 멀티파트(이미지, 동영상, 파일) 데이터는 처리하지 못하며 뒤에 나오는 multer 모듈을 사용
app.use(express.json());
app.use(express.urlencoded({extended: false}));
  • 4.16.0 버전부터 body-parser 미들웨어의 일부 기능이 익스프레스에 내장되어 있어 따로 설치할 필요가 없다.
  • body-parser를 직접 설치하면 Raw, Text 형식의 데이터를 추가 해석할 수 있다. Raw는 요청의 본문 버퍼 데이터, Text는 텍스트 데이터일 때 해석하는 미들웨어.
const bodyParser = require('body-parser');
app.use(bodyParser.raw());
app.use(bodyParser.text());

요청 데이터 종류. JSON은 JSON 형식 데이터 전달 방식이고 URLencoded는 주소 형식으로 데이터를 보내는 방식.

  • urlencoded 메서드를 보면 {extended: false}라는 옵션이 들어 있다.
    ✔️이 옵션이 false이면 노드의 querystring 모듈을 사용해 쿼리스트링을 해석
    ✔️ true 이면 qs 모듈을 이용해서 쿼리스트링을 해석

4.2절에서 POST와 PUT 요청의 본문을 전달받으려면 red.on('data')와 req.on('end')로 스트림을 사용해야 했다. but body-parser를 사용하면 그럴 필요가 없다. 이 패키지가 내부적으로 스트림을 처리해 req.body에 추가.

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

  • cookie-parser는 요청에 동봉된 쿠키를 해석해 req.cookies 객체로 만든다. parseCookie 함수와 기능이 비슷
app.use(cookieParser(비밀키));

해석된 쿠키들은 req.cookies 객체에 들어간다 예를 들어 name = boram 쿠키를 보낸다면 req.cookies는 {name: 'boram'}이 된다. 유효 기간이 지난 쿠키는 알아서 필터링.

첫 번째 인수로 비밀 키를 넣어줄 수 있다. 서명된 쿠키가 있는 경우, 제공한 비밀 키를 통해 해당 쿠키가 내 서버가 만든 쿠키임을 검증할 수 있다. 쿠키는 클라이언트에서 위조하기 쉬우므로 비밀 키를 통해 만들어낸 서명을 쿠키 값 뒤에 붙인다. 서명이 붙으면 쿠키가 name=boram.sign과 같은 모양이 된다. 서명된 쿠키는 req.cookies 대신 req.singedCookies 객체에 들어 있다.

cookie-parser 쿠키를 생성할 때 쓰이는 것은 아님. 쿠키를 생성/제거하기 위해서는 res.cookies, res.clearCookie 매서드를 사용해야 한다. res.cookie(키, 값, 옵션) 형식으로 사용. 옵션은 4.3 절에서의 쿠키 옵션과 동일. domain, expires, httpOnly, maxAge, path, secure 등이 있다.

  • 쿠키를 지우려면, 키와 값 외에 옵션도 정확하게 일치해야 쿠키가 지워진다. 단, expires나 maxAge 옵션은 일치할 필요가 없다.
  • 옵션 중에는 signed라는 옵션이 있는데, 이를 true로 설정하면 쿠키 뒤에 서명. 내 서버가 쿠키를 만들었다는 것을 검증할 수 있으므로 대부분의 경우 서명 옵션을 켜두는 것이 좋다.
  • 서명을 위한 비밀 키는 cookieParser 미들웨어 인수로 넣은 process.env.COOKIE_SECRET이 된다.

6.2.5 express-session


세션 관리용 미들웨어. 로그인 등의 이유로 세션을 구현하거나 특정 사용자를 위한 데이터를 임시적으로 저장해둘 때 매우 유용하다. 세션은 사용자별로 req.session 객체 안에 유지된다.

express-session 1.5 버전 이전에는 내부적으로 cookie-parser를 사용하고 있어서 cookie-parser 미들웨어보다 위에 위치해야 했지만 1.5 버전 이후부터는 사용하지 않게 되어 순서가 상관 없어졌다. 그래도 현재 어떤 버전을 사용하고 있는지 모른다면 cookie-parser 미들웨어 뒤에 놓는 것이 안전하다.

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

  • resave는 요청이 올 때 세션에 수정사항이 생기지 않더라고 세션을 다시 저장할지 설정
  • saveUninitialized는 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정
  • 현재는 둘 다 필요 없으므로 false

express-session은 세션 관리 시 클라이언트에 쿠키를 보낸다. 4.3절에서 배운 세션 쿠키가 이것이다.

  • 안전하게 쿠키를 전송하려면 쿠키에 서명을 추가해야 하고
  • 쿠키를 서명하는데 secret의 값이 필요
  • cookie-parser의 secret과 같게 설정하는 것이 좋다
  • 세션 쿠키의 이름은 name 옵션으로 설정하며 기본 이름은 connect.sid이다

cookie 옵션은 세션 쿠키에 대한 설정. maxAge, domain, path, expires, sameSite, httpOnly, secure 등 일반적인 쿠키 옵션이 모두 제공된다.

  • 현재 httpOnly를 true로 설정해 클라이언트에서 쿠키를 확인하지 못하도록
  • secure는 false로 해서 https가 아닌 환경에서도 사용
  • 배포 시에는 https를 적용하고 secure도 true로 설정하는 것이 좋다

예제 코드에는 나와 있지 않지만, store 옵션도 있다. 현재는 메모리에 세션을 저장하도록! 문제는 서버를 재시작하면 메모리가 초기화되어 세션이 모두 사라진다는 것이다. 따라서 배포 시에는 store에 데이터베이스를 연결해 세션을 유지하는 것이 좋다. 보통 레디스가 자주 쓰인다.

  • express-session으로 만들어진 req.session 객체에 값을 대입하거나 삭제해서 세션을 변경할 수 있다. 나중에 세션을 한 번에 삭제하려면 req.session.destroy 메서드를 호출하면 된다.
  • 현재 세션의 아이디는 req.sessionID로 확인할 수 있다. 세션을 강제로 저장하기 위해 req.session.save 메서드가 존재하지만, 일반적으로 요청이 끝날 때 자동으로 호출되므로 직접 save 메서드를 호출할 일은 거의 없다

실제 로그인은 9.3 절에서 하지만, 세션 쿠키 모양이 조금 독특하니 미리 알아두면 좋다. express-session에서 서명한 쿠키 앞에는 s:이 붙는다. 실제로는 encodeURIComponent 함수가 실행되어 s%3A가 된다. s%3A의 뒷부분이 실제 암호화된 쿠키 내용. 앞에 s%3A가 붙은 경우, 이 쿠키가 express-session 미들웨어에 의해 암호화된 것.


6.2.6 미들웨어의 특성 활용하기

  • 미들웨어는 req, res, next를 매개변수로 가지는 함수(에러 처리 미들웨어만 예외적으로 err, req, res, next를 가진다)로
  • app.use나 app.get, app.post 등으로 장착. 특정한 주소의 요청에만 미들웨어가 실행되게 하려면 첫 번째 인수로 주소를 넣으면 된다.

  • 위와 같이 동시에 여러 개의 미들웨어를 장착할 수도 있고 다음 미들웨어로 넘어가려면 next 함수를 호출해야 한다
  • 위 미들웨어들은 내부적으로 next를 호출하고 있으므로 연달아 쓸 수 있다
  • next를 호출하지 않는 미들웨어는 res.send 나 res.sendFile 등의 메서드로 응답을 보내야 한다
  • express.static과 같은 미들웨어는 정적 파일을 제공하는 경우 express.json, express.urlencode, cookieParser 미들웨어는 실행되지 않는다
  • 미들웨어 장착 순서에 따라 어떤 미들웨어는 실행되지 않을 수도 있다는 것을 기억!
  • 만약 next도 호출하지 않고 응답도 보내지 않으면 클라이언트는 응답을 받지 못해 하염 없이 기다리게 된다

지금까지는 next에 아무런 인수를 넣지 않았지만 next 함수에 인수를 넣을 수 있다. 단, 인수를 넣는다면 특수한 동작을 한다. route라는 문자열을 넣으면 다음 라우터의 미들웨어로 바로 이동하고 그 외의 인수를 넣는다면 바로 에러 처리 미들웨어로 이동. 이때의 인수는 에러 처리 미들웨어의 err 매개변수가 된다. 라우터에서 에러가 발생할 때 에러를 next(err)을 통해 에러 처리 미들웨어로 넘긴다.

미들 웨어 간에 데이터 전달하는 방법도. 세션을 사용한다면 req.session 객체에 데이터를 넣어도 되지만, 세션이 유지되는 동안에 데이터도 계속 유지되는 단점. 만약 요청이 끝날 때까지만 데이터를 유지하고 싶다면 req 객체에 데이터를 넣어두면 된다.

현재 요청이 처리되는 동안 req.data를 통해 미들웨어 간에 데이터를 공유할 수 있다. 새로운 요청이 오면 req.data는 초기화됨. 속성명이 꼭 data일 필요는 없지만 다른 미들웨어와 겹치지 않게 조심해야. 예를 들어 속성명을 body로 한다면 (req.body) body-parser 미들웨어와 긴으이 겹치게 된다.

app.set과의 차이

app.set으로 익스프레스 데이터를 저장할 수 있음을 배웠다. app.get 또는 req.app.get으로 어디서든 데이터를 가져올 수 있음. 그러나 app.set을 사용하지 않고 req 객체에 데이터를 넣어서 다음 미들웨어로 전달하는 이유가 있다. app.set은 익스프레스에서 전역적으로 사용되므로 사용자 개개인의 값을 넣기에는 부적절하며, 앱 전체의 설정을 공유할 때 사용하면 됨. req객체는 요청을 보낸 사용자 개개인에게 귀속 되므로 req 객체를 통해 개인의 데이터를 전달하는 것이 좋다

미들웨어를 사용할 때 유용한 패턴 한 가지를 소개. 미들웨어 안에 미들웨어를 넣는 방식. 다음 예제의 두 방식은 같은 기능을 한다.

이 패턴이 유용한 이유는 기존 미들웨어의 기능을 확장할 수 있기 때문. 예를 들어 다음과 같이 분기 처리를 할 수 있다. 조건문에 따라 다른 미들웨어를 적용하는 코드.

앞으로 살펴볼 예제에서 위와 같은 패턴을 사용하는 경우를 볼 수 있다.

6.2.7 multer

이번에는 사용하는 방법이 다소 어려운 미들웨어

  • 이미지, 동영상 등을 비롯한 여러 가지 파일들을 멀티파트 형식으로 업로드 할 때 사용하는 미들웨어
  • 멀티파트 형식이란 다음과 같이 enctype이 multipart/form-data인 폼을 통해 업로드하는 데이터 형식을 의미
  • 다음과 같은 multipart.html이 있다면 멀티파트 형식으로 데이터를 업로드 할 수 있다.
<form action = "/upload" methode = "post" enctype = "multipart/form-data">
    <input type = "file" name = "image"/>
    <input type = "text" name = "title"/>
    <button type = "submit"> 업로드 </button>
</form>

멀티파트 형식으로 업로드하는 데이터는 개발자 도구 Network 탭에서 다음과 같이 보인다. 이미지 하나를 선택하고 title 인풋이 제목이라고 적어서 업로드 하면 다음과 같이 데이터가 전송.

이러한 폼을 통해 업로드 하는 파일은 body-parser로는 처리할 수 없고 직접 파싱(해석)하기도 어려우므로 multer라는 미들웨어를 따로 사용하면 편리!

0개의 댓글