Request and Response
Express extends the native Node.js http.IncomingMessage (request) and http.ServerResponse (response) objects with a rich set of convenience properties and methods. These extensions form the primary interface through which route handlers and middleware interact with incoming HTTP requests and construct outgoing responses. This page documents every property and method Express adds, explains how content negotiation works, and shows how to set headers, status codes, and response bodies.
Purpose
The core purpose of Express's request and response extensions is to eliminate boilerplate when handling HTTP interactions. Without Express, a developer would need to manually parse URL parameters, query strings, request bodies, and cookies, then manually construct response bodies with correct headers and status codes. Express's req and res objects encapsulate these common tasks into intuitive, chainable methods.
The extensions serve four primary goals:
- Parse and expose request data (parameters, query strings, body, headers, cookies) through convenient properties.
- Simplify response construction (JSON, HTML, file downloads, redirects) with dedicated methods.
- Automate content negotiation so the server responds with the format the client prefers.
- Provide lifecycle hooks (like
req.accepts()andres.format()) that integrate with Express's middleware pipeline.
How It Works: Architecture of the Module
Express does not replace the Node.js request and response objects. Instead, it attaches additional properties and methods directly onto the native objects using a technique called "monkey-patching." This happens inside the createServer function in lib/express.js and the handle method in lib/application.js.
The key architectural points are:
- Request augmentation occurs in
lib/request.js. This file exports an object whose properties are merged into thereqobject at runtime. - Response augmentation occurs in
lib/response.js. Similarly, its properties are merged into theresobject. - The merge happens lazily via
__proto__assignment inlib/application.jsaround line 50-60, ensuring that every request object inherits Express's methods without modifying the globalhttp.IncomingMessageprototype. - Middleware can further augment
reqandres(e.g.,body-parseraddsreq.body,cookie-parseraddsreq.cookies), but those are separate modules. Express itself only adds the properties documented here.
The augmentation is visible in lib/express.js:
// lib/express.js (lines 30-40)
var proto = require('./application');
var req = require('./request');
var res = require('./response');These modules are then applied in lib/application.js:
// lib/application.js (lines 50-60)
app.request = { __proto__: req, app: app };
app.response = { __proto__: res, app: app };When a request arrives, Express creates req and res objects that inherit from these prototypes, giving every handler access to the extended API.
Walkthrough of Each Main Flow
Request Object Extensions
Express adds the following properties and methods to the req object. Each is defined in lib/request.js.
req.params
This property contains route parameters parsed from the URL path. For example, a route defined as /users/:userId would populate req.params.userId with the actual value from the request URL.
The implementation in lib/request.js (lines 45-55) shows:
defineGetter(req, 'params', function params(){
if (this._params) { return this._params; }
this._params = this._route? this._route.params(this): {};
return this._params;
});The _params cache ensures that parsing happens only once per request. The actual parsing is delegated to the route's params method, which uses the route pattern to extract named segments.
req.query
This property contains the parsed query string parameters. Express uses the qs library (or the built-in querystring module) to parse the URL's query portion.
From lib/request.js (lines 60-70):
defineGetter(req, 'query', function query(){
var qs = require('qs');
var str = parseUrl(this).query;
var c = this._querycache;
if (c && c.str === str) return c.val;
var val = qs.parse(str);
this._querycache = { str: str, val: val };
return val;
});The _querycache prevents re-parsing if the same request object is accessed multiple times.
req.body
Express itself does not add req.body. This property is provided by middleware like body-parser. However, Express's request object reserves the property name and does not interfere with middleware that sets it.
req.cookies
Similarly, req.cookies is not part of Express core. It is provided by the cookie-parser middleware.
req.path
Returns the URL pathname (without query string). Implementation in lib/request.js (lines 75-85):
defineGetter(req, 'path', function path(){
return parseUrl(this).pathname;
});req.hostname
Returns the hostname from the Host header, with support for X-Forwarded-Host when the trust proxy setting is enabled.
From lib/request.js (lines 90-105):
defineGetter(req, 'hostname', function hostname(){
var trust = this.app.get('trust proxy fn');
var host = this.get('X-Forwarded-Host');
if (host && trust) {
host = host.split(',').pop().trim();
} else {
host = this.get('Host');
}
if (!host) return;
var offset = host.indexOf(':');
return offset!== -1? host.substring(0, offset): host;
});req.ip
Returns the remote IP address, respecting the X-Forwarded-For header when trust proxy is enabled.
From lib/request.js (lines 110-120):
defineGetter(req, 'ip', function ip(){
var trust = this.app.get('trust proxy fn');
return proxyaddr(this, trust);
});req.ips
When trust proxy is enabled, returns an array of IP addresses from the X-Forwarded-For header.
req.protocol
Returns the protocol string (http or https), respecting X-Forwarded-Proto when trust proxy is enabled.
req.secure
A boolean shorthand: true if req.protocol === 'https'.
req.subdomains
Returns an array of subdomains from the hostname. For example, api.example.com yields ['api'].
req.accepts(types)
The primary content negotiation method. It checks the Accept request header and returns the best matching type from the provided list. If no match, it returns false.
From lib/request.js (lines 130-145):
req.accepts = function(){
var accept = accepts(this);
return accept.types.apply(accept, arguments);
};Usage examples:
req.accepts('html')returns'html'if the client accepts HTML, elsefalse.req.accepts(['json', 'html'])returns the preferred format.req.accepts('json', 'html')works the same way.
req.acceptsEncodings(encodings)
Negotiates the Accept-Encoding header for compression support.
req.acceptsCharsets(charsets)
Negotiates the Accept-Charset header.
req.acceptsLanguages(langs)
Negotiates the Accept-Language header.
req.get(field)
Returns the value of a specific request header (case-insensitive). Defined in lib/request.js (lines 150-160):
req.get = req.header = function header(name){
var lc = name.toLowerCase();
switch (lc) {
case 'referer':
case 'referrer':
return this.headers.referrer || this.headers.referer;
default:
return this.headers[lc];
}
};req.range(size, options)
Parses the Range header for partial content requests. Returns an array of ranges or undefined.
req.is(type)
Checks the Content-Type header against a given type. Returns the matched type string or false.
Response Object Extensions
Express adds the following methods and properties to the res object. Each is defined in lib/response.js.
res.status(code)
Sets the HTTP response status code. Returns res for chaining.
From lib/response.js (lines 50-60):
res.status = function(code){
this.statusCode = code;
return this;
};res.set(field, value) / res.header(field, value)
Sets a response header. Can also accept an object of key-value pairs.
From lib/response.js (lines 65-80):
res.set = res.header = function header(field, val){
if (arguments.length === 2) {
var value = Array.isArray(val)? val.map(String): String(val);
this.setHeader(field, value);
} else {
for (var key in field) {
this.setHeader(key, field[key]);
}
}
return this;
};res.get(field)
Returns the value of a response header that has already been set.
res.cookie(name, value, options)
Sets a cookie on the response. Options include domain, path, expires, maxAge, secure, httpOnly, signed, and sameSite.
From lib/response.js (lines 90-110):
res.cookie = function(name, value, options){
var opts = options || {};
var secret = this.req.secret;
var signed = opts.signed;
if (signed &&!secret) throw new Error('cookieParser("secret") required for signed cookies');
var val = typeof value === 'object'? 'j:' + JSON.stringify(value): String(value);
if (signed) val = 's:' + sign(val, secret);
if ('maxAge' in opts) opts.expires = new Date(Date.now() + opts.maxAge);
var header = serialize(name, val, opts);
this.set('Set-Cookie', header);
return this;
};res.clearCookie(name, options)
Clears a cookie by setting its expiration to a past date.
res.redirect(url, status)
Redirects the client to a different URL. Default status is 302. Can accept a relative path or a fully qualified URL.
From lib/response.js (lines 120-140):
res.redirect = function(url){
var address = url;
var body;
var status = 302;
if (arguments.length === 2) {
if (typeof arguments[0] === 'number') {
status = arguments[0];
address = arguments[1];
} else {
status = arguments[1];
}
}
this.set('Location', address);
this.status(status).end();
};res.json([body])
Sends a JSON response. Automatically sets the Content-Type header to application/json and serializes the body using JSON.stringify.
From lib/response.js (lines 150-170):
res.json = function(obj){
var val = obj;
if (typeof obj === 'number' && arguments.length === 1) {
val = obj;
obj = null;
}
if (obj === null && typeof val === 'number') {
this.statusCode = val;
val = undefined;
}
var body = stringify(val, this.req.app.get('json replacer'), this.req.app.get('json spaces'));
if (!this.get('Content-Type')) {
this.set('Content-Type', 'application/json');
}
return this.send(body);
};res.jsonp([body])
Same as res.json() but supports JSONP callbacks. If the request includes a callback query parameter, the response wraps the JSON in a function call.
res.send([body])
The universal response method. It determines the appropriate Content-Type based on the body type:
- If body is a string,
Content-Typedefaults totext/html. - If body is a Buffer,
Content-Typedefaults toapplication/octet-stream. - If body is an object or array, it calls
res.json(). - If body is a number, it treats it as the status code and sends the corresponding status message.
From lib/response.js (lines 180-220):
res.send = function(body){
var chunk = body;
var encoding;
var type;
switch (typeof chunk) {
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;
}
if (typeof chunk === 'string') {
encoding = 'utf8';
type = this.get('Content-Type');
if (typeof type === 'string') {
this.set('Content-Encoding', 'utf8');
}
}
this.end(chunk, encoding);
return this;
};res.sendFile(path, options, callback)
Transfers a file from the filesystem. Options include root, headers, dotfiles, lastModified, and acceptRanges. The callback receives an error if the transfer fails.
res.download(path, filename, options, callback)
Same as sendFile but sets the Content-Disposition header to prompt a download. The filename parameter overrides the displayed filename.
res.sendStatus(statusCode)
Sets the status code and sends the corresponding HTTP status message as the body (e.g., 404 sends "Not Found").
res.type(type)
Sets the Content-Type header. Accepts a MIME type string or a file extension (e.g., 'json' becomes 'application/json').
res.format(object)
Performs content negotiation using the Accept request header. The argument is an object mapping content types to handler functions. Express selects the best matching type and calls the corresponding handler.
From lib/response.js (lines 240-270):
res.format = function(obj){
var req = this.req;
var next = req.next;
var fn = obj.default;
if (fn) delete obj.default;
var keys = Object.keys(obj);
var key = keys.length > 0? req.accepts(keys): false;
if (key) {
this.set('Content-Type', type(key));
obj[key](req, this, next);
} else if (fn) {
fn(req, this, next);
} else {
this.status(406).end();
}
return this;
};res.append(field, value)
Appends a value to an existing response header, or creates the header if it doesn't exist.
res.links(links)
Sets the Link header for pagination or related resources. Accepts an object mapping relation types to URLs.
res.locals
An object that holds response-local variables, available to rendered templates. This is the recommended place to store data that views need.
res.render(view, locals, callback)
Renders a view template and sends the HTML response. The view engine must be configured via app.set('view engine',...). The locals parameter merges with res.locals and app.locals.
From lib/response.js (lines 290-310):
res.render = function(view, options, callback){
var app = this.req.app;
var done = callback;
var opts = options || {};
var req = this.req;
var self = this;
if (typeof options === 'function') {
done = options;
opts = {};
}
opts._locals = self.locals;
done = done || function(err, str){
if (err) return req.next(err);
self.send(str);
};
app.render(view, opts, done);
};Public API Summary
Request Properties and Methods
| Symbol | Signature | Purpose |
|---|---|---|
req.params | object | Route parameters parsed from the URL path |
req.query | object | Parsed query string parameters |
req.path | string | URL pathname without query string |
req.hostname | string | Hostname from Host header |
req.ip | string | Remote IP address |
req.ips | string[] | Array of IPs when behind a proxy |
req.protocol | string | http or https |
req.secure | boolean | true if protocol is HTTPS |
req.subdomains | string[] | Subdomain parts of the hostname |
req.accepts(types) | string | false | Content negotiation for media types |
req.acceptsEncodings(encodings) | string | false | Content negotiation for encodings |
req.acceptsCharsets(charsets) | string | false | Content negotiation for charsets |
req.acceptsLanguages(langs) | string | false | Content negotiation for languages |
req.get(field) | string | undefined | Get a request header value |
req.range(size, options) | array | undefined | Parse Range header |
req.is(type) | string | false | Check Content-Type header |
Response Properties and Methods
| Symbol | Signature | Purpose |
|---|---|---|
res.status(code) | this | Set HTTP status code |
res.set(field, value) | this | Set a response header |
res.get(field) | string | Get a response header value |
res.cookie(name, value, options) | this | Set a cookie |
res.clearCookie(name, options) | this | Clear a cookie |
res.redirect(url, status) | void | Redirect the client |
res.json([body]) | this | Send JSON response |
res.jsonp([body]) | this | Send JSONP response |
res.send([body]) | this | Send response of any type |
res.sendFile(path, options, callback) | void | Send a file |
res.download(path, filename, options, callback) | void | Prompt file download |
res.sendStatus(statusCode) | this | Send status code with message |
res.type(type) | this | Set Content-Type |
res.format(object) | this | Content-negotiated response |
res.append(field, value) | this | Append to a header |
res.links(links) | this | Set Link header |
res.locals | object | Local variables for views |
res.render(view, locals, callback) | void | Render a view template |
Interactions with Other Modules
Application Module
The request and response objects are created per-request by the Application module. The app reference is available as req.app and res.app, allowing handlers to access application-level settings like app.get('view engine') or app.get('trust proxy fn').
Routing Module
Route parameters (req.params) are populated by the Routing module. When a route matches, the router extracts named parameters from the URL and stores them in req.params. The req._route property (internal) holds a reference to the matched route, which provides the params() method.
Middleware Module
Middleware can modify both req and res. For example, the express.json() middleware (from the Middleware module) parses JSON bodies and sets req.body. The express.static() middleware uses res.sendFile() internally. The res.format() method is often used in middleware to handle content negotiation for API responses.
View System Module
The res.render() method delegates to the View System module. It calls app.render(), which locates the view template, compiles it using the configured engine, and passes the merged locals (from res.locals, app.locals, and the options parameter).
Configuration and Conventions
trust proxy Setting
Several request properties (req.ip, req.ips, req.protocol, req.hostname) respect the trust proxy application setting. When enabled, Express trusts the X-Forwarded-* headers, which is essential when running behind a reverse proxy like Nginx or a cloud load balancer.
Configure it in the Application module:
app.set('trust proxy', 1); // trust first proxy
app.set('trust proxy', 'loopback'); // trust localhostjson replacer and json spaces Settings
These settings control JSON serialization in res.json() and res.jsonp(). json replacer is a function that transforms values during serialization. json spaces adds indentation for human-readable output.
app.set('json replacer', function(key, value) {
if (key === 'password') return undefined;
return value;
});
app.set('json spaces', 2);case sensitive routing and strict routing
These settings affect how req.path and route matching behave. case sensitive routing (default false) makes route matching case-sensitive when enabled. strict routing (default false) treats trailing slashes as significant.
query parser Setting
Controls how req.query is parsed. Options are 'extended' (uses qs library, default), 'simple' (uses Node's querystring), or a custom function.
etag Setting
Controls whether Express generates ETag headers for responses. Can be true, false, 'strong', or 'weak'.
x-powered-by Setting
When true (default), Express adds an X-Powered-By: Express header to all responses. Disable it with app.set('x-powered-by', false).
Content Negotiation in Detail
Express provides three levels of content negotiation:
-
req.accepts(types)checks theAcceptheader and returns the best match. This is the foundation for all negotiation. -
res.format(object)usesreq.accepts()internally to select a handler. The object keys are MIME types or extensions (e.g.,'json','html','text/plain'). Thedefaultkey provides a fallback handler. -
res.type(type)sets theContent-Typeheader, which influences how the client interprets the response but does not perform negotiation.
The negotiation flow in res.format() works as follows:
- Extract all keys from the object (excluding
default). - Call
req.accepts(keys)to find the best matching type. - If a match is found, set the
Content-Typeheader and call the corresponding handler. - If no match and a
defaulthandler exists, call it. - If no match and no default, send a
406 Not Acceptableresponse.
This pattern is commonly used for REST APIs that support multiple response formats:
res.format({
'text/plain': function(req, res) {
res.send('Hello');
},
'text/html': function(req, res) {
res.render('hello');
},
'application/json': function(req, res) {
res.json({ message: 'Hello' });
},
'default': function(req, res) {
res.status(406).send('Not Acceptable');
}
});