Node.js 를 사용 하여 간단 한 MVC 프레임 워 크 를 실현 하 는 방법

Node.js 를 사용 하여 정적 자원 서버 를 구축 하 는 글 에서 우 리 는 서버 가 정적 자원 에 대한 요청 을 처 리 했 으 나 동적 요청 과 관련 되 지 않 았 고 클 라 이언 트 가 보 낸 서로 다른 요청 에 따라 개성 화 된 내용 을 되 돌 릴 수 없습니다.정적 자원 만 으로 는 이런 복잡 한 사이트 응용 을 견 딜 수 있 습 니까?본 고 는 동태 적 인 요 구 를 어떻게 사용 하 는 지,그리고 간단 하고 쉬 운 MVC 구 조 를 어떻게 구축 하 는 지 소개 할 것 입 니 다.앞에서 정적 자원 요청 이 어떻게 응답 하 는 지 상세 하 게 소 개 했 기 때문에 본 고 는 모든 정적 부분 을 생략 할 것 이다.
간단 한 예시
먼저 간단 한 예제 에 착안 하여 Node 에서 클 라 이언 트 에 게 어떻게 동적 내용 을 되 돌려 주 는 지 알 수 있 습 니 다.
우리 가 이런 수요 가 있다 고 가정 하면:
사용자 가 방문Node할 때 남자 배우 목록 페이지 로 돌아 갑 니 다.
사용자 가 방문/actors할 때 여배우 목록 으로 돌아 가기
다음 코드 로 기능 을 완성 할 수 있 습 니 다:

const http = require('http');
const url = require('url');

http.createServer((req, res) => {
  const pathName = url.parse(req.url).pathname;
  if (['/actors', '/actresses'].includes(pathName)) {
    res.writeHead(200, {
      'Content-Type': 'text/html'
    });
    const actors = ['Leonardo DiCaprio', 'Brad Pitt', 'Johnny Depp'];
    const actresses = ['Jennifer Aniston', 'Scarlett Johansson', 'Kate Winslet'];
    let lists = [];
    if (pathName === '/actors') {
      lists = actors;
    } else {
      lists = actresses;
    }

    const content = lists.reduce((template, item, index) => {
      return template + `<p>No.${index+1} ${item}</p>`;
    }, `<h1>${pathName.slice(1)}</h1>`);
    res.end(content);
  } else {
    res.writeHead(404);
    res.end('<h1>Requested page not found.</h1>')
  }
}).listen(9527);
위의 코드 의 핵심 은 경로 가 일치 하 는 것 입 니 다.요청 이 도 착 했 을 때 경로 에 대응 하 는 논리 적 처리 가 있 는 지 확인 하고 요청 이 일치 하지 않 을 때 404 로 돌아 갑 니 다.일치 성공 시 해당 논 리 를 처리 합 니 다.
simple request
위의 코드 는 분명히 통용 되 지 않 고 두 가지 경로 만 일치 하 는 대기 옵션(요청 방법 을 구분 하지 않 음)과 데이터 베이스 와 템 플 릿 파일 을 사용 하지 않 은 전제 에서 코드 는 이미 약간 꼬 였 다.따라서 그 다음 에 우 리 는 간단 하고 간단 한 MVC 구 조 를 구축 하여 데이터,모델,표현 을 분리 시 켜 각자 의 직책 을 맡 을 것 이다.
간이 MVC 프레임 워 크 구축
MVC 는 각각 다음 과 같다.
M:Model(데이터)
V:View(표현)
C:컨트롤 러(논리)
Node 에서 MVC 구조 에서 요청 을 처리 하 는 과정 은 다음 과 같 습 니 다.
서버 도착 요청
서버 에서 요청 을 경로 로 처리 합 니 다.
경로 일치,요청 가이드 대응 하 는 controller
controller 가 요청 을 받 고 model 에 데 이 터 를 요청 합 니 다.
model 은 controller 에 필요 한 데 이 터 를 되 돌려 줍 니 다.
controller 는 받 은 데 이 터 를 좀 더 가공 해 야 할 수도 있 습 니 다.
controller 는 처 리 된 데 이 터 를 view 에 건 네 줍 니 다.
view 데이터 와 템 플 릿 에 따라 응답 내용 생 성
서버 에서 이 내용 을 클 라 이언 트 로 되 돌려 줍 니 다.
이 를 근거 로 우 리 는 다음 과 같은 모듈 을 준비 해 야 한다.
server:감청 및 응답 요청
router:요청 을 올 바른 controller 에 맡 깁 니 다.
controllers:업무 논 리 를 실행 하고 model 에서 데 이 터 를 꺼 내 view 에 전달 합 니 다.
모델:데이터 제공
view:html 제공
다음 디 렉 터 리 만 들 기:

-- server.js
-- lib
  -- router.js
-- views
-- controllers
-- models
server
server.js 파일 생 성:

const http = require('http');
const router = require('./lib/router')();

router.get('/actors', (req, res) => {
  res.end('Leonardo DiCaprio, Brad Pitt, Johnny Depp');
});

http.createServer(router).listen(9527, err => {
  if (err) {
    console.error(err);
    console.info('Failed to start server');
  } else {
    console.info(`Server started`);
  }
});
이 파일 의 세부 사항 에 관 계 없 이 router 는 다음 에 완 성 될 모듈 입 니 다.여기 서 먼저 도입 하고 도착 하면 바로 처리 하 십시오.
라 우 터 모듈
router 모듈 은 사실 한 가지 일 만 완성 하면 정확 한 contrller 처 리 를 요청 합 니 다.이상 적 으로 이렇게 사용 할 수 있 습 니 다.

const router = require('./lib/router')();
const actorsController = require('./controllers/actors');

router.use((req, res, next) => {
  console.info('New request arrived');
  next()
});

router.get('/actors', (req, res) => {
  actorsController.fetchList();
});

router.post('/actors/:name', (req, res) => {
  actorsController.createNewActor();
});
전체적으로 말 하면,우 리 는 그것 이 중간 부품 과 비 중간 부품 을 동시에 지원 하 기 를 희망 하 며,요청 이 도착 하면 router 에서 일치 하 는 중간 부품 들 에 게 처리 할 것 입 니 다.미들웨어 는 요청 대상 과 응답 대상 에 접근 할 수 있 는 함수 로 미들웨어 에서 할 수 있 는 일 은 다음 과 같 습 니 다.
로그 추가 와 처리 오류 등 모든 코드 를 실행 합 니 다.
요청(req)과 응답 대상(res)을 수정 합 니 다.예 를 들 어 req.url 에서 조회 파 라 메 터 를 가 져 오고 req.query 에 값 을 부여 합 니 다.
끝 응답
다음 미들웨어 호출(next)
Note:
주의해 야 할 것 은 특정한 미들웨어 에서 끝 응답 도 없고 next 방법 으로 제어 권 을 다음 미들웨어 에 맡 기지 않 으 면 요청 이 걸 립 니 다.
__비 경로 미들웨어다음 방식 으로 추가 합 니 다.모든 요청 과 일치 합 니 다.

router.use(fn);
예 를 들 어 위의 예:

router.use((req, res, next) => {
  console.info('New request arrived');
  next()
});
__루트 중간 부품다음 방식 으로 추가 합 니 다.요청 방법 과 경로 가 정확하게 일치 합 니 다.

router.HTTP_METHOD(path, fn)
빗질 을 다 한 후에 먼저 틀 을 써 라.
/lib/router.js

const METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'];

module.exports = () => {
  const routes = [];

  const router = (req, res) => {
    
  };

  router.use = (fn) => {
    routes.push({
      method: null,
      path: null,
      handler: fn
    });
  };

  METHODS.forEach(item => {
    const method = item.toLowerCase();
    router[method] = (path, fn) => {
      routes.push({
        method,
        path,
        handler: fn
      });
    };
  });
};
이상 은 주로 router 에 use,get,post 등 방법 을 추가 하고 이 방법 을 호출 할 때마다 routes 에 routes 규칙 을 추가 합 니 다.
Note:
자바 script 에서 함 수 는 특수 한 대상 으로 호출 될 수 있 으 며 속성,방법 도 가 질 수 있 습 니 다.
다음 의 중점 은 router 함수 입 니 다.해 야 할 일 은:
req 대상 에서 method,pathname 가 져 오기
method,pathname 에 따라 routes 배열 의 각 route 와 추 가 된 순서대로 일치 하도록 요청 합 니 다.
어떤 route 와 일치 하 는 데 성공 하면 route.handler 를 실행 하고 다음 route 와 일치 하거나 끝 나 는 절 차 를 실행 합 니 다(다음 설명)
일치 하지 않 으 면 다음 route 와 계속 일치 하고 3,4 단 계 를 반복 합 니 다.

 const router = (req, res) => {
    const pathname = decodeURI(url.parse(req.url).pathname);
    const method = req.method.toLowerCase();
    let i = 0;

    const next = () => {
      route = routes[i++];
      if (!route) return;
      const routeForAllRequest = !route.method && !route.path;
      if (routeForAllRequest || (route.method === method && pathname === route.path)) {
        route.handler(req, res, next);
      } else {
        next();
      }
    }

    next();
  };
비 경로 미들웨어 에 대해 서 는 handler 를 직접 호출 합 니 다.경로 미들웨어 에 대해 서 는 요청 방법 과 경로 가 모두 일치 할 때 만 handler 를 호출 합 니 다.일치 하 는 route 가 없 을 때 다음 route 와 계속 일치 합 니 다.
주의해 야 할 것 은 특정한 route 가 성공 적 으로 일치 하 는 상황 에서 handler 를 실행 한 후에 다음 route 와 일치 하 는 지 여 부 는 개발 자가 handler 에서 next()를 주동 적 으로 호출 하여 제어 권 을 내 놓 았 는 지 확인 해 야 합 니 다.
재server.js__일부 route 추가:

router.use((req, res, next) => {
  console.info('New request arrived');
  next()
});

router.get('/actors', (req, res) => {
  res.end('Leonardo DiCaprio, Brad Pitt, Johnny Depp');
});

router.get('/actresses', (req, res) => {
  res.end('Jennifer Aniston, Scarlett Johansson, Kate Winslet');
});

router.use((req, res, next) => {
  res.statusCode = 404;
  res.end();
});
요청 이 도착 할 때마다 먼저 log 를 인쇄 하고 다른 route 와 일치 합 니 다.actors 나 actresses 의 get 요청 과 일치 할 때 배우 이름 을 직접 보 내 고 다른 route 와 계속 일치 하지 않 아 도 됩 니 다.일치 하지 않 으 면 404 로 돌아 갑 니 다.
브 라 우 저 에서 순서대로 접근http://localhost:9527/erwe、http://localhost:9527/actors、http://localhost:9527/actresses 테스트 해 보기:
404 /actresses에서 관찰 한 결 과 는 기대 에 부합 되 고 배경 명령 행 에서 도 세 개의network문 구 를 인쇄 했다.
다음은 router 모듈 을 계속 개선 하 겠 습 니 다.
우선 router.all 방법 을 추가 합 니 다.호출 은 모든 요청 방법 에 route 를 추가 한 것 을 의미 합 니 다.

router.all = (path, fn) => {
    METHODS.forEach(item => {
      const method = item.toLowerCase();
      router[method](path, fn);
    })
  };
이어서 오류 처 리 를 추가 합 니 다.
/lib/router.js

const defaultErrorHander = (err, req, res) => {
  res.statusCode = 500;
  res.end();
};

module.exports = (errorHander) => {
  const routes = [];

  const router = (req, res) => {
      ...
    errorHander = errorHander || defaultErrorHander;

    const next = (err) => {
      if (err) return errorHander(err, req, res);
      ...
    }

    next();
  };
server.js

...
const router = require('./lib/router')((err, req, res) => {
  console.error(err);
  res.statusCode = 500;
  res.end(err.stack);
});
...
기본적으로 오류 가 발생 하면 500 을 되 돌려 주지 만 개발 자가 router 모듈 을 사용 할 때 자신의 오류 처리 함수 로 대체 할 수 있 습 니 다.
코드 를 수정 하여 오류 처 리 를 정확하게 수행 할 수 있 는 지 테스트 합 니 다.

router.use((req, res, next) => {
  console.info('New request arrived');
  next(new Error('an error'));
});
이렇게 하면 모든 요청 이 500 으로 돌아 가 야 합 니 다.
error stack
계속,route.path 와 pathname 의 일치 규칙 을 수정 합 니 다.현재 우 리 는 두 문자열 이 같 을 때 만 일치 하 게 통과 할 수 있다 고 생각 합 니 다.이것 은 url 에 경로 매개 변 수 를 포함 하 는 상황 을 고려 하지 않 았 습 니 다.예 를 들 어:
localhost:9527/actors/Leonardo
...과
router.get('/actors/:name', someRouteHandler);
이 route 는 성공 적 으로 일치 해 야 합 니 다.
문자열 형식의 route.path 를 정규 대상 으로 변환 하고 route.pattern 에 저장 하 는 함수 가 추가 되 었 습 니 다.

const getRoutePattern = pathname => {
 pathname = '^' + pathname.replace(/(\:\w+)/g, '\(\[a-zA-Z0-9-\]\+\\s\)') + '$';
 return new RegExp(pathname);
};
이렇게 하면 경로 매개 변 수 를 가 진 url 과 일치 하고 이 경로 매개 변 수 를 req.params 대상 에 저장 할 수 있 습 니 다.

    const matchedResults = pathname.match(route.pattern);
    if (route.method === method && matchedResults) {
      addParamsToRequest(req, route.path, matchedResults);
      route.handler(req, res, next);
    } else {
      next();
    }

const addParamsToRequest = (req, routePath, matchedResults) => {
  req.params = {};
  let urlParameterNames = routePath.match(/:(\w+)/g);
  if (urlParameterNames) {
    for (let i=0; i < urlParameterNames.length; i++) {
      req.params[urlParameterNames[i].slice(1)] = matchedResults[i + 1];
    }
  }
}
route 테스트 추가:

router.get('/actors/:year/:country', (req, res) => {
  res.end(`year: ${req.params.year} country: ${req.params.country}`);
});
방문New request arrived해 보기:
url parameters
router 모듈 은 여기에 쓰 여 있 습 니 다.매개 변수의 포맷 과 요청 주 체 를 가 져 오 는 것 에 대해 서 는 사소한 것 은 시험 하지 않 고 bordy-parser 등 모듈 을 직접 사용 할 수 있어 야 합 니 다.
현재 우 리 는 router 모듈 을 만 들 었 습 니 다.그 다음 에 route handler 안의 업무 논 리 를 모두 contrller 로 옮 깁 니 다.
수정server.js__,컨트롤 러 도입:

...
const actorsController = require('./controllers/actors');
...
router.get('/actors', (req, res) => {
  actorsController.getList(req, res);
});

router.get('/actors/:name', (req, res) => {
  actorsController.getActorByName(req, res);
});

router.get('/actors/:year/:country', (req, res) => {
  actorsController.getActorsByYearAndCountry(req, res);
});
...
새controllers/actors.js__:

const actorsTemplate = require('../views/actors-list');
const actorsModel = require('../models/actors');

exports.getList = (req, res) => {
  const data = actorsModel.getList();
  const htmlStr = actorsTemplate.build(data);
  res.writeHead(200, {
    'Content-Type': 'text/html'
  });
  res.end(htmlStr);
};

exports.getActorByName = (req, res) => {
  const data = actorsModel.getActorByName(req.params.name);
  const htmlStr = actorsTemplate.build(data);
  res.writeHead(200, {
    'Content-Type': 'text/html'
  });
  res.end(htmlStr);
};

exports.getActorsByYearAndCountry = (req, res) => {
  const data = actorsModel.getActorsByYearAndCountry(req.params.year, req.params.country);
  const htmlStr = actorsTemplate.build(data);
  res.writeHead(200, {
    'Content-Type': 'text/html'
  });
  res.end(htmlStr);
};
controller 에 view 와 model 을 동시에 도입 하여 이 두 사람 간 의 접착제 역할 을 한다.controller 의 작업 돌 이 켜 보기:
controller 가 요청 을 받 고 model 에 데 이 터 를 요청 합 니 다.
model 은 controller 에 필요 한 데 이 터 를 되 돌려 줍 니 다.
controller 는 받 은 데 이 터 를 좀 더 가공 해 야 할 수도 있 습 니 다.
controller 는 처 리 된 데 이 터 를 view 에 건 네 줍 니 다.
이 controller 에서 저 희 는 model 모듈 의 방법 으로 배우 목록 을 가 져 오고 데 이 터 를 view 에 건 네 주 며 view 에 게 배우 목록 페이지 를 보 여 주 는 html 문자열 을 생 성 합 니 다.마지막 으로 이 문자열 을 클 라 이언 트 에 게 되 돌려 주 고 브 라 우 저 에 목록 을 보 여 줍 니 다.
model 에서 데이터 가 져 오기
보통 model 은 데이터 베 이 스 를 통 해 데 이 터 를 가 져 와 야 합 니 다.여기 서 우 리 는 데 이 터 를 json 파일 에 저장 하 는 것 을 간소화 합 니 다.
/models/test-data.json

[
  {
    "name": "Leonardo DiCaprio",
    "birth year": 1974,
    "country": "US",
    "movies": ["Titanic", "The Revenant", "Inception"]
  },
  {
    "name": "Brad Pitt",
    "birth year": 1963,
    "country": "US",
    "movies": ["Fight Club", "Inglourious Basterd", "Mr. & Mrs. Smith"]
  },
  {
    "name": "Johnny Depp",
    "birth year": 1963,
    "country": "US",
    "movies": ["Edward Scissorhands", "Black Mass", "The Lone Ranger"]
  }
]
이어서 모델 에서 이 데이터 에 접근 할 수 있 는 방법 을 정의 할 수 있다.
models/actors.js

const actors = require('./test-data');

exports.getList = () => actors;

exports.getActorByName = (name) => actors.filter(actor => {
  return actor.name == name;
});

exports.getActorsByYearAndCountry = (year, country) => actors.filter(actor => {
  return actor["birth year"] == year && actor.country == country;
});
controller 가 model 에서 원 하 는 데 이 터 를 얻 으 면 다음 단 계 는 view 가 빛 을 발 하고 열 을 낼 차례 입 니 다.view 층 은 보통 템 플 릿 엔진,예 를 들 어 dust 등 을 사용 합 니 다.마찬가지 로 간소화 하기 위해 서 여 기 는 템 플 릿 에서 자 리 를 차지 하 는 부 호 를 간단하게 교체 하 는 방식 으로 html 를 얻 고 매우 제한 적 으로 과장 하 며 과정 을 대충 이해 하면 됩 니 다.
생 성/views/actors-list.js:

const actorTemplate = `
<h1>{name}</h1>
<p><em>Born: </em>{contry}, {year}</p>
<ul>{movies}</ul>
`;

exports.build = list => {
  let content = '';
  list.forEach(actor => {
    content += actorTemplate.replace('{name}', actor.name)
          .replace('{contry}', actor.country)
          .replace('{year}', actor["birth year"])
          .replace('{movies}', actor.movies.reduce((moviesHTML, movieName) => {
            return moviesHTML + `<li>${movieName}</li>`
          }, ''));
  });
  return content;
};

브 라 우 저 에서 테스트 해 보기:
test mvc
이로써 큰 성 과 를 거 두 었 다!
이상 의 Node.js 를 사용 하여 간단 한 MVC 프레임 워 크 를 실현 하 는 방법 은 바로 편집장 이 여러분 에 게 공유 한 모든 내용 입 니 다.여러분 에 게 참고 가 되 고 여러분 들 이 저 희 를 많이 응원 해 주시 기 바 랍 니 다.

좋은 웹페이지 즐겨찾기