6. 익스프레스

최상민·2023년 5월 19일
0

Node.js 교과서

목록 보기
6/9
post-thumbnail

익스프레스란?

4장에서 웹 서버를 만들어보았는데, 코드가 보기 좋지 않고 확장성도 떨어진다고 느낄 수 있었다.

그래서 npm에 있는 서버를 제작하는 과정에서 겪게 되는 불편을 해소하고 편의 기능을 추가한 웹 서버 프레임워크가 있는데, 이 중 대표적인것이 익스프레스이다.

익스프레스는 기존 메서드들을 계속 사용할 수 있으며, 편리한 메서드들을 추가해 기능을 보완하였고, http 모듈의 요청과 응답 객체에 추가 기능들을 부여했다.

또한, 코드를 분리하기 쉽게 만들어 관리도 용이하며, if문으로 요청 메서드와 주소를 구별하지 않아도 된다.

익스프레스 사용하기

익스프레스를 사용하려면 먼저 5장에서 배웠던 npm init으로 package.json을 생성해주고 npm install express(npm i express)로 익스프레스를 install 해야한다.

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를 install 한 뒤 app.js에 express 모듈을 이용하여 서버 코드를 작성한 것이다.

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

  • app.set('port', 포트) - 서버가 실행될 포트를 설정함.
  • app.get(주소, 라우터) - 주소에 대한 GET 요청이 올 때 어떤 동작을 할지 적는 부분. 익스프레스에서는 res.write나 res.end 대신 res.send를 사용함.

GET 요청 이외에도 POST, PUT, PATCH, DELETE, OPTIONS에 대한 라우터를 위한 app.post, app.put, app.patch, app.delete, app.options 메서드가 존재한다.

listen을 하는 부분은 http 웹 서버와 동일하다.

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

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

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

app.get('/', (req, res) => {
	res.sendFile(path.join(__dirname, '/index.html'));
});

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

미들웨어

뒤에 나오는 라우터와 에러 핸들러 또한 미들웨어의 일종이므로 미들웨어가 익스프레스의 전부라고 해도 과언이 아니다.

그렇다면 미들웨어가 뭘까?

미들웨어는 요청과 응답의 중간(middle)에 위치하기 때문에 미들웨어(middleware)라고 부르는 것이다.

미들웨어는 요청과 응답을 조작해 기능을 추가하기도 하고, 나쁜 요청을 걸러내기도 한다.

미들웨어는 app.use와 함께 사용되며, app.use(미들웨어) 형태이다.

...
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.use나 app.get 같은 라우터에 미들웨어를 여러개 장착할 수 있다. 위 코드의 app.get 라우터에 두 개의 미들웨어가 연결되어 있다. 다만, next를 호출해야 다음 미들웨어로 넘어갈 수 있다.

에러 처리 미들웨어는 매개변수가 err, req, res, next로 네 개이다. 모든 매개변수를 사용하지 않더라도 매개변수가 반드시 네 개여야 한다. 첫 번째 매개변수 err에는 에러에 관한 정보가 담겨있다. 에러 처리 미들웨어는 특별한 경우가 아니면 가장 아래에 위치하는게 좋다.

미들웨어를 통해 요청과 응답에 다양한 기능을 추가할 수 있고, 이미 만은 사람이 유용한 기능들을 패키지로 만들어두었다. 실무에서 자주 사용하는 패키지들을 알아보고 설치해보자.

npm i 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({ extend: 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-cokkie',
}));

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

위 코드는 서버 코드에서 설치한 패키지들을 추가한 코드이다.

새로 추가된 코드를 보면 req, res, next와 같은 것들이 보이지 않는데, 미들웨어 내부에 들어 있으며, next도 내부적으로 호출하기에 다음 미들웨어로 넘어갈 수 있다.

dotenv 패키지는 .env 파일을 읽어서 process.env로 만든다. process.env를 별도의 파일로 관리하는 이유는 보안과 설정의 편의성 때문이다.

morgan

위 서버 코드를 실행 후 localhost:3000으로 접속하면 콘솔에 위 사진과 같이 출력된다.

위 사진에서 GET / 500 7.730 ms - 44 로그는 morgan 미들웨어에서 나오는 것이며, 각각 [HTTP 메서드][주소] [HTTP 상태코드][응답 속도] - [응답 바이트]를 의미하고, morgan 미들웨어는 요청과 응답에 대한 정보를 콘솔에 기록한다.

morgan 미들웨어는 app.use(morgan('dev')); 와 같이 사용하며, 인수로 dev 이외에 combined, common, short, tiny 등을 넣을 수 있다. 인수를 바꾸게 되면 로그도 달라지고, 개발환경에선 dev를, 배포 환경에선 combined를 애용하는 편이다.

static

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

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

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

위 코드와 같이 사용하며, 함수의 인수로 정적 파일들이 담겨 있는 폴더를 지정하면 된다.

예를 들어 public/stylesheet/style.css는 http://localhost:3000/stylesheets/style.css로 접근할 수 있다.

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

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

body-parser

요청의 본문에 있는 데이터를 해석해서 req.body 객체로 만들어주는 미들웨어이다. 보통 폼 데이터나 AJAX 요청의 데이터를 처리하지만, 멀티파트 데이터는 처리하지 못한다.

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

위 코드와 같이 사용하며, 익스프레스 4.16.0 버전부터 별도의 설치 없이 사용할 수 있다.

JSON은 JSON 형식의 데이터 전달 방식이고, URL-encoded는 주소 형식으로 데이터를 보내는 방식이다.

폼 전송은 URL-encoded 방식을 주로 사용한다. urlencoded 메서드의 { extended: false }에서 옵션이 false이면 노드의 querystring 모듈을 사용해 쿼리스트링을 해석하고, true이면 qs 모듈을 사용해 쿼리스트링을 해석한다.

이 패키지는 내부적으로 스트림을 처리해 req.body에 추가하며, JSON 형식으로 { name: 'zerocho', book: 'nodejs' }를 본문에 보내면 req.body에 그대로 들어가며, URL-encodede 형식으로 name=zerocho&book=nodejs를 본문으로 보내면 req.body에 { name: 'zerocho', book: 'nodejs' }가 들어간다.

cookie-parser는 4장에서 배웠던 요청에 동봉된 쿠키를 req.cookies 객체로 만든다.

app.use(cookieParser(비밀키));

위 코드와 같이 사용하며, 해석된 쿠키들은 req.cookies 객체에 들어간다.

예를 들어 name=zerocho 쿠키를 보냈다면 req.cookies는 { name: 'zerocho' }가 된다.
유효기간이 지난 쿠키는 알아서 걸러진다.

쿠키 생성/제거

쿠키를 생성/제거할 때는 res.cookie, res.clearCookie 메서드를 사용해야 한다.

res.cookie(키, 값, 옵션) 형식으로 사용하며, 4장에서 배운 쿠키 옵션과 동일하며 domain, expires, httpOnly, maxAge, path, secure등이 있다.

res.cookie('name', 'zerocho', {
	expires: new Date(Date.now() + 900000),
    httpOnly: true,
    secure: true,
});
res.clearCookie('name', 'zerocho', { httpOnly: true, secure: true });

쿠키를 지우려면, 키와 값 외의 옵션도 정확히 일치해야 쿠키가 지워진다.

옵션 중 signed라는 옵션이 있는데, 이를 true로 설정하면 쿠키 뒤에 서명이 붙고, 내 서버가 쿠키를 만들었다는 것을 검증할 수 있으므로 대부분의 경우 서명 옵션을 켜두는 것이 좋다.

서명을 위한 비밀 키는 cookieParser 미들웨어에 인수로 넣은 process.env.COOKIE_SECRET이 된다.

express-session

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

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

express-session의 어떤 버전을 사용하고 있는지 모른다면 cookie-parser 미들웨어 뒤에 놓고 사용하도록 하자. 버전에 따라 cookie-parser 보다 뒤에 위치해야할 때도 있고, 상관없을 때도 있기 때문이다.

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

  • resave: 요청이 올 때 세션에 수정사항이 생기지 않더라도 세션을 다시 저장할지 설정하는 것
  • saveUninitialized: 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정하는 것
  • secret: 쿠키를 안전하게 전송하려면 쿠키에 서명을 추가해야하고, 쿠키를 서명하는데 필요한 값, cookie-parser의 secret과 같게 설정하는 것이 좋음
  • cookie: 세션 쿠키에 대한 설정. 일반적인 쿠키 옵션이 모두 제공된다.
  • httpOnly: true로 설정해 클라이언트에서 쿠키를 확인하지 못하도록 함.
  • secure: false로 설정해 https가 아닌 환경에서도 사용할 수 있게 함. 배포 시에는 https를 적용하고 secure도 true로 설정하는 것이 좋다.

Router 객체로 라우팅 분리

4장에서 라우터를 만들 때는 요청 메서드와 주소별로 분기를 처리하느라 코드가 매우 복잡했고, if문으로 분기하여서 보기도 좋지않고 확장하기도 어려웠다. 라우팅 분리는 익스프레스의 장점 중 하나이기도 하다.

//routes/index.js

const express = require('express');

const router = express.Router();

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

module.exports = router;
//routes/user.js

const express = require('express');

const router = express.Router();

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

module.exports = router;
//app.js

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

dotenv.config();
const indexRouter = require('./routes');
const userRouter = require('./routes/user');
...

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

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

routes 폴더에 index.js와 user.js를 app.js에 연결하고 404 처리 미들웨어를 추가하였다.

이렇게 index는 indexRouter로 '/'에, user는 userRouter로 '/user에 연결시켜서 분리시켰으며 indexRouter는 use의 '/'와 get의 '/'가 합쳐져 GET / 라우터가 되었고, userRouter는 use의 '/user'와 get의 '/'가 합쳐져 GET /user 라우터가 되었다.

이제 서버를 실행한 뒤 localhost:3000과 localhost:3000/user로 접속하면 각각에 해당하는 응답을 받을 수 있다.

next('route')를 사용하여 다음 라우터로 넘어갈 수 있다. 라우터에 연결된 나머지 미들웨어들을 건너뛰고 싶을 때 사용한다.

router.get('/', (req, res, next) => {
	next('route');
}, (req, res, next) => {
	console.log('실행되지 않음);
    next();
}, (req, res, next) => {
	console.log('실행되지 않음);
    next();
});
router.get('/', (req, res) => {
	console.log('실행 된다.');
    res.send('Hello, Express');
});

위 코드와 같은 주소의 라우터를 여러 개 만들어도 next()를 호출하면 다음 미들웨어가 실행되며, 첫 번째 라우터의 첫 번째 미들웨어에서 next() 대신 next('route')를 호출해서 주소와 일치하는 다음 라우터로 넘어가게 된다.

router.route('/abc')
	.get((req, res) => {
    	res.send('GET /abc');
    })
    .post((req, res) => {
    	res.send('POST /abc');
    });

위 코드와 같이 주소는 같지만 메서드는 다른 코드를 묶어서 보기 좋게 사용할 수 있다.

req, res 객체

익스프레스의 req, res 객체는 http 모듈의 req, res 객체를 확장한 것이다.

자주 쓰이는 것들을 알아보자.

req 객체

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

res 객체

  • res.app: req.app처럼 res 객체를 통해 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 상태 코드를 지정한다.

메서드 체이닝

res
  .status(201)
  .cookie('test', 'test')
  .redirect('/admin');

위 코드와 같이 메서드 체이닝을 통해 코드양을 줄일 수 있다.


익스프레스는 유튜브와 구글링을 통해 많은 코드들과 강의를 참고하며 배우고 사용하였다. 대부분 익스프레스로 서버 실행하는 법, 라우팅 등 간단한 내용들이였지만, 이번 책을 통해 미들웨어, 패키지 등 많은 것을 배워가는 파트였다. 특히 미들웨어 실행 순서 등은 꼭 기억해야하는 부분인거 같다..

profile
기록으로 복습하자

0개의 댓글

관련 채용 정보