[Node.js] 요청 및 응답 객체: Express에서의 상호작용
안녕하세요! 지난 시간에는 Express로 간단한 서버를 만들어보고, 서버에 요청이 들어왔을 때 응답이 없는 문제를 확인했습니다. 이번에는 서버가 요청을 받으면서 어떤 객체를 사용하는지, 그리고 이를 통해 어떻게 응답을 생성하는지 알아보겠습니다.
들어오는 요청을 받아오는 객체: req
Express에서 모든 들어오는 요청은 콜백 함수에 자동으로 전달되는 객체를 통해 처리됩니다. 이 객체의 이름은 원하는 대로 정할 수 있으나, req가 일반적으로 사용되는 표준입니다.
app.use((req, res) => {
// 여기에서 req 객체 사용
});
req 객체는 HTTP 요청 정보를 담고 있습니다. 예를 들어, 어떤 URL로의 요청인지, 어떤 메서드(GET, POST 등)를 사용했는지 등의 정보를 담고 있습니다. 이를 통해 우리는 들어오는 요청에 대한 다양한 정보를 활용할 수 있습니다.
console.dir(req);
req 안의 상당한 객체들을 보고싶다면 클릭해보세요.
jinhojun@jinhojun-ui-MacBookAir FirstAPP % node index.js
LISTENING ON PORT 8080!
WE GOT A NEW REQUEST!!
<ref *2> IncomingMessage {
_readableState: ReadableState {
state: 137264,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: [],
flowing: null,
errored: null,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
decoder: null,
encoding: null,
[Symbol(kPaused)]: null
},
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
socket: <ref *1> Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_closeAfterHandlingError: false,
_readableState: ReadableState {
state: 4184,
highWaterMark: 16384,
buffer: [BufferList],
length: 0,
pipes: [],
flowing: true,
errored: null,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
decoder: null,
encoding: null,
[Symbol(kPaused)]: false
},
_events: [Object: null prototype] {
end: [Array],
timeout: [Function: socketOnTimeout],
data: [Function: bound socketOnData],
error: [Function: socketOnError],
close: [Array],
drain: [Function: bound socketOnDrain],
resume: [Function: onSocketResume],
pause: [Function: onSocketPause]
},
_eventsCount: 8,
_maxListeners: undefined,
_writableState: WritableState {
state: 786508,
highWaterMark: 16384,
defaultEncoding: 'utf8',
length: 0,
corked: 0,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
buffered: [],
bufferedIndex: 0,
pendingcb: 0,
errored: null,
[Symbol(kOnFinished)]: []
},
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
requestTimeout: 300000,
headersTimeout: 60000,
keepAliveTimeout: 5000,
connectionsCheckingInterval: 30000,
requireHostHeader: true,
joinDuplicateHeaders: undefined,
rejectNonStandardBodyWrites: false,
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
_connections: 1,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
noDelay: true,
keepAlive: false,
keepAliveInitialDelay: 0,
highWaterMark: 16384,
httpAllowHalfOpen: false,
timeout: 0,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
_connectionKey: '6::::8080',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 4,
[Symbol(kUniqueHeaders)]: null,
[Symbol(http.server.connections)]: ConnectionsList {},
[Symbol(http.server.connectionsCheckingInterval)]: [Timeout]
},
_server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
requestTimeout: 300000,
headersTimeout: 60000,
keepAliveTimeout: 5000,
connectionsCheckingInterval: 30000,
requireHostHeader: true,
joinDuplicateHeaders: undefined,
rejectNonStandardBodyWrites: false,
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
_connections: 1,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
noDelay: true,
keepAlive: false,
keepAliveInitialDelay: 0,
highWaterMark: 16384,
httpAllowHalfOpen: false,
timeout: 0,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
_connectionKey: '6::::8080',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 4,
[Symbol(kUniqueHeaders)]: null,
[Symbol(http.server.connections)]: ConnectionsList {},
[Symbol(http.server.connectionsCheckingInterval)]: [Timeout]
},
parser: HTTPParser {
'0': null,
'1': [Function: parserOnHeaders],
'2': [Function: parserOnHeadersComplete],
'3': [Function: parserOnBody],
'4': [Function: parserOnMessageComplete],
'5': [Function: bound onParserExecute],
'6': [Function: bound onParserTimeout],
_headers: [],
_url: '',
socket: [Circular *1],
incoming: [Circular *2],
outgoing: null,
maxHeaderPairs: 2000,
_consumed: true,
onIncoming: [Function: bound parserOnIncoming],
joinDuplicateHeaders: null,
[Symbol(resource_symbol)]: [HTTPServerAsyncResource]
},
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
setEncoding: [Function: socketSetEncoding],
_paused: false,
_httpMessage: ServerResponse {
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
_closed: false,
socket: [Circular *1],
_header: null,
_keepAliveTimeout: 5000,
_onPendingData: [Function: bound updateOutgoingData],
req: [Circular *2],
_sent100: false,
_expect_continue: false,
_maxRequestsPerSocket: 0,
locals: [Object: null prototype] {},
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype],
[Symbol(errored)]: null,
[Symbol(kHighWaterMark)]: 16384,
[Symbol(kRejectNonStandardBodyWrites)]: false,
[Symbol(kUniqueHeaders)]: null
},
[Symbol(async_id_symbol)]: 9,
[Symbol(kHandle)]: TCP {
reading: true,
onconnection: null,
_consumed: true,
[Symbol(owner_symbol)]: [Circular *1]
},
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: true,
[Symbol(kSetKeepAlive)]: false,
[Symbol(kSetKeepAliveInitialDelay)]: 0,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: false,
rawHeaders: [
'Host',
'localhost:8080',
'Connection',
'keep-alive',
'sec-ch-ua',
'"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
'sec-ch-ua-mobile',
'?0',
'sec-ch-ua-platform',
'"macOS"',
'Upgrade-Insecure-Requests',
'1',
'User-Agent',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
'Accept',
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Sec-Fetch-Site',
'none',
'Sec-Fetch-Mode',
'navigate',
'Sec-Fetch-User',
'?1',
'Sec-Fetch-Dest',
'document',
'Accept-Encoding',
'gzip, deflate, br',
'Accept-Language',
'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
],
rawTrailers: [],
joinDuplicateHeaders: null,
aborted: false,
upgrade: false,
url: '/',
method: 'GET',
statusCode: null,
statusMessage: null,
client: <ref *1> Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_closeAfterHandlingError: false,
_readableState: ReadableState {
state: 4184,
highWaterMark: 16384,
buffer: [BufferList],
length: 0,
pipes: [],
flowing: true,
errored: null,
defaultEncoding: 'utf8',
awaitDrainWriters: null,
decoder: null,
encoding: null,
[Symbol(kPaused)]: false
},
_events: [Object: null prototype] {
end: [Array],
timeout: [Function: socketOnTimeout],
data: [Function: bound socketOnData],
error: [Function: socketOnError],
close: [Array],
drain: [Function: bound socketOnDrain],
resume: [Function: onSocketResume],
pause: [Function: onSocketPause]
},
_eventsCount: 8,
_maxListeners: undefined,
_writableState: WritableState {
state: 786508,
highWaterMark: 16384,
defaultEncoding: 'utf8',
length: 0,
corked: 0,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
buffered: [],
bufferedIndex: 0,
pendingcb: 0,
errored: null,
[Symbol(kOnFinished)]: []
},
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
requestTimeout: 300000,
headersTimeout: 60000,
keepAliveTimeout: 5000,
connectionsCheckingInterval: 30000,
requireHostHeader: true,
joinDuplicateHeaders: undefined,
rejectNonStandardBodyWrites: false,
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
_connections: 1,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
noDelay: true,
keepAlive: false,
keepAliveInitialDelay: 0,
highWaterMark: 16384,
httpAllowHalfOpen: false,
timeout: 0,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
_connectionKey: '6::::8080',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 4,
[Symbol(kUniqueHeaders)]: null,
[Symbol(http.server.connections)]: ConnectionsList {},
[Symbol(http.server.connectionsCheckingInterval)]: [Timeout]
},
_server: Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
requestTimeout: 300000,
headersTimeout: 60000,
keepAliveTimeout: 5000,
connectionsCheckingInterval: 30000,
requireHostHeader: true,
joinDuplicateHeaders: undefined,
rejectNonStandardBodyWrites: false,
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
_connections: 1,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
noDelay: true,
keepAlive: false,
keepAliveInitialDelay: 0,
highWaterMark: 16384,
httpAllowHalfOpen: false,
timeout: 0,
maxHeadersCount: null,
maxRequestsPerSocket: 0,
_connectionKey: '6::::8080',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 4,
[Symbol(kUniqueHeaders)]: null,
[Symbol(http.server.connections)]: ConnectionsList {},
[Symbol(http.server.connectionsCheckingInterval)]: [Timeout]
},
parser: HTTPParser {
'0': null,
'1': [Function: parserOnHeaders],
'2': [Function: parserOnHeadersComplete],
'3': [Function: parserOnBody],
'4': [Function: parserOnMessageComplete],
'5': [Function: bound onParserExecute],
'6': [Function: bound onParserTimeout],
_headers: [],
_url: '',
socket: [Circular *1],
incoming: [Circular *2],
outgoing: null,
maxHeaderPairs: 2000,
_consumed: true,
onIncoming: [Function: bound parserOnIncoming],
joinDuplicateHeaders: null,
[Symbol(resource_symbol)]: [HTTPServerAsyncResource]
},
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
setEncoding: [Function: socketSetEncoding],
_paused: false,
_httpMessage: ServerResponse {
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
_closed: false,
socket: [Circular *1],
_header: null,
_keepAliveTimeout: 5000,
_onPendingData: [Function: bound updateOutgoingData],
req: [Circular *2],
_sent100: false,
_expect_continue: false,
_maxRequestsPerSocket: 0,
locals: [Object: null prototype] {},
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype],
[Symbol(errored)]: null,
[Symbol(kHighWaterMark)]: 16384,
[Symbol(kRejectNonStandardBodyWrites)]: false,
[Symbol(kUniqueHeaders)]: null
},
[Symbol(async_id_symbol)]: 9,
[Symbol(kHandle)]: TCP {
reading: true,
onconnection: null,
_consumed: true,
[Symbol(owner_symbol)]: [Circular *1]
},
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: true,
[Symbol(kSetKeepAlive)]: false,
[Symbol(kSetKeepAliveInitialDelay)]: 0,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
_consuming: false,
_dumped: false,
next: [Function: next],
baseUrl: '',
originalUrl: '/',
_parsedUrl: Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: null,
query: null,
pathname: '/',
path: '/',
href: '/',
_raw: '/'
},
params: {},
query: {},
res: <ref *3> ServerResponse {
_events: [Object: null prototype] { finish: [Function: bound resOnFinish] },
_eventsCount: 1,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
strictContentLength: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
_closed: false,
socket: <ref *1> Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: null,
_closeAfterHandlingError: false,
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: true,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: [Server],
_server: [Server],
parser: [HTTPParser],
on: [Function: socketListenerWrap],
addListener: [Function: socketListenerWrap],
prependListener: [Function: socketListenerWrap],
setEncoding: [Function: socketSetEncoding],
_paused: false,
_httpMessage: [Circular *3],
[Symbol(async_id_symbol)]: 9,
[Symbol(kHandle)]: [TCP],
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: true,
[Symbol(kSetKeepAlive)]: false,
[Symbol(kSetKeepAliveInitialDelay)]: 0,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
_header: null,
_keepAliveTimeout: 5000,
_onPendingData: [Function: bound updateOutgoingData],
req: [Circular *2],
_sent100: false,
_expect_continue: false,
_maxRequestsPerSocket: 0,
locals: [Object: null prototype] {},
[Symbol(kCapture)]: false,
[Symbol(kBytesWritten)]: 0,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] { 'x-powered-by': [Array] },
[Symbol(errored)]: null,
[Symbol(kHighWaterMark)]: 16384,
[Symbol(kRejectNonStandardBodyWrites)]: false,
[Symbol(kUniqueHeaders)]: null
},
[Symbol(kCapture)]: false,
[Symbol(kHeaders)]: {
host: 'localhost:8080',
connection: 'keep-alive',
'sec-ch-ua': '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
},
[Symbol(kHeadersCount)]: 28,
[Symbol(kTrailers)]: null,
[Symbol(kTrailersCount)]: 0
}
응답을 생성하는 객체: res
다음으로, 응답을 생성하기 위한 객체인 res를 알아봅시다. 마찬가지로, 이 객체의 이름은 정할 수 있지만, res가 흔히 사용되는 이름입니다.
app.use((req, res) => {
// 여기에서 res 객체 사용
});
res 객체는 다양한 메서드를 가지고 있습니다. 이 중에서 가장 자주 사용되는 것이 res.send입니다. 이 메서드를 사용하면 간단하게 HTTP 응답을 보낼 수 있습니다.
res.send("Hello, we got your request! THIS IS A RESPONSE");
예시: JSON 응답 보내기
res.send는 다양한 데이터 유형을 처리할 수 있습니다. 예를 들어, JavaScript 객체를 전달하여 JSON 형태로 응답을 생성할 수 있습니다.
res.send({ color: "red", message: "A fly landed on my face. Sorry!" });
이렇게 하면 Express는 자동으로 HTTP 응답 헤더 필드를 할당하고, 적절한 콘텐츠 타입을 설정합니다.
이제 터미널에서 서버를 재실행하고, Postman이나 브라우저를 통해 요청을 보내면, 다양한 형태의 응답을 확인할 수 있을 것입니다. 이러한 간단한 예제를 통해 Express에서의 요청과 응답 객체 사용법을 익혔습니다.
다음 시간에는 요청에 따라 특정한 응답을 생성하는 방법, 즉 라우팅에 대해 자세히 배워보도록 하겠습니다. 계속해서 함께해요! 🚀