본문 바로가기
Back-End/Express

[Express] res.send와 res.json의 차이

by SeanK 2022. 1. 4.

Express를 다루다 보면 res.send()와 res.json()을 혼용해서 사용하는 경우가 많다. 

 

 

대강 res.json()은 JSON 데이터를 보낼 때 사용한다고 알고 넘어갔는데,

 

사실 res.send()로도 JSON 데이터가 넘어간다는 사실!

 

엥?

 

그럼 둘의 차이가 도대체 뭐지?

 

res.json() VS res.send()

 

익스프레스 어플리케이션 서버는 HTTP 요청을 받으면, 서버는 흔히 res라고 불리는 객체를 응답으로 전송한다. 

 

res 객체는 기본적으로 API 콜의 보내지는 응답의 한 부분이라고 생각하면 된다. 

 

res.send()는 content type을 "text/Html"로 설정해 클라이언트가 그것을 텍스트로 다루게끔 한다. 

 

반면, res.json()은 content type "application/JSON"으로 설정해 클라이언트에서 응답을 JSON 객체로 다루게끔 한다는 차이가 있다. 

 

 

근데 위에서는 .send()도 JSON을 보낸다며?

 

 

이부분은 .json()과 .send()의 소스코드를 살펴보면 간단하게 이해가 되는데, 코드 길이가 꽤 되니 아래에 공유하니 궁금하신 분들은 찾아보시길...

 

아래 코드를 보면 알겠지만 .json()도 결국에 리턴할 때는 res.send()를 리턴한다. 

 

즉 .json()을 사용하면 여러 가지 json을 보내기 위한 세팅을 마친 뒤 res.send()로 그 데이터를 보내는 방식이다. 

 

res.send의 소스코드에는 (필자도 길어서 다 읽어보진 않았다) 인자로 넘겨지는 body의 데이터 타입을 검사하는 코드가 들어가 있다. 여기서 만약에 데이터 타입이 object이면 res.json()을 호출한다. (코드 중간쯤에 나온 듯하다)

 

사실 이렇게만 놓고보면 .send()와 .json()을 따로 분리한 이유가 있나...? 싶지만 다른 개발자의 글을 읽어보니, 

.json()은 JSON 데이터를 보낸다는 의미를 명확히 표시를 하니 JSON을 보낼 때는 .json()을 사용한다고 한다. 

 

res.send()

 

res.send = function send(body) {
  var chunk = body;
  var encoding;
  var req = this.req;
  var type;

  // settings
  var app = this.app;

  // allow status / body
  if (arguments.length === 2) {
    // res.send(body, status) backwards compat
    if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
      deprecate('res.send(body, status): Use res.status(status).send(body) instead');
      this.statusCode = arguments[1];
    } else {
      deprecate('res.send(status, body): Use res.status(status).send(body) instead');
      this.statusCode = arguments[0];
      chunk = arguments[1];
    }
  }

  // disambiguate res.send(status) and res.send(status, num)
  if (typeof chunk === 'number' && arguments.length === 1) {
    // res.send(status) will set status message as text string
    if (!this.get('Content-Type')) {
      this.type('txt');
    }

    deprecate('res.send(status): Use res.sendStatus(status) instead');
    this.statusCode = chunk;
    chunk = statuses[chunk]
  }

  switch (typeof chunk) {
    // string defaulting to html
    case 'string':
      if (!this.get('Content-Type')) {
        this.type('html');
      }
      break;
    case 'boolean':
    case 'number':
    case 'object':
      if (chunk === null) {
        chunk = '';
      } else if (Buffer.isBuffer(chunk)) {
        if (!this.get('Content-Type')) {
          this.type('bin');
        }
      } else {
        return this.json(chunk);
      }
      break;
  }

  // write strings in utf-8
  if (typeof chunk === 'string') {
    encoding = 'utf8';
    type = this.get('Content-Type');

    // reflect this in content-type
    if (typeof type === 'string') {
      this.set('Content-Type', setCharset(type, 'utf-8'));
    }
  }

  // determine if ETag should be generated
  var etagFn = app.get('etag fn')
  var generateETag = !this.get('ETag') && typeof etagFn === 'function'

  // populate Content-Length
  var len
  if (chunk !== undefined) {
    if (Buffer.isBuffer(chunk)) {
      // get length of Buffer
      len = chunk.length
    } else if (!generateETag && chunk.length < 1000) {
      // just calculate length when no ETag + small chunk
      len = Buffer.byteLength(chunk, encoding)
    } else {
      // convert chunk to Buffer and calculate
      chunk = Buffer.from(chunk, encoding)
      encoding = undefined;
      len = chunk.length
    }

    this.set('Content-Length', len);
  }

  // populate ETag
  var etag;
  if (generateETag && len !== undefined) {
    if ((etag = etagFn(chunk, encoding))) {
      this.set('ETag', etag);
    }
  }

  // freshness
  if (req.fresh) this.statusCode = 304;

  // strip irrelevant headers
  if (204 === this.statusCode || 304 === this.statusCode) {
    this.removeHeader('Content-Type');
    this.removeHeader('Content-Length');
    this.removeHeader('Transfer-Encoding');
    chunk = '';
  }

  if (req.method === 'HEAD') {
    // skip body for HEAD
    this.end();
  } else {
    // respond
    this.end(chunk, encoding);
  }

  return this;
};

 

res.json()

 

res.json = function json(obj) {
  var val = obj;

  // allow status / body
  if (arguments.length === 2) {
    // res.json(body, status) backwards compat
    if (typeof arguments[1] === 'number') {
      deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
      this.statusCode = arguments[1];
    } else {
      deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
      this.statusCode = arguments[0];
      val = arguments[1];
    }
  }

  // settings
  var app = this.app;
  var escape = app.get('json escape')
  var replacer = app.get('json replacer');
  var spaces = app.get('json spaces');
  var body = stringify(val, replacer, spaces, escape)

  // content-type
  if (!this.get('Content-Type')) {
    this.set('Content-Type', 'application/json');
  }

  return this.send(body);
};

'Back-End > Express' 카테고리의 다른 글

[Express] json()과 urlencoded() 메소드에 관하여  (0) 2022.01.06
[Express] CORS Configuration Options  (0) 2021.12.27