ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Express.js] Routing(express.Router())
    Back-End/Express.js 2021. 2. 2. 15:37
    반응형

    🤯 서론

    서버를 열기 위한 최소한의 공부를 목표했지만... 역시 쉽지가 않다ㅎㅎㅎㅎㅎ

    Express-generator 로 서버환경을 구현했다보니, app.js 코드들이나 디렉토리들을 하나하나 분석하기도 해야했고

     

    그러다보니, 우리가 요청하는 URI에 따른 서버로직을 구현하기 위해 필요한 Routing과,

    여기에 사용되거나 혹은 생성기에 기본적으로 포함되었던 Middleware의 의미와 간단한 이해를 정리해야겠다고 느꼈다.


    📗 Routing

    [Express 공식문서]

    라우팅은 URI(또는 경로) 및 특정한 HTTP 요청 메소드(GET, POST 등)인 특정 엔드포인트에 대한 클라이언트 요청에 애플리케이션이 응답하는 방법을 결정하는 것을 말합니다.

    각 라우트는 하나 이상의 핸들러 함수를 가질 수 있으며, 이러한 함수는 라우트가 일치할 때 실행됩니다.

     

    위에서 알 수 있듯, Routing은 클라이언트에서 경로와 HTTP 요청을 보내면, 이에 대응하는 라우터에서 Response를 반환한다.

    아래는 Router를 정의하는 기본적인 문법이다.

    app.[method] ('path', (req, res) => {
      /* handler function */
    })

     

    1) app

    express() 객체를 담은 인스턴스이다. 여기엔 HTTP method에 대응하는 함수들을 가지고 있다.

     

    2) method

    HTTP request 폼에는 Method 개념이 존재한다. 클라이언트에서 보내는 요청의 유형으로 이해하면 될 것 같다.

    CRUD에 대응되는 GET(read), PUT(create),POST(update), Delete(delete) 등의 메소드에 대응하는 함수들을 여기에 작성한다.

    • app.get() : HTTP 'GET' 요청에 대응한다. 오직 데이터를 읽는 목적이므로, 특정 리소스를 반환하는 것이 통상이다.
    • app.post() : HTTP 'POST' 요청에 대응한다. 정보를 request body에 첨부하며, 서버 리소스가 이를 참고해 수정될 수 있다.
    • app.put() : HTTP 'PUT' 요청에 대응한다. 현재 URI 표현값을 첨부한 payload로 바꾼다. (리소스 생성)
    • app.delete() : HTTP 'DELETE' 요청에 대응한다. 특정 리소스를 삭제한다. 
    • app.use() : 위 모든 HTTP 요청에 대응할 수 있다. path(경로값)를 시작점으로 관련 모든 경로를 대응한다.
    • app.all() : 위 모든 HTTP 요청에 대응할 수 있다. path(경로값)가 정확히 일치해야 대응한다.
    • app.listen() : 해당 path(경로값)에 접근했을 때 내부의 콜백함수가 실행된다. HTTP 요청이 아닌 URI 접속에도 실행된다.

    이외에도, route(), param() 등등 다양한 메서드들이 존재하나 우선 HTTP 통신에 필요한 메서드만 인지하도록 하자!

     

    3) path 

    서버경로에 해당하는 URI 파라미터 값이다. /users, /products 등 경로뿐만 아니라, /users/:id 형태로 동적 라우팅을 대응할 수 있다.

     

    4) Handler function

    request(클라이언트 요청객체), response(서버 응답객체) 2가지 인자를 받는다. 각각의 처리 메서드를 활용한다.

     

    - Request(요청) 확인 메서드

    • req.params : URI path의 변수값을 조회할 수 있다. /:id가 1이라면, { id: '1' } 객체를 반환한다.
    • req.query : URI path의 쿼리스트링을 조회한다. ?limit=5&skip=10 이라면, { limit: '5', skip='10' } 객체를 반환한다.

    - Response(응답) 처리 메서드

    • res.send() : 다양한 형태의 응답을 보낼 수 있다. (버퍼 데이터, 문자열, HTML 코드, JSON 데이터 등등)
    • res.sendFile() : 파일을 응답으로 보낸다. 파일경로를 통상 첨부한다.
    • res.json() : JSON 데이터를 응답으로 보낸다. 상품들의 정보를 보낼 때 유용할 것 같으며, send()에 비해 직관적이다.
    • res.render([템플릿 파일경로], { 변수 }) : Pug(Jade) 등의 템플릿을 렌더링한다. 파일경로와, 사용될 변수를 입력한다.
    • res.redirect(주소) : 내부의 주소URI로 응답을 보낸다. 
    • res.status(상태코드) : 해당 상태코드의 로직을 추가할 수 있다. (예시: res.status(404).send('Not Found')) 

    HTTP 프로토콜에 따라 하나의 요청엔 하나의 응답(Stateless)으로 대응해야 하며, 중복응답엔 에러가 발생한다.

    (Error: Can't set headers after they are sent.)

     

    * next()

    핸들러 함수에는 next() 라는 메서드가 정의되어 있다. 다수의 핸들러 함수를 지정할 때, 함수의 로직 마지막에 next()를 기입한다.

    app.get('/path', 
      (req, res) => {
        console.log('GET request to the /path');
        next();	// 둘 이상의 핸들러 함수를 사용하기 위함.
      }, 
      (req, res)  => {
        res.send('This is /path page!');
      }
    );

    🤔 URI vs URL?

    프론트와 백은 왔다갔다 하면서 궁금했던 부분이, 바로 이 URI와 URL의 차이다.

    막연하게, URL은 웹 브라우저에서 보이는 주소, URI는 서버에 접근하는 경로라고 생각했는데 이 부분이 상당부분 잘못되었다는 것을 알았다.

     

    - URI(Uniform Resource Identifier) : 통합 자원 식별자

    인터넷에 있는 자원을 나타내는 유일한 주소이다. URI의 존재는 인터넷에서 요구되는 기본 조건으로 인터넷 프로토콜이 동반된다.

    URI의 하위 개념으로, URL과 URN이 있다.

     

    - URL(Uniform Resource Locator) : 파일 식별자, 통합 자원 지시자

    네트워크 상에서 리소스가 어디에 있는지 알려주기 위한 규약이다. 컴퓨너 네트워크와 검색 메커니즘에서의 위치를 지정하는 웹 리소스에 대한 참조이다. 해당 주소에 접속하려면, URL에 맞는 프로토콜로 접근해야 한다.

     

    - URN(Uniform Resource Name) : 통합 자원 이름

    리소스의 위치에 영향을 받지 않는 그 이름이다. 독립적인 값으로, 리소스 위치를 옮기거나 여러 프로토콜로 접근해도 문제없다.

     

     

    위키피디아 개념은 이해하기 힘들지만, 결론은 URI는 리소스에 대한 규약, URL는 리소스 규약에 대한 형태이자 식별자인 것이다.

    위 경우에서, path까지는 URL이지만, query string을 포함한 전체부분은 URI라고 생각된다. (일종의 부분집합)


    📗 express.Router()

    [Express 공식문서]

    express.Router 클래스를 사용하면 모듈식 마운팅 가능한 핸들러를 작성할 수 있습니다. Router 인스턴스는 완전한 미들웨어이자 라우팅 시스템이며, 따라서 “미니 앱(mini-app)”이라고 불리는 경우가 많습니다.

    const express = require('express');
    const app = express();
    
    app.get('/',(req,res,next) => {
      res.send('Hello world!');
    });
    
    app.post('/users',(req,res,next) => {
    	// response...
    });
    
    app.get('/products',(req,res,next) => {
    	// response...
    });
    
    app.get('/products/:id',(req,res,next) => {
    	// response...
    });

    Express에서는 위처럼 Router를 만들 수 있다. 하지만, 프로젝트의 규모가 커지면 위처럼 app.js 의 코드량도 늘어날 것이다.

    이를 위해, Express는 Router() 클래스를 제공하며, 이를 활용해서 Router 기능 및 핸들러들을 각각 모듈화할 수 있다.

     

     

    - Router 모듈화

    위에서 언급했듯, Express는 기본적으로 라우팅이 가능하지만 기능별 코드관리를 위해 Router를 별도로 분리(모듈화)하는 것이다.

    우리는 Express-generator 에서 만들어진 모듈화된 Router들을 가지고 있다. 이를 분석해보도록 하였다.

     

    1) router/index.js

    Router 모듈들을 모아놓은 router 디렉토리가 있으며, 각 모듈은 js파일로 작성한다.

    var express = require('express');
    var router = express.Router();
    
    router.get('/', function(req, res, next) {
      res.render('index', { title: 'AirTnT' });
    });
    
    module.exports = router;
    
    1. express 변수에 Express를 가져오고, router 변수를 Express의 Router() 를 인스턴스화 한다.
    2. router 객체는 app과 같은 문법으로 사용하면 된다.(메서드, 경로, 핸들러 함수)
    3. 해당 모듈을 export 한다. (module.exports 의미: express.Router() 가 반환한 객체(router)를 수정한 최종객체를 모듈로 반환)

    * 모듈이란? jongmin92 님의 github 블로그(jongmin92.github.io/2016/08/25/Node/module-exports_exports/)

     

    2) /app.js

    메인 서버파일에서 모듈들을 import 하고 활용하는 부분이다.

    var indexRouter = require('./routes/index');
    
    app.use('/', indexRouter);
    1. routes 디렉토리 -> index.js를 require 한 뒤, indexRouter 에 저장한다.
    2. app(express())에서 사용할 핸들러로 이 indexRouter 를 적용한다.

     

    - Router 중복경로 모듈화

    같은 URI 경로의 하위경로들에서 라우팅을 하는 경우가 있다. (로그인/회원가입을 sign/login, sign/register...)

    이 때도, 상위경로 단위로 모듈화하면 기능별 바인딩을 더 효율적으로 할 수 있다. (위 예시에선 /routes/sign.js)

    // /routes/sign.js
    
    var express = require('express');
    var router = express.Router();
    
    router.get('/', function (req,res) {
        res.send('This is Sign Server');
    });
    
    router.post('/login', function (req, res) {
       /* Login logic */
    });
    
    router.post('/register', function (req, res) {
       /* Register logic */
    });
    
    module.exports = router;
    
    
    // /app.js
    
    var express = require('express');
    var app = express();
    var signRouter = require('./router/sign.js');
    
    app.get('/', function (req, res) {
        res.send('This is main');
    });
    
    app.use('/sign', signRouter);
    1. sign.js 안에는, 하위경로들을 지정하고 여기의 핸들러 함수는 각각의 로직들을 포함하고 있다.
    2. app.js 에서는, sing.js를 signRouter 변수로 저장하여 app.use() 하면 해당 하위로직들까지 구현이 되는 것이다.

    동적 라우팅, 페이지네이션을 이전에 구현해보면서, 데이터 핸들링을 위해 반드시 필요했던 서버 라우팅을 드디어 깨우쳤다!!! 😃😃

    또한, Express에서 라우팅 기능을 지원하는 Router() 의 사용법에 대해서도 감을 잡게 되었다~

     

    이제, 로그인/회원가입 서버, 에어비앤비 숙소 정보(동적 라우팅, 페이지네이션) 순서로 구현하며 라우팅을 숙달해야겠다.

     

     

    공부하면서 미들웨어라는 단어를 많이 접했다. app이나 router 내의 핸들러 함수를 미들웨어라고도 칭하곤 했다.

    또한, morgan, body-parser 등 생성자에 포함된 모듈들을 third-party middleware 라고 부르기도 했다.

    미들웨어는 프론트-백의 Request, Response 중간에 동작하는 객체들이라고 한다. 다음엔 이 미들웨어를 좀 더 자세히 공부하겠다!

     

     

    [출처]

    - Express 공식 홈페이지 : expressjs.com/ko/guide/routing.html  

    - 김정환 님의 블로그 : jeonghwan-kim.github.io/express-js-2-%EB%9D%BC%EC%9A%B0%ED%8C%85/  

    - jongmin92 님의 블로그(jongmin92.github.io/2016/08/25/Node/module-exports_exports/)

    반응형
Designed by Tistory.