LeChuck

express로 서버 구축하기

·8 min to read

앞서 Node.js에서 제공하는 http 모듈을 이용해서 서버를 구축해보았다. npm에는 서버 구축을 편리하게 해주는 koa, hapi, express 같은 패키지들이 있다.

express는 기존 http 모듈의 기능을 유지하면서도 편리한 확장 기능들을 추가했다. 코드를 모듈화하여 관리하기 쉬운 구조를 취하며, if문으로 요청 메서드와 주소를 구별하는 기존의 방식보다 더 직관적이고 깔끔한 방법을 제공한다.

express 기본

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

  2. 서버가 실행될 포트를 설정한다. app.set()에는 포트 외에도 원하는 값이면 어떤 것이든 name,value 꼴로 설정할 수 있다.

  3. app.get(path, callback)는 HTTP GET 요청을 처리하는 메서드다. GET 요청이 들어왔을 때 req와 res를 통해 어떤 동작을 취할지 정의한다. express에서는 http 모듈에서 제공하는 .write()이나 .end() 대신 .send(body)를 이용하여 response한다.

http 모듈을 사용할 땐 HTML 파일을 불러오기 위해서 fs(file System) 모듈을 사용했지만, express에는 sendFile 메소드가 있다.

미들웨어

미들웨어는 요청(request)과 응답(response)의 중간에 위치하여 미들웨어라 불린다. 미들웨어는 express의 핵심이다. 미들웨어는 요청과 응답을 조작하여 기능을 추가하기도 하고, 나쁜 요청을 걸러내기도 한다.

미들웨어는 req, res, next를 매개변수로 갖는 함수이며 app.use(), app.get()등에 장착된다.

미들웨어 함수는 아래와 같은 태스크를 수행할 수 있다.

  • 모든 코드를 실행
  • 요청 및 응답 오브젝트(req, res)에 대한 변경을 실행
  • 요청-응답 주기를 종료
  • 스택 내의 그 다음 미들웨어를 호출

현재 미들웨어가 요청-응답 주기를 종료하지 않은 경우에는 .next()를 호출해서 그 다음 미들웨어에 제어를 넘겨야 한다. 그렇지 않으면 해당 요청은 응답받지 못한채 방치된다. 따라서 미들웨어의 로드 순서는 중요하다.

  1. app.use(path, callback)는 미들웨어를 mount하는 함수다. 첫 번째 인자인 path를 생략하면 '/' path를 default로 갖게되어서, '/'로 시작하는 모든 요청에 callback 미들웨어가 대응된다. .next()를 호출해야 다음 미들웨어가 실행된다.

.next()에 'route'라는 문자열을 인수로 전달하면 다음 라우터의 미들웨어로 이동하고, 그 외의 인수를 넣으면 에러 처리 미들웨어로 이동하게 된다. 그리고 에러 처리로 전달된 인수는 에러 처리 미들웨어의 err 매개변수가 된다.

  • next() -> 다음 미들웨어로

  • next('route') -> 다음 라우터로

  • next(error) -> 에러 핸들러로

  1. app.use()app.get() 같은 라우터에 다수의 미들웨어를 장착할 수 있다. get 라우터에 두 개의 미들웨어가 장착된 모습이다.

  2. 에러 처리 미들웨어로 4개의 매개변수를 갖고 있는데, 이는 생략할 수 없다. res.status() 메서드로 에러 코드를 지정한 뒤, res.send()를 통해 error message를 response하는 모습이다.

위 코드는 '모든 요청 -> GET / 요청 -> throw 에러 -> 에러 처리' 순으로(절차대로) 실행된다.

라우터, 에러 핸들러 -> 미들웨어

미들웨어 실용편

npm i morgan cookie-parser express-session

아래 미들웨어들은 내부적으로 req, res에 대한 처리와 .next()의 호출을 수행하고 있다.

  1. morgan - 요청과 응답에 대한 추가 log 정보를 콘솔에 제공. GET / 404 13.959 ms - 139와 같은 형식으로 출력되는데, 이는 (HTTP 메서드) (주소) (HTTP 상태 코드) (응답 속도) - (응답 바이트)를 의미한다. 'dev'외에 combined, common, short 등의 인수를 전달할 수 있다.

  2. static 미들웨어는 정적인 파일들을 제공하는 라우터 역할을 한다. express에서 기본으로 제공한다.

  3. body-parser는 요청의 본문에 있는 데이터를 해석해서 req.body 객체로 만들어주는 미들웨어다. 보통 <form> 데이터나 AJAX 요청의 데이터를 처리한다. 단, 멀티파트(이미지, 동영상) 데이터는 처리하지 못한다. 멀티파트 데이터 처리는 multer를 이용해야 한다.

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

  1. cookie-parser는 요청에 동봉된 쿠키를 해석해 req.cookies 객체로 만든다. 문자열 형식의 name=zerocho 쿠키를 { name: 'zerocho' } 와 같이 객체로 만들어주는 것. 만약 유효기간이 지난 쿠키라면 알아서 걸러낸다. cookieParser()의 첫 번째 인수로 비밀 키를 전달하고 있는 모습이다.

  2. express-session은 로그인 등의 이유로 세션을 구현할 때 유용하다. express-session은 인수로 세션에 대한 설정을 받는다. express-session은 세션 관리 시 클라이언트에 세션 쿠키를 보낸다. 안전하게 쿠키를 전송하고자 하면 secret 옵션을 추가하여 쿠키에 서명하는 것이 좋다. 서명하면 클라이언트 dev tool에서 쿠키 확인 시 암호화되어 알아볼 수 없는 문자열로 나타난다.

위와 같이 app.use()에 여러 개의 미들웨어를 한 번에 장착할 수도 있다. 위 미들웨어들은 내부적으로 .next를 호출하고 있으므로 알아서 다음 미들웨어로 넘어가진다.

미들웨어 간에는 세션 혹은 req 객체를 이용해서 데이터를 전달할 수 있다. 세션을 이용하면 req.session 객체를 이용해서 참조할 수 있고, 세션이 유지되는 동안에만 데이터가 유지된다. 만약 req 객체를 이용한 전달이라면, 요청이 끝날 때까지만 데이터가 유지되는 형식이다.

앞서 app.set()을 이용해서 데이터를 저장할 수 있다고 하였는데, 위 방식과의 차이점은 app.set()은 express 전역에서 사용되므로 앱 전체의 설정을 공유할 때 사용하기 용이하고, req 객체를 이용한 방식은 요청을 보낸 사용자 개개인에게 귀속되므로 개인의 데이터를 전달할 때 용이하다는 점이다.

multer 미들웨어

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

멀티파트 형식이란 MIME 타입의 일종으로, enctype이 multipart/form-data인 폼을 통해 업로드하는 데이터 형식을 의미한다.

MIME(Multipurpose Internet Mail Extensions) 타입은 인터넷에서 사용되는 파일 포맷을 정의한 것이다. 미디어 타입으로도 불린다. 웹에서 파일의 확장자는 별 의미가 없다. 확장자 대신 MIME 타입을 올바르게 정의하는 것이 중요하다. 브라우저들은 리소스를 내려받았을 때 어떤 동작을 취할 지 결정하기 위해 해당 리소스의 MIME 타입을 참고한다.

MIME 타입은 type/substype 형태를 취한다. / 구분자를 사이에 두고 타입과 서브타입으로 구성되는 형식이다. type 부분에는 개별타입 혹은 멀티파트 타입이 자리할 수 있다. MDN - MIME 타입

위와 같은 multipart.html 파일이 있다면 multer를 이용해 멀티파트 형식으로 데이터를 업로드할 수 있다. multipart/form-data는 브라우저에서 서버로 HTML <form>의 내용을 POST 전송 시 사용된다.

multer 설정하기

multer 미들웨어는 위와 같이 설정할 수 있다. 1) storage 속성에다 어디에(destination) 어떤 이름으로(filename) 저장할지를 지정했다.

req와 file에는 각각 요청에 대한 정보와 업로드한 파일에 대한 정보가 담겨있고, done 매개변수는 함수다. done의 첫 번째 인자로 (에러가 있는 경우) 에러를 넣고 두 번째 인자로는 실제 경로나 파일 이름을 넣어준다. req나 file의 데이터를 가공해서 done으로 넘기는 형식이다.

storage 속성을 종합해보면 uploads라는 폴더에 [파일명 + 현재시간.확장자]라는 이름으로 파일을 업로드하겠다는 의미다.

  1. limits 속성에는 업로드에 대한 제한 사항을 설정하고 있다. 파일 크기를 5MB로 제한한 모습이다.

  2. multer 설정을 통해서 획득한 upload 변수에는 다양한 종류의 미들웨어가 있다. 파일을 하나만 업로드하는 경우 single 미들웨어를, 여러개를 업로드할 경우 array 미들웨어를 사용한다.

위 multer가 제대로 적용되려면 서버에 uploads 폴더가 존재해야 한다.

multer로 파일 업로드하기

파일 하나 업로드하기 - single

single 미들웨어는 위와 같이 사용한다. single의 인자로 html input 태그의 name 속성인 image를 전달했다. single 미들웨어를 라우터 미들웨어 코드 위쪽에 배치해두면 파일 업로드 후 req.file 객체가 생성된다. 업로드 성공 시 결과는 req.file 객체 안에 들어있다. req.body에는 파일이 아닌 데이터인 title이 들어 있다.

파일 여러개 업로드하기 - array

여러 파일을 업로드하기 위해선 HTML input 태그에 multiple 속성을 부여하고, single 미들웨어 대신 array 미들웨어를 사용한다.

다수의 input 태그로부터 여러개 파일 업로드하기 - fields

req.file 객체가 req.files 객체로 바뀐 것을 유념하라.

Router 객체로 라우팅 분리하기

http 모듈이 아닌 express를 사용하는 이유 중 하나가 바로 라우팅을 깔끔하게 관리할 수 있다는 점이다. 더 이상 if문으로 분기하여 일일이 url을 확인할 필요가 없다. app.get() 같은 메서드로 라우팅하면 된다.

위와 같이 두 개의 라우터 모듈(파일)을 만든 뒤, app.js에서 app.use()를 통해 불러와 사용할 수 있다.

앞서 next()에 'route'를 인자로 전달하면 다음 미들웨어가 아닌 다음 라우터로 건너뛴다고 했다. 아래 코드를 참조하자.

또한, 주소는 같지만 HTTP 요청이 다른 다수의 라우팅을 할 때 아래와 같이 express.route()를 활용할 수 있다.

References

(공식)Express 앱에서 사용하기 위한 미들웨어 작성

(공식) API reference

HTTP multipart/form-data 이해하기

<Node.js 교과서>(2020, 조현영)