라우팅

Seokjun Moon·2023년 3월 2일
0

Express

목록 보기
2/2

2023.02.28

MySQL 서버에 데이터를 추가하는 부분을 추가하면서 라우팅에 대해 이해하지 못하여 undefined 오류 등 간단한 내용들로 많은 시간들을 잡아먹었습니다. 그래서 express의 작동 원리, 구문 구조, 사용하는 이유 등을 더 파악하고자 공부 시작 !





기본 라우팅

  • 라우팅은 URI(또는 경로) 및 특정한 HTTP 요청 메소드(GET, POST 등)인 특정 엔드포인트에 대한 클라이언트 요청에 애플리케이션이 응답하는 방법을 결정하는 것을 말합니다.
  • 각 라우트는 하나 이상의 핸들러 함수를 가질 수 있으며, 이러한 함수는 라우트가 일치할 때 실행됩니다.
  • 라우트 정의에는 다음과 같은 구조가 필요합니다.
app.METHOD(PATH, HANDLER)

여기서,

  • app : express의 인스턴스
  • METHOD : HTTP 요청 메소드
  • PATH : 서버에서의 경로
  • HANDLER : 라우트가 일치할 때 실행되는 함수

입니다. 다음 코드에서는 간단한 라우트의 정의를 설명합니다.

  • 홈페이지에서 Hello World!로 응답:

    app.get('/', function (req, res) {
        res.send('Hello World!');
    });
  • 애플리케이션의 홈 페이지인 루트 라우트(/)에서 POST 요청에 응답:

    app.post('/', function (req, res) {
        res.send('Got a POST request');
    });
  • /user 라우트에 대한 PUT 요청에 응답:

    app.put('/user', function (req, res) {
        res.send('Got a PUT request at /user');
    });
  • /user 라우트에 대한 DELETE 요청에 응답:

    app.delete('/user', function (req, res) {
        res.send('Got a DELETE request at /user');
    });



HTTP 요청 메서드

HTTP는 요청 메서드를 정의하여, 주어진 리소스에 수행하길 원하는 행동을 나타냅니다. 간혹 요청 메서드를 "HTTP 동사"라고 부르기도 합니다. 각각의 메서드는 서로 다른 의미를 구현하지만, 일부 기능은 메서드 집합 간에 서로 공유하기도 합니다. 예를 들어, 응답 메서드는 안전하거나, 캐시 가능하거나, 멱등성을 가질 수 있습니다.

GET

  • 특정 리소스의 표시를 요청합니다. GET을 사용하는 요청은 오직 데이터를 받기만 합니다.
  • GET 메서드의 요청과 동일한 응답을 요구하지만, 응답 본문을 포함하지 않습니다.

POST

  • 특정 리소스에 엔티티를 제출할 때 쓰입니다. 이는 종종 서버의 상태의 변화나 부작용을 일으킵니다.

PUT

  • 목적 리소스의 모든 현재 표시를 요청 payload로 바꿉니다.

DELETE

  • 특정 리소스를 삭제합니다.

CONNECT

  • 목적 리소스로 식별되는 서버로의 터널을 맺습니다.

OPTIONS

  • 목적 리소스의 통신을 설정하는 데 쓰입니다.

TRACE

  • 목적 리소스의 경로를 따라 메시지 loop-back 테스트를 합니다.

PATCH

  • 리소스의 부분만을 수정하는 데 쓰입니다.



라우팅

라우팅은 애플리케이션 엔드 포인트(URI)의 정의, 그리고 URI가 클라이언트 요청에 응답하는 방식을 말합니다. 다음 코드는 매우 기본적인 라우트의 예시입니다.

var express = require('express');
var app = express();

app.get('/', function(req, res) {
  res.send('hello world');
});

라우트 메소드

라우트 메소드는 HTTP 메소드 중 하나로부터 파생되며(생성된다는 말), express 클래스의 인스턴스에 연결됩니다. 다음 코드는 앱의 루트에 대한 GET 및 POST 메소드에 대해 정의된 라우트의 예시입니다.

// GET method route
app.get('/', function (req, res) {
  res.send('GET request to the homepage');
});

// POST method route
app.post('/', function (req, res) {
  res.send('POST request to the homepage');
});

Express는 HTTP 메소드에 해당하는 다음과 같은 라우팅 메소드를 지원합니다. get, post, put, head, delete, options, trace, copy, lock, mkcol, move, purge, propfind, proppatch, unlock, report, mkactivity, checkout, merge, m-search, notify, subscribe, unsubscribe, patch, search 및 connect.

특수한 라우팅 메소드인 app.all()은 어떠한 HTTP 메소드로부터도 파생되지 않습니다.(무슨말??) 이 메소드는 모든 요청 메소드에 대해 한 경로에서 미들웨어 함수를 로드하는 데 사용됩니다. (즉, 해당 주소로 요청이 들어오면 처리한다는 말. 해당 경로로 오는 모든 요청을 혼자 담당한다는 것)

다음 예시에서는, GET, POST, PUT 또는 DELETE 메소드를 사용하는 경우, 또는 http 모듈에서 지원되는 기타 모든 HTTP 요청 메소드를 사용하는 경우 등의 "/secret"에 대한 요청을 위하여 핸들러가 실행됩니다. (그러니까 /secret 으로 들어온 모든 요청은 아래 핸들러가 다룬다는 말)

app.all('/secret', function (req, res, next) {
  console.log('Accessing the secret section ...');
  next(); // pass control to the next handler
});

라우트 경로

라우트 경로는, 요청 메소드의 조합을 통해 요청이 이루어질 수 있는 엔드포인트를 정의합니다. 라우트 경로는 문자열, 문자열 패턴 또는 정규식이 가능합니다. 자세한건 홈페이지 참고


라우트 핸들러

미들웨어와 비슷하게 작동하는 여러 콜백 함수를 제공하여 요청을 처리할 수 있다. 유일한 차이점은 이러한 콜백은 next('route')를 호출하여 나머지 라우트 콜백을 후회할 수도 있다는 점입니다. 이러한 메커니즘을 이용하면 라우트에 대한 사전 조건을 지정한 후, 현재의 라우트를 계속할 이유가 없는 경우에는 제어를 후속 라우트에 전달할 수 있습니다.

그러니까 여러 함수를 넣어서 순차적으로 실행시킬 수 있는데, next() 함수로 건너뛰어서 제어를 이어지는 라우트로 넘길 수 있다는 말.

하나의 콜백 함수는 하나의 라우트를 처리할 수 있습니다. 예를 들면 다음과 같습니다.

app.get('/example/a', function (req, res) {
  res.send('Hello from A!');
});

2개 이상의 콜백 함수는 하나의 라우트를 처리할 수 있습니다. (next 오브젝트를 반드시 지정해야 함) 예를 들면 다음과 같습니다.

app.get('/example/b', function (req, res, next) {
  console.log('the response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from B!');
});

하나의 콜백 함수 배열은 하나의 라우트를 처리할 수 있습니다. 예를 들면 다음과 같습니다.

var cb0 = function (req, res, next) {
  console.log('CB0');
  next();
}

var cb1 = function (req, res, next) {
  console.log('CB1');
  next();
}

var cb2 = function (req, res) {
  res.send('Hello from C!');
}

app.get('/example/c', [cb0, cb1, cb2]);

독립적인 함수와 함수 배열의 조합은 하나의 라우트를 처리할 수 있습니다. 예를 들면 다음과 같습니다.

var cb0 = function (req, res, next) {
  console.log('CB0');
  next();
}

var cb1 = function (req, res, next) {
  console.log('CB1');
  next();
}

app.get('/example/d', [cb0, cb1], function (req, res, next) {
  console.log('the response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from D!');
});

응답 메소드

다음 표에 표시된 응답 오브젝트에 대한 메소드(res)는 응답을 클라이언트로 전송하고 요청-응답 주기를 종료할 수 있습니다. 라우트 핸들러로부터 다음 메소드 중 어느 하나도 호출되지 않는 경우, 클라이언트 요청은 정지된 채로 방치됩니다.

그러니까 이건 요청한 클라이언트로 그 결과를 보내주는 메소드라는 것입니다. 아래 메소드 중 하나는 반드시 실행되어야 합니다. 클라이언트로 보내는 결과는 문자열이 될 수도 있고, 파일, html 문서나 상태코드가 될 수도 있습니다. 특별한 경우에는 바디없이 HTTP 헤더만 보낼 수도 있습니다. res (response 객체)는 이러한 기능을 각각의 함수로 구현해 둔 것입니다.

메소드설명
res.download()파일이 다운로드되도록 프롬프트합니다.
res.end()응답 프로세스를 종료합니다.
res.json()JSON 응답을 전송합니다.
res.jsonp()JSONP 지원을 통해 JSON 응답을 전송합니다.
res.redirect()요청의 경로를 재지정합니다.
res.render()보기 템플리트를 렌더링합니다.
res.send()다양한 유형의 응답을 전송합니다.
res.sendFile()파일을 옥텟 스트림의 형태로 전송합니다.
res.sendStatus()응답 상태 코드를 설정한 후 해당 코드를 문자열로 표현한 내용을 응답 본문으로서 전송합니다.



라우터

라우트 메소드들의 집합? 이라고 생각합니다. 라우팅을 위한 라우팅 함수들의 목록?


app.route()

app.route()를 이용하면 라우트 경로에 대해 체인 가능한 라우트 핸들러를 작성할 수 있습니다. 경로는 한 곳에 지정되어 있으므로, 모듈식 라우트를 작성하면 중복성과 오타가 감소하여 도움이 됩니다.

쉽게 말하자면, 라우터는 클라이언트의 요청 경로(path)를 보고 이 요청을 처리할 수 있는 곳으로 기능을 전달해주는 역할을 합니다. 이러한 역할을 라우팅이라고 하는데, 애플리케이션 엔드 포인트(URI)의 정의, 그리고 URI가 클라이언트 요청에 응답하는 방식을 의미합니다. 예를 들어, 클라이언트가 /users 경로로 요청을 보낸다면, 이에 대한 응답 처리를 하는 함수를 별도로 분리해서 만든 다음 get() 메소드를 호출하여 라우터로 등록할 수 있습니다. 즉,

특정 경로로 들어온 요청들을 해당하는 라우팅 함수로 받고, 라우트 핸들러에 연결하여 원하는 동작을 하도록 만든 것이 라우터

라고 할 수 있습니다. 라우터는 트리, 라우팅 함수는 엣지, 라우트 핸들러는 각각의 노드 라고 표현할 수도 있는 것 같습니다.

이진 트리를 예로 들어보면, 해당 노드에서 크면 오른쪽 하위 노드로, 작으면 왼쪽 하위 노드로 이동하는 규칙으로 생성된 트리가 있습니다. 이 때, 트리 전체는 라우터가 되고 '크면 오른쪽 하위 노드로, 작으면 왼쪽 하위 노드로 이동' 이라는 규칙은 라우팅 함수가 되고, 하위에 새로 생긴 노드는 라우트 핸들러라고 할 수 있는 것 같습니다.


express.Router()

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

다음 예시에서는 라우터를 모듈로서 작성하고, 라우터 모듈에서 미들웨어 함수를 로드하고, 몇몇 라우트를 정의하고, 기본 앱의 한 경로에 라우터 모듈을 마운트합니다.

var express = require('express');
var router = express.Router();

// middleware that is specific to this router
router.use(function timeLog(req, res, next) {
  console.log('Time: ', Date.now());
  next();
});
// define the home page route
router.get('/', function(req, res) {
  res.send('Birds home page');
});
// define the about route
router.get('/about', function(req, res) {
  res.send('About birds');
});
module.exports = router;

위 내용을 birds.js 라고 저장을 했다면, 이후 앱 내에서 다음과 같이 라우터 모듈을 불러올 수 있습니다.

var birds = require('./birds');
...
app.use('/birds', birds);

next('route')

라우터에 연결된 다음 미들웨어로 건너뛸 때 사용합니다.

const express = require('express');
const router = express.Router();

router.get('/', (req, res, next) => {
    next('route');
}, (req, res, next) => {
    console.log('실행되지 않습니다.');
    next();
});

router.get('/', (req, res) => {
    console.log('실행됩니다.');
    res.send('Hello, Express.');
});

이렇게 다음 라우터의 라우팅 함수로 넘어갑니다. next() 는 다음 라우팅 핸들러로 넘어갑니다. (뭔 차이인지 지금은 잘 모르겠어서 router.get() 안에 연결된 함수들을 라우팅 핸들러라고 부르고, get() 을 라우팅 함수라고 지칭했습니다.)


라우터 매개변수

라우터 주소에 정규표현식을 비롯한 특수 패턴을 사용할 수 있는데, 이를 라우트 매개변수라고 부릅니다.

const express = require('express');
const router = express.Router();

router.get('/user/:id', (req, res) => {
    console.log(req.params.id);
});

위 코드와 같이 id라는 매개변수에 req.params 로 접근할 수 있습니다. 근데 중요한 점은, 아이디가 tester 인 경우와 단순히 /user/tester 의 경로인 경우가 있을 수 있습니다. 이런 경우에는

router.get('/user/:id', (req, res) => {
    console.log(req.params, req.query);
});

router.get('/user/foo', (req,res) => {
    console.log('Hello, foo!');
});

위와 같이 :id 를 상단에 위치시켜야 합니다. 코드는 위에서 아래 방향으로 실행되므로, id=tester 인 경우를 제대로 다루기 위해서는 위에와 같이 둬야 합니다.

또한 주소에 쿼리 스트링을 쓸 수도 있습니다. 쿼리 스트링의 키-값 정보는 req.query 객체 안에 있습니다. 예를 들어

const express = require('express');
const router = express.Router();

router.get('/user/:id', (req, res) => {
    console.log(req.params, req.query);
    res.send(``);
});

app.use('/', router);

라고 작성한 뒤, /users/123?limit=10&skip=10 이라는 주소로 요청을 하면,

{ id: 'tester' } { limit: '10', skip: '10' }

위와 같은 결과를 얻을 수 있습니다.


라우터 그룹화

만약 주소는 같지만 메서드가 다른 코드가 있을 때, 이를 하나로 묶기 위해서 app.route() 또는 express.Router() 를 사용할 수 있습니다. 예를 들어

const express = require('express');
const router = express.Router();

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

위의 코드는 아래와 같이 묶어서 표현할 수 있습니다.

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



느낀점

라우팅을 한번 가볍게 보면서 뭔가 알듯말듯 블로그 마다 이름이나 표현이 묘하게 달라서 이해하기 조금 어려웠습니다. 좀 더 공부해야 할 것 같습니다. 하지만 MySQL을 연결할 때, 생각 없이 작성하던 .get 이나res.~~ 등의 호출 이유를 알게 되고 express 를 사용하는 이유(라우터 관리가 깔끔합니다) 그리고 node.js 의 특징인 이벤트-핸들러의 의미를 조금이나마 알 것 같습니다.




출처

profile
차근차근 천천히

0개의 댓글