RepoFold

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:

  1. Parse and expose request data (parameters, query strings, body, headers, cookies) through convenient properties.
  2. Simplify response construction (JSON, HTML, file downloads, redirects) with dedicated methods.
  3. Automate content negotiation so the server responds with the format the client prefers.
  4. Provide lifecycle hooks (like req.accepts() and res.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 the req object at runtime.
  • Response augmentation occurs in lib/response.js. Similarly, its properties are merged into the res object.
  • The merge happens lazily via __proto__ assignment in lib/application.js around line 50-60, ensuring that every request object inherits Express's methods without modifying the global http.IncomingMessage prototype.
  • Middleware can further augment req and res (e.g., body-parser adds req.body, cookie-parser adds req.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, else false.
  • 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-Type defaults to text/html.
  • If body is a Buffer, Content-Type defaults to application/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.

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

SymbolSignaturePurpose
req.paramsobjectRoute parameters parsed from the URL path
req.queryobjectParsed query string parameters
req.pathstringURL pathname without query string
req.hostnamestringHostname from Host header
req.ipstringRemote IP address
req.ipsstring[]Array of IPs when behind a proxy
req.protocolstringhttp or https
req.securebooleantrue if protocol is HTTPS
req.subdomainsstring[]Subdomain parts of the hostname
req.accepts(types)string | falseContent negotiation for media types
req.acceptsEncodings(encodings)string | falseContent negotiation for encodings
req.acceptsCharsets(charsets)string | falseContent negotiation for charsets
req.acceptsLanguages(langs)string | falseContent negotiation for languages
req.get(field)string | undefinedGet a request header value
req.range(size, options)array | undefinedParse Range header
req.is(type)string | falseCheck Content-Type header

Response Properties and Methods

SymbolSignaturePurpose
res.status(code)thisSet HTTP status code
res.set(field, value)thisSet a response header
res.get(field)stringGet a response header value
res.cookie(name, value, options)thisSet a cookie
res.clearCookie(name, options)thisClear a cookie
res.redirect(url, status)voidRedirect the client
res.json([body])thisSend JSON response
res.jsonp([body])thisSend JSONP response
res.send([body])thisSend response of any type
res.sendFile(path, options, callback)voidSend a file
res.download(path, filename, options, callback)voidPrompt file download
res.sendStatus(statusCode)thisSend status code with message
res.type(type)thisSet Content-Type
res.format(object)thisContent-negotiated response
res.append(field, value)thisAppend to a header
res.links(links)thisSet Link header
res.localsobjectLocal variables for views
res.render(view, locals, callback)voidRender 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 localhost

json 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:

  1. req.accepts(types) checks the Accept header and returns the best match. This is the foundation for all negotiation.

  2. res.format(object) uses req.accepts() internally to select a handler. The object keys are MIME types or extensions (e.g., 'json', 'html', 'text/plain'). The default key provides a fallback handler.

  3. res.type(type) sets the Content-Type header, which influences how the client interprets the response but does not perform negotiation.

The negotiation flow in res.format() works as follows:

  1. Extract all keys from the object (excluding default).
  2. Call req.accepts(keys) to find the best matching type.
  3. If a match is found, set the Content-Type header and call the corresponding handler.
  4. If no match and a default handler exists, call it.
  5. If no match and no default, send a 406 Not Acceptable response.

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');
 }
});
Generated from commit ae6dd37