하나의 파일에서 Express의 모든 라우트 경로에 대한 미들웨어를 처리할 수 있지만, 서비스가 커질수록 하나의 파일에서 모든 라우팅 로직을 처리하는 건 유지보수를 더 어렵게 만드는 원인이 된다.
이런 문제 때문에, 어느 정도 규모 있는 서비스를 만들 땐 보통 라우팅 경로에 따라 모듈을 쪼개서 관리하는 것이 일반적이다.
이때 express.Router 메서드가 사용된다. express.Ruouter는 router 객체를 반환하며, router 객체가 제공하는 getpostputpatchdelete 등 메서드를 통해 라우팅 로직을 처리할 수 있다.
보통은 routes와 같은 이름으로 디렉토리를 만든 후 라우터들을 목적에 따라 구분하고, 각 모듈에서 정의한 라우팅 로직을 module.exports = router 방식으로 정의해둔 라우터 객체를 외부에 노출한다. 그리고, 실제 앱 부분에서 노출된 라우터를 require로 불러와 해당 라우터를 app.use로 연결해준다.
app.use('/shelters', shelterRouter) 방식으로 사용하면 shelterRouter로 정의한 모든 라우팅 경로 앞에 prefix로 /shelter가 붙는다. shelterRouter에 router.get('/:id', (req, res) => {})로 정의한 라우터가 있다면 실제로는 /shelter/:id 경로일 때 라우팅 로직이 실행되는 것이다.
라우터를 모듈로 쪼개둔다면, 특정 진입점이 있는 라우터에서만 적용하고 싶은 미들웨어도 쉽게 쪼개서 관리할 수 있다. 예를 들어 /admin 경로 뒤에 오는 모든 라우터들에서 권한이 인증된 유저인지 확인하는 미들웨어를 먼저 실행하고 처리하고 싶다면 routes/admin.js 모듈에서 정의한 router 객체에 router.use(isAdmin)처럼 미리 정의한 로직을 미들웨어로 전달해 주고, 미들웨어에서 조건에 맞는 경우만 next()를 호출해 주고, 그렇지 않을 경우 next(err)로 바로 에러 핸들링을 처리해 줄 수 있다.
라우터를 모듈로 잘 쪼개면 유지보수가 수월해주고, 프로젝트의 비즈니스 로직을 한눈에 파악하기 쉽도록 관리할 수 있다. 결국, 어디에서 어떤 라우터 모듈로 쪼개서 어떻게 관리할지는 개발자의 판단의 영역이다. 해당 부분에서 좋은 판단을 하기 위해선 역시 다양한 경험이 중요하겠다.
쿠키에 대해서도 공부했다. http 통신은 무상태성(Stateless)이라는 특징을 갖는다. 풀어보면, 기본적으로 http 통신에선 어떠한 데이터가 저장되지 않는다는 뜻이다. 각 요청과 응답은 독립적이며, 이전 요청과 다음 요청 사이엔 어떠한 상태가 공유되지 않는다.
하지만, 경우에 따라선 http 통신을 위한 '기억된 상태'가 필요한 경우가 있다. 이런 경우에 사용되는 게 '쿠키'다.
쿠키는 http request에 사용할 어떠한 상태(데이터, 정보)를 저장해 두는 저장소이다. 주로 브라우저에 저장되어 있다가, 클라이언트에서 서버로 요청이 갈 때 브라우저에 저장된 쿠키가 같이 서버로 전달된다.
Express는 미들웨어의 res 객체에 res.cookie라는 메서드가 있다. 해당 메서드는 두 개의 매개변수를 받으며, 첫 번째 매개 변수는 쿠키에 저장될 값의 키, 두 번째 매개 변수는 저장 될 값을 나타낸다(쿠키는 객체 형태로 데이터를 관리해 준다).
서버에서 정의해 준 쿠키는 해당 라우팅 경로로 접속 시 브라우저에 저장된다. 그 상태에서 브라우저에서 서버로 요청이 전달되면 req.cookies에 브라우저에서 관리되던 쿠키가 전달된다.
쿠키를 사용하면 다양한 처리를 해줄 수 있다. 예를 들어서, 어떤 사람이 서비스를 이용할 때 다크 모드를 선호하는지 라이트 모드를 선호하는지에 대한 정보를 쿠키로 관리한 후, 서버에서 쿠키의 mode 정보를 활용해 필요한 값들을 내려주도록 할 수 있다. 흔히 사용되는 로그인 인증-인가 시 '이 요청이 인증된 사람으로부터 온 요청이다'라는 걸 식별하는 과정에서도 쿠기를 활용할 수 있다.
서버에서 쿠키에 서명 처리를 할 수 있다. app.use(cookieParser('secret'))와 같이, cookieParser를 설정할 때 특정 값을 전달해 주면 해당 값으로 쿠키의 값이 서명 처리된다(서명을 위한 키도 보안이 필요하기 때문에, 실제 코드에 하드 코딩해서 넣진 않는다. 주로 .env에 값을 저장해 둔 다음 불러와서 넣어준다).
'서명'은 '보안'의 개념은 아니다. 서명을 하더라도 클라이언트에 쿠키의 값이 내려갔을 때 원본 값을 확인할 수는 있다.
대신, 서명을 하면 서버에서 내려준 쿠키가 임의로 변경됐는지, 다른 값으로 들어오진 않았는지를 파악할 수 있다. 만약에 클라이언트에서 별도로 쿠키의 값을 조정해서 서버로 보냈으면 이는 의도하지 않은 동작일 수 있다. 이런 경우에 서명을 통해 쿠키의 값 변경 여부를 파악한 다음 서버에서 내려준 쿠키와 일치할 때에만 필요한 로직을 처리하도록 해줄 수 있다.
쿠키는 클라이언트에서 저장되고 관리되기 때문에, 서버에서 특정 브라우저의 접근에 대한 데이터를 누적해서 관리하기 어렵다. 이러한 니즈가 있을 경우 세션을 사용할 수 있다.
세션은 서버에서 관리된다. 보통은 세션을 위한 별도의 데이터베이스를 두고, 해당 DB에 무상태성인 http 통신 과정에서 저장하고 관리해야 하는 정보를 추출해 저장해 두는 식으로 관리한다.
데브 환경에서는 별도의 DB를 두지 않고, PC의 메모리에 세션 데이터를 저장하고 관리할 수 있다. 단, 해당 경우엔 브라우저를 새로고침 해주면 기존 세션 정보가 날아간다는 단점이 있다.
세션을 사용하면, http 통신 과정에서 쿠키에 세션 아이디(sid)가 클라이언트로 전달된다. 그리고, 해당 클라이언트에서 어떠한 요청이 전달되면 쿠키에 있는 세션 아이디를 key로 해서 세션 DB에 저장된 필요한 정보를 탐색하고 활용할 수 있다.
쿠키로 클라이언트와 통신을 할 땐 많은 숫자의 데이터를 주고받기에 부담이 될 수 있다. 따라서, 세션을 활용해 데이터를 저장하고, 클라이언트와의 통신 과정에선 쿠키에 비교적 크기가 작은 세션 아이디 정보만 담아 주고받는 게 훨씬 더 경제적이다.
뿐만 아니라, 쿠키에 많은 정보를 담아 통신에 사용하면 통신 내용이 탈취됐을 경우 중요한 정보가 외부에 노출될 우려가 생기고, 보안 상 취약점이 발생할 우려가 있다. 세션은 서버에서 데이터를 저장하고, 필요한 것들만 추출해서 로직 처리 후 내려줄 수 있기 때문에, 조금 더 안정적인 활용이 가능하다.