백엔드

Express req.query와 query parser

lgvv 2026. 7. 7. 00:58

Express req.query와 query parser

Express에서 query string이 req.query로 어떻게 들어오는지 정리.

특히 같은 query key를 여러 번 보냈을 때 왜 배열로 바뀌는지 헷갈려서 정리.


이 글은 Express 5의 기본 query parser인 simple 기준으로 하며, Express 4는 기본값이 extended였으므로 버전에 따라 parser가 다를 수 있음.

예제 요청

아래처럼 같은 key인 tags를 여러 번 보낸다고 가정.

GET /example/info/b?tags=node&tags=express&tags=javascript

 

Express에서 req.query를 찍으면 아래처럼 보임.

[Object: null prototype] {
  tags: ["node", "express", "javascript"]
}

 

tags가 하나의 문자열이 아니라 배열로 들어옴.

app.get("/example/info/b", (req, res) => {
  console.log(req.query);

  const tags = req.query.tags;

  res.send({
    tags,
    message: `선택한 태그: ${Array.isArray(tags) ? tags.join(", ") : tags}`,
  });
});

 

왜 배열이 되는지

Express가 URL의 query string을 읽고 req.query 객체를 만드는 과정에서 생김.

다만 Express가 직접 같은 key를 하나씩 검사해서 배열로 만드는 구조는 아니며, Express는 설정된 query parser를 호출하고, 실제 변환은 parser가 담당.

 

query parser 설정

Express에는 query parser 설정이 있음.

Express 5 기준 기본값은 simple.

app.set("query parser", "simple");

 

이 설정은 아래 의미.

query string을 파싱할 때 simple parser 사용

 

처음에는 app.set이 배열 변환을 직접 하는 줄 알았는데, 그건 아니었음.

app.set은 Express 앱의 설정값을 저장하는 메서드로 아래처럼 나눌 수 있음.

 

app.set("query parser", "simple")
-> 어떤 query parser를 쓸지 설정

parser
-> query string을 실제 객체로 변환

 

simple query parser

 

Express에서 query parser 설정이 simple이면 Node 내장 모듈인 querystring.parse를 사용.

 

case "simple":
  fn = querystring.parse;
  break;

 

즉 simple은 query string 파싱에 querystring.parse에 사용.

 

실제 배열 변환

실제 query string을 객체로 바꾸는 동작은 querystring.parse에서 일어남.

const querystring = require("querystring");

querystring.parse("tags=node&tags=express&tags=javascript");

 

결과:

[Object: null prototype] {
  tags: ["node", "express", "javascript"]
}

 

같은 key인 tags가 여러 번 나오므로 값들을 배열로 묶음.

Node 문서 예제도 같은 흐름.

 

querystring.parse("foo=bar&abc=xyz&abc=123");


결과는 아래처럼 abc가 배열이 됨.

 

{
  foo: "bar",
  abc: ["xyz", "123"]
}

 

전체 흐름

정리하면 아래 흐름.

URL 요청
/example/info/b?tags=node&tags=express&tags=javascript

-> Express가 ? 뒤의 query string 확인
tags=node&tags=express&tags=javascript

-> app 설정에서 query parser 확인
query parser = "simple"

-> simple 설정이므로 querystring.parse 사용

-> 같은 key가 여러 번 있으므로 배열로 변환
{
  tags: ["node", "express", "javascript"]
}

-> req.query에 들어감

 

역할만 다시 보면 아래처럼 정리해볼 수 있음.

 

app.set(...)
-> 어떤 parser를 쓸지 설정

querystring.parse(...)
-> query string을 실제 객체로 변환
-> 같은 key가 여러 번 있으면 배열로 만듦

 

[Object: null prototype] 정리

 

로그에 아래처럼 찍히는 경우가 있음.

[Object: null prototype] { tags: ["node", "express"] }

 

Node의 querystring.parse가 일반 객체 {}와 조금 다른 객체를 만들어서 그렇게 보이는 것.

Node 문서 기준으로도 querystring.parse가 반환하는 객체는 Object.prototype을 상속하지 않음.

 

지금 단계에서는 크게 신경쓰지 않아도 괜찮아 보임.

중요한 건 req.query.tags가 배열로 들어올 수 있다는 점.

 

const tags = req.query.tags;

console.log(Array.isArray(tags)); // true
console.log(tags[0]); // "node"
console.log(tags[1]); // "express"


하나만 보내면 문자열

같은 key가 여러 번 들어오면 배열.

하나만 들어오면 문자열.

GET /example/info/b?tags=node

 

결과:

{
  tags: "node"
}

 

그래서 tags를 무조건 배열이라고 생각하면 헷갈릴 수 있음.

실제로는 문자열일 수도 있고 배열일 수도 있음.

그래서 사용할 때 한 번 정규화해두는 편이 안전해 보임.

 

const tags = req.query.tags;

const tagList = Array.isArray(tags)
  ? tags
  : tags
  ? [tags]
  : [];

 

이렇게 하면 이후 로직에서는 tagList를 배열로만 다룰 수 있음.


extended parser를 쓰면?

query parser를 extended로 바꾸면 qs 모듈 기반으로 파싱.

app.set("query parser", "extended");

 

simple보다 복잡한 query string을 다룰 수 있음.

예를 들면 아래처럼 중첩된 형태.

 

GET /users?filter[name]=kim

 

다만 지금 글에서 헷갈린 포인트는 같은 key가 여러 번 들어왔을 때 배열이 되는 이유.

이 케이스는 simple 기준으로는 querystring.parse가 처리한다고 이해할 수 있음.

정리

req.query는 Express가 query string을 파싱해서 만들어주는 객체로 Express 5 기준 기본 query parser는 simple.

simple은 Node 내장 querystring.parse를 사용하며, 같은 key가 여러 번 오면 querystring.parse가 값을 배열로 묶음.

 

tags=node&tags=express&tags=javascript

 

결과:

{
  tags: ["node", "express", "javascript"]
}

 

반대로 하나만 오면 문자열.

{
  tags: "node"
}

 

따라서 req.query.tags를 사용할 때는 문자열과 배열 가능성을 같이 고려하는 편이 안전해 보임.

 


(참고)
https://expressjs.com/en/5x/api/application/
https://expressjs.com/en/5x/api/request/
https://nodejs.org/api/querystring.html

'백엔드' 카테고리의 다른 글

Express app.param vs app.get  (0) 2026.07.07
Socket과 WebSocket 정리 (RFC 6455)  (0) 2026.02.11
Message Broker 개념 정리와 Kafka, RabbitMQ 비교  (0) 2025.11.05
Node.js 몽고디비 알아보기  (0) 2023.08.10
Node.js MySQL 다뤄보기  (0) 2023.07.23