먹고 기도하고 코딩하라

express.js API 문서 - app, router, req/res 본문

Javascript

express.js API 문서 - app, router, req/res

사과먹는사람 2020. 7. 6. 22:04
728x90
728x90

지난 주에 express 문서를 보면서 궁금한 걸 다 털어내버렸다. 그런 김에 정리했으니 한 번 더 되새길 겸 블로그에 글도 남긴다. 더 자세하고 정확한 내용은 문서에 있다. 나는 내가 궁금한 것만 정리한 요약본을 올린다.

 


 

express()

- express() : Express app을 생성한다.

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

 

- express.Router() : router 객체를 생성한다. 미들웨어와 HTTP 메소드 라우트를 router 객체에 붙일 수 있다.

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

 

- express.static(root) : Express의 빌트인 미들웨어이다. 정적 파일을 전달해주는 역할을 한다. root는 정적 파일들이 모여 있는 루트 디렉토리를 지정해주는 것이다. 파일이 없으면 404를 뱉는 대신 next()를 호출해서 다음 미들웨어로 옮겨간다. 아래 코드에서는 코드를 쓰는 파일이 위치한 디렉토리 + /public 이라는 경로에서 정적 파일들을 불러온다.

// E:/me/practice/node/main.js
app.use(express.static(__dirname, '/public');
// E:/me/practice/node/public 디렉토리에서 가져오게 됨

 

- express.urlencoded({ option }) :  Express의 빌트인 미들웨어이다. body-parser에 기반해 request에 붙어오는 정보를 파싱해주는 역할을 한다. return 형식은 미들웨어이다. utf-8 인코딩된 req body만 받아들이고, gzip과 deflate 인코딩을 자동으로 해준다. 주로 아래와 같이 POST 메소드로 오는 요청에 대해 쓰인다.

app.post('*', express.urlencoded({ extended: false });

 

 

Application

app 객체의 메소드로는 다음과 같은 것이 있다.

1. http 요청 라우팅 -> app.METHOD(ex. app.post, app.get, ...)

2. 미들웨어 설정 -> app.route

3. HTML 뷰 렌더링 -> app.render

4. 템플릿 엔진 등록 -> app.engine

(그리고 app.use)

 

1. app.METHOD([path], callback)

app.get, app.post, app.put, app.delete(path, middleware) 등이 있다. 이 때 들어가는 미들웨어는 단일 미들웨어이거나 미들웨어 체인(쉼표(,)로 나눠진), 미들웨어 배열일 수 있다.

app.get('/', (req, res, next) => {
  res.send('index page');
});

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!');
});

app.post('/pages', (req, res, next) => {
  if (!req.body.nickname) next(err);
  else next();
}, (req, res, next) => {
  res.status(200).send(`hi! ${req.body.nickname}!`);
});

app.put('/', [middleware1, middleware2]); //가능

두 번째 app.get 함수를 보면 하나의 app.get path에 미들웨어가 2개 묶여 있다. /example/b로 사용자가 접속하면 콘솔에 the response will be sent by... 가 먼저 뜬 다음, next()를 실행하여 바로 다음 스택의 미들웨어인 res.send('Hello from B!')를 실행하게 된다. next()에 대해서는 미들웨어 글에서 따로 다루겠다.

그리고 여기 한 가지 헷갈릴 만한 게 있다. app.get은 매개변수에 따라 2가지 용도로 나뉘는데 path가 있거나 매개변수로 콜백함수를 받는다면 위의 app.METHOD 의 get 메소드인 것이고, app.set과 짝꿍인 app.get이 또 있다. 아래와 같이 쓰인다.

app.set('views', './views');
app.get('views'); //./views

app.set은 view engine 정의나 views를 어디서 가져올지 등을 정한다. 이러저러 써먹을 일이 없진 않다.

 

2. app.route(path)

라우트 객체를 return한다. HTTP 메소드를 붙일 수 있다고 한다. 그런데 아직까지는 이 메소드를 써 본 적이 없다.

app.route('/')
  .get((req, res, next) => {
    console.log('get'); 
  })
  .post((req, res, next) => {
    console.log('post');
  });

위의 코드는 아래와 똑같다.

//routes/root.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res, next) => {
  console.log('get');
});

router.post('/', (req, res, next) => {
  console.log('post');
});

module.exports = router;
//app.js
const rootRouter = require('./routes/root');

app.use('/', rootRouter);

 

3. app.render(view, [locals], callback(err, html))

콜백 함수를 통해 렌더링된 html을 반환한다. 템플릿 파일에 든 변수에 값을 넘기는 객체가 있기도 하다.

//views/email.pug
doctype html
html(lang="en")
  head
    meta(charset="utf-8")
    title= "Email"
  body
    p= name
//app.js
app.set('view engine', 'pug');
app.use(express.static(__dirname, '/views');

app.render('email', { name: 'Tobi' }, (err, html) => {
  //code
});

이 경우 views 디렉토리 안의 email.pug를 보내는데, pug 안의 name 값으로 Tobi를 주게 된다. 아래서 더 자세히 살펴볼 res.render()는 app.render()를 view 렌더링에 사용한다. 근데 사실 나는 저렇게 쓰지는 않고 라우터 모듈에서 res.render로 쓴다. 바로 아래 예제를 참고하시라.

 

 

4. app.engine(확장자, callback)

app.engine('pug', require('pug').__express);

app.engine은 템플릿 엔진을 사용할 때 어떤 것을 사용할지 정하는 것이다. 딱히 다른 의미는 없고, app.set('view engine', 'pug') 했다면 위의 코드는 생략해도 된다.

내친김에 템플릿 렌더링을 좀 살펴보자면 다음과 같이 하면 된다.

//views/index.pug
html
  head
    title= title
  body
    h1= message
//app.js
app.engine('pug', require('pug').__express);
app.set('views', './views');	//views 템플릿이 있는 디렉토리
app.set('view engine', 'pug');	//사용할 템플릿 엔진

app.get('/', (req, res) => {
  res.render('index', { title: 'Hi', message: 'Hello world!' }, (err, html) => {
  //템플릿 엔진을 쓸 때는 res.render를 씀. 첫 번째는 파일 이름(이름만), 두 번째는 파일 안의 변수들
    if (err) throw err;
    res.send(html);
  });
});

 

 

5. app.use([path], callback)

app.use([path], callback)는 특정 경로에 콜백으로 등록된 미들웨어 함수를 마운팅하는 함수이다. 만약 path가 없다면(아래 예시에서 보게 될 것) 모든 요청에 대해 콜백 함수를 실행하게 된다. app.METHOD()와 달리 app.use는 app.use('/apple')이라고 했을 때 '/apple', '/apple/2', '/apple/seed/5' 같은 경로에 모두 매칭된다. path에는 정규식을 쓰거나 간단한 패턴을 쓸 수도 있다.

router도 적합한 미들웨어이므로 app.use 안에 라우터가 매개변수로 들어갈 수 있다. 이 app.use는 단일 미들웨어를 받을 수도 있고, 미들웨어 여러 개를 받거나 심지어는 배열로 받을 수도 있다. 라우터 모듈을 쓸 때는 app.get이 아니라 app.use()로 쓰면 해당 path에서 알아서 라우터로 들어가서 거기에서 요청을 처리하게 된다.

app.use('/', router);
app.use(router1, router2);
app.use([router1, router2, router3]);	//셋 다 가능

요약 : app.use(path, callback)은 해당 path에 대해 등록된 미들웨어(콜백)를 실행한다. path가 없다면 모든 경로에 대한 요청에 대해 미들웨어를 실행한다. 

 

+ app.all(path, callback)

app.all은 좀 특수한 라우팅 메소드이다. path가 반드시 필요하다. 어떤 http 메소드로부터 파생되지 않고 모든 요청 메소드에 대해 한 경로에서 미들웨어 함수를 로드하는 데 사용된다. 말이 좀 어렵다. 일단 코드를 보자.

app.all('/secret', (req, res, next) => {
  res.send('secret page');
});

/secret path를 담당하고 있는데, 사용자가 /secret으로 접속하게 되면 get, post, put, delete... 그 외 어떤 메소드든지 간에 이 핸들러가 실행되게 되어 있다. 단, 이 메소드 위에 app.get('/secret'), app.post('/secret') 같은 함수가 있지 않다는 가정 하에서다. 쉽게 말해 하나의 경로에 대해 모든 메소드 접근을 다 받아들이는 메소드이다.

 

+ app.listen(portNUM, callback)

app.listen(portNUM)은 포트 번호에 리스닝이 성공하면 callback 함수를 실행한다. 보통 3000번 포트를 많이 쓰는데 Node.js에서 기본으로 쓰는 포트 번호라고 한다. 왜 하필 3000인지는 잘 모르겠다. 콜백함수는 생략 가능하다.

app.listen(3000);

 

Request

보통 req로 많이 줄여서들 쓴다. req 객체는 HTTP 요청을 나타낸다. 요청에 대한 쿼리 스트링, 파라미터, 바디, 헤더 등을 프로퍼티로 가진다. 몇 가지 쓸만한 것들을 살펴보자.

 

req.body

key-value 쌍을 포함해 POST 메소드를 통해 제출된 값이다. 기본은 undefined이고 express.json()이나 express.urlencoded()를 하고 난 다음 쓰게 된다.

app.post('*', express.urlencoded({ extended: false }));
app.post('/profile', (req, res, next) => {
  console.log(req.body.name, req.body.major);
});

 

req.cookie

cookie-parser 미들웨어를 쓸 때 이 프로퍼티가 요청에 의해 전송된 쿠키를 갖고 있다. 쿠키가 없다면 기본은 { }이다.

 

req.ip

요청의 IP 주소를 포함한다.

 

req.params

이거 많이 쓴다. 먼저 코드를 보자.

app.get('/user/:name', (req, res, next) => {
  if (req.params.name === 'Luke') {
    res.send('Welcome, Master Luke!');
  } else {
    res.send('What can I help you?');
  }
});

// /user/Luke 일 때 req.params = { name: 'Luke' }

사용자가 /user/Luke 라는 경로로 들어갔을 때 req.params.name은 Luke가 된다. 경로에 콜론(:)을 붙이면 그 자리에 입력하는 것은 key(name)의 value가 돼서 req.params라는 객체에 프로퍼티로 담기게 된다. 사용자가 요청한 주소 값에 따라 값이 달라진다. req.params.name 이렇게 접근이 가능하다. 쏠쏠하게 잘 써먹을 수 있어 좋다. 기본은 { } 이다. 

 

req.path

기존 Node.js에서 url_parse(req.url, true) 해서 나온 객체의 pathname과 똑같다. 쿼리 스트링을 제외한 path만 반환한다.

app.get((req, res, next) => {
  console.log(req.url);
});

// /pages/5?id=Luke로 접속 -> /pages/5

 

req.query

각 쿼리 스트링의 프로퍼티를 갖고 있는 객체. query parser가 없다면 빈 객체이다. 기존 url_parse(req.url, true)에서 query를 한 것과 똑같다.

app.get((req, res, next) => {
  console.log(req.query);
});

// /pages/5?id=Luke로 접속 -> { id: 'Luke' }

 

 

Response

보통 res로 많이 줄여서들 쓴다. res 객체는 HTTP 응답을 나타내며 express 앱이 http 요청을 받았을 때 브라우저에 전송하게 된다.

 

res.end()

응답 프로세스를 끝낸다. 뭔가 데이터를 보내려면 res.end 안에 집어넣지 말고 res.send를 쓴다.

 

res.location(path)

응답 HTTP 헤더 Location에 path를 집어넣는다고 한다. 난 써본 적 없다. 와중에 res.location('back')은 특별한 값인데, 요청의 Referer 헤더값을 참조하게 된다고 한다. Referer가 없다면 '/'을 의미한다고

 

res.redirect(status, path)

특정 path로 리다이렉트해준다. status code가 딱히 정해져 있지 않다면 302 Found이다.

 

res.render(view [, locals] [, callback])

view를 렌더링해 HTML 문자열로 보내 클라이언트에게 보내준다. view 인자는 렌더링할 view 파일의 이름이다. 확장자가 없다면 view engine에 설정된 확장자로 결정하게 된다. locals는 view의 비어 있는 지역 변수 정의를 위한 프로퍼티고 callback은 err와 렌더링된 문자열을 갖게 된다. 콜백이 있다고 자동으로 뭔가 일어나진 않으니 res.send(html)을 하든지 뭔가 해야 한다. 만약 err가 생기면 next(err)를 내부적으로 호출한다. 템플릿 엔진을 쓴다면 아주 자주 쓰게 될 메소드다.

res.render('index', { name: 'Tobi' }, (err, html) => { res.send(html) });

 

res.send([body])

HTTP 응답을 전송한다. body 파라미터는 Buffer 객체, String, 객체, 배열 등이 될 수 있다.

 

res.type(type)

HTTP 헤더의 Content-Type을 정한다.

 

 

Router

모든 express 앱은 빌트인 앱 라우터를 가진다. 이 router 객체는 미들웨어처럼 동작한다. 즉, app.use()나 다른 router.use()의 인자로 router를 넘겨줄 수 있다는 뜻이다.

 

router.all(path, callback)

router.post, router.get 같은 router.METHOD 외의 다른 모든 것에 매칭된다. app.all의 라우터 버전이다.

 

router.METHOD(path,callback)

쿼리 스트링이 있든 없든 상관없이 path와 HTTP 요청 메소드만 보고 매칭한다.

router.get('/pages/:id', (req, res, next) => {
  console.log(req.params.id);
  res.send(`page ${id}: Welcome!`);
});

// 사용자가 /pages/5?id=Luke 로 GET 방식 접근 -> 위 메소드로 들어감

 

router.route(path)

HTTP 메소드(get, all 등)를 매달아 쓸 수 있는 단일 route를 return한다. app.route랑 비슷하다. 쉽게 말해 하나의 path에 여러 http 메소드를 체이닝할 수 있는 것이다. 한 경로에 여러 메소드로 접근 가능하다면 router 안에서도 이렇게 쓸 수 있다.

router.route('/login')
  .get((req, res, next) => {
    res.redirect('/');
  })
  .post((req, res, next) => {
    db.query('select * from user where email=? and pw=?', (err, results) => {});
  });

 

router.use([path], [function, ...] function)

특정 미들웨어 함수(들)를 사용한다. app.use()와 비슷한 메소드. morgan이나 winston 같은 모듈을 설치해 로그를 제일 먼저 찍게 하기에 좋다.

const logger = require('morgan');
router.use(logger());

 

알아둘 것은 use는 router.method 함수처럼 next()로 체이닝하는 것이 아니라는 것이다. use를 써두면 위에서부터 아래로 순서대로 use에 등록된 미들웨어들이 순차 실행되는 것이지, 어느 것은 빠지고 그렇지 않다. 다만 라우팅을 하는 미들웨어를 만나서 그 라우터에서 http 메소드로 빠진다면 그 때는 얘기가 달라진다. req, res를 받는 콜백이 있는 순간 그 콜백에서 next()를 하느냐 마느냐에 따라 응답이 끝날지 아닐지가 다르다. 

 

 

 

Reference

Express 4.x - API 참조

 

 

728x90
반응형
Comments