Application, Request and Response

By Syncfusion®, Inc. · Last updated

The app object and its settings, app.locals and sub-apps, everything req holds, every way res can answer, the one-response rule, and caching with ETags, 304 answers and range requests.

What you will learn

  • Create and configure the app object
  • Read params, query, body and headers from req
  • Send text, JSON and status-only responses
  • Avoid double responses with return
  • Use ETags and 304 Not Modified

The Express Application

Application object = the object that express() returns. We always call it app.

  • app holds your routes, middleware and settings.
  • app is also a function. Node.js calls it for every request.
  • app.listen starts a server that sends requests to app.
  • One project usually has one main app.
  • You can mount smaller apps inside it.

Simple lesson: app is the brain of your server. Everything starts from it.

A Real-World Example

The canteen manager keeps a notice board in the kitchen.

  1. "Opening time: 8 AM."
  2. "Dark mode on the menu screen: off."
  3. "School name: Green Valley."
  4. Every cook reads the same board.

app has a notice board too. It is called settings, plus a shared shelf called app.locals.

In this lesson, you will learn:

  1. What the application object holds and how settings work.
  2. The two meanings of app.get, and app.locals vs res.locals.
  3. How NODE_ENV changes error output, and how to mount a sub-app.
  4. What req tells you: URL details, params, query, body and headers.
  5. How res sends content, status codes and headers.
  6. The one-response rule, ETags, 304 answers and ranges.

Application Settings

  • app.set(name, value) = saves a setting.
  • app.get(name) = reads a setting.
  • app.enable(name) = sets it to true.
  • app.disable(name) = sets it to false.
  • app.enabled(name) = asks "is it true?".
  • app.disabled(name) = asks "is it false?".
  • The terminal prints each setting when the server starts.
  • env is development by default.
  • Open Headers. There is no X-Powered-By header, because you disabled it.

Simple lesson: Settings are named values on app. Set them before your routes.

The Two Meanings of app.get

app.get does two different jobs. The number of arguments decides which.

  • app.get(name) with one argument = read a setting.
  • app.get(path, handler) with two or more = add a GET route.
  • app.get("env") returns "development".
  • app.get("/env", handler) answers requests to /env.
  • Paths start with /. Setting names usually do not.

Easy way to remember: a path starts with /. app.get("env") reads, app.get("/env", ...) routes.

Simple lesson: One argument reads a setting. A path and a function make a route.

Sharing Data with app.locals

app.locals = an object for values the whole app shares.

  • Set it once: app.locals.school = "Green Valley".
  • Read it in any route: req.app.locals.school.
  • Views (templates) can read it directly. See Views and Static Files.
  • res.locals is different. It lives for one request only.
  • Use app.locals for things that never change per request, like the school name.
  • Send /about twice. school stays the same.
  • askedAt changes each time, because res.locals is new for every request.

Simple lesson: app.locals is for the whole app. res.locals is for one request.

The Environment Setting

env = a setting that says where the app is running.

  • Express copies it from the NODE_ENV environment variable.
  • If NODE_ENV is missing, env is development.
  • Real servers set NODE_ENV=production.
  • In production, Express hides error details from visitors.
  • In development, Express shows the full error to help you.
  • Send /broken. The visitor only sees "Internal Server Error".
  • The real message still prints in the terminal, for you.
  • Change .env to NODE_ENV=development and run again. Now the visitor sees the message too.

Always set NODE_ENV=production on real servers. Error details can leak passwords and file paths.

Simple lesson: env changes how much Express tells visitors when things break.

Useful Built-In Settings

  • x-powered-by: adds X-Powered-By: Express. Turn it off to hide what you use.
  • json spaces: pretty-prints JSON, such as app.set("json spaces", 2).
  • case sensitive routing: makes /Menu and /menu different.
  • strict routing: makes /menu and /menu/ different.
  • query parser: "simple" by default in Express 5. "extended" allows nested objects.
  • trust proxy: for apps behind a proxy. See Redirects, Content Negotiation and Proxies.
  • view engine and views: for HTML templates. See Views and Static Files.
  • With curl or a browser, the JSON now comes on many lines. The lab panel always formats JSON, so look at Content-Length under Headers: it grew.
  • /Menu gives 404, because routing is now case sensitive.
  • Remove the enable line and run again. /Menu works.

Simple lesson: A few settings change how every route behaves.

Mounting a Sub-Application

Sub-application = a second express() app used inside the main one.

  • Create it like any app: const admin = express().
  • Mount it with app.use("/admin", admin).
  • Its routes now live under /admin.
  • admin.mountpath = the path it was mounted on.
  • The mount event fires when it is mounted.
  • The sub-app keeps its own settings.
  • The terminal prints the mount message before the server starts.
  • admin.get("/") answers /admin, because of the mount path.
  • Most projects use Routing and Routers for this instead. It is lighter.

A sub-app is a full Express app with its own settings. If you only need to group routes, a router is lighter. See Routing and Routers.

Simple lesson: app.use(path, otherApp) plugs one app into another.

Request and Response Objects

Every route handler gets two objects: req and res.

  • req = the request object. It holds everything the client sent.
  • res = the response object. It has tools to send your answer.
  • Express builds both from the Node.js request and response.
  • Express adds many helpers on top, such as req.query and res.json.
  • You read from req. You write with res.

Simple lesson: req is the question. res is how you answer.

A Second Example

A canteen order slip arrives at the kitchen.

  1. The slip says who ordered, what they want and how many.
  2. The cook reads the slip.
  3. The cook fills a plate and adds a label.
  4. The plate goes back to the counter.

The slip is req. The plate and its label are res.

Request URL Information

  • req.method = the HTTP method, such as GET.
  • req.url = the path and query, as seen by the current router.
  • req.originalUrl = the full original path and query. It never changes.
  • req.baseUrl = the path where the current router is mounted.
  • req.path = the path without the query string.
  • req.route = the route that matched, including its path pattern.
  • originalUrl keeps the full text: /canteen/menu/7?veg=true.
  • baseUrl is /canteen, where the router is mounted.
  • url and path are what the router itself sees.

Easy way to remember: path for routing, originalUrl for logs, baseUrl for where the router lives.

Simple lesson: Use req.path to know where. Use req.originalUrl for logs.

Request Data

  • req.params = values from the path, such as :id. See Routing and Routers.
  • req.query = values after ?, such as ?veg=true.
  • req.body = data sent in the body. It needs a body parser. See Body Parsers and Third-Party Middleware.
  • req.headers = every header, with lowercase names.
  • req.get(name) = one header, in any letter case.
  • req.header(name) = the same as req.get. It is just another name.
  • req.cookies = cookies, only after you add cookie-parser. See Cookies and Sessions.
  • Every value from the URL is a string. "7" is not the number 7.
  • req.get ignores letter case. X-Student-Id and x-student-id are the same.
  • In Express 5, req.body is undefined when no body was parsed.

Treat everything in req as untrusted. Any client can send any params, query, body or headers. Check values before you use them.

Simple lesson: params come from the path, query from after the ?, body from the message.

Request Connection Information

  • req.protocol = http or https.
  • req.secure = true when the protocol is https.
  • req.hostname = the host name without the port, such as localhost.
  • req.ip = the client's IP address.
  • req.ips = a list of IP addresses when the app is behind a proxy.
  • req.subdomains = parts before the main domain, such as ["menu"] for menu.school.in.
  • req.xhr = true when the X-Requested-With header is XMLHttpRequest.

Request Type Checks

  • req.is(type) = checks the Content-Type of the body.
  • req.is("json") returns "json" when the body is JSON, and false otherwise.
  • req.fresh and req.stale = whether the client's cached copy is still good.
  • req.range() = reads the Range header, used for partial downloads.
  • ip is ::1, the IPv6 address of your own computer.
  • Behind a proxy, these values need the trust proxy setting.

Simple lesson: req also knows how the client connected.

Sending Content with res

  • res.send(body) = sends text, HTML, an object or an array.
  • res.json(data) = always sends JSON.
  • res.end() = ends the response with no Express helpers. Rarely needed.
  • res.sendStatus(code) = sends a status code and its standard text.
  • res.jsonp(data) = old-style JSON for script tags. Rarely used today.
  • res.sendStatus(204) sends "No Content" with an empty body.
  • res.sendStatus(404) would send the text "Not Found".
  • res.end() sends nothing at all, with status 200.

Simple lesson: Use res.json for data and res.send for text and HTML.

Status and Headers

  • res.status(code) = sets the status. Chain it: res.status(201).json(order).
  • res.set(name, value) = sets a header. res.header is the same.
  • res.append(name, value) = adds another value to a header.
  • res.get(name) = reads a header you already set.
  • res.type(type) = sets Content-Type from a short name like "json".
  • res.vary(header) = tells caches the answer depends on that header.
  • res.links(links) = sets the Link header, used for page links.
  • Open Headers to see each one.
  • X-Note holds both values: fresh, hot.
  • Set headers before you send. After sending, it is too late.

Simple lesson: Status and headers first, then the body.

Response Locals

res.locals = an object that lives for one request only.

  • Middleware can put values in it.
  • Later middleware and the route handler can read them.
  • Views can read them too. See Views and Static Files.
  • It is the safe place for per-request data, such as the logged-in user.

Simple lesson: Pass data down the chain with res.locals.

The One-Response Rule

Each request gets exactly one response.

  • Send twice, and Node.js throws ERR_HTTP_HEADERS_SENT.
  • This usually happens when code keeps running after an early res.send.
  • Fix it with return res.send(...).
  • res.headersSent = true once the response has started.
  • Send /greet with no name. The client gets 400, but the terminal shows an error.
  • Add return before res.status(400) and run again.
  • Now the error is gone, because the function stops early.

Easy way to remember: return res.send(...) whenever there is code below it.

Simple lesson: One request, one response. Use return to stop early.

Caching and Partial Requests

Browsers try not to download the same thing twice.

  • ETag = a short fingerprint of a response body. Express adds one to res.send answers.
  • The browser keeps the copy and sends If-None-Match with that ETag next time.
  • req.fresh = true when the client's copy still matches. req.stale is the opposite.
  • When the copy is fresh, res.send answers 304 Not Modified with no body. That saves data.
  • Range = a header asking for only part of a file, such as bytes=0-99. Video players use it.
  • req.range(size) reads it. It returns a list of { start, end }, or -1 when the range cannot be served.
  • Send /menu, open Headers and copy the ETag value.
  • Type it into the request headers as If-None-Match: <the ETag> and send again. You get 304 and an empty body.
  • The range request gets 206 Partial Content with only the first 20 characters.
  • A range past the end gets 416 Range Not Satisfiable.

Simple lesson: ETags let browsers reuse copies. Ranges let them download just a part.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
app.get("env") to make a routeIt adds a route called envOne argument reads a settingNo route is added
Setting json spaces after routes runOrder does not matterSet settings before routesSome routes miss the setting
Storing per-user data in app.localsIt is per requestIt is shared by everyoneUsers see each other's data
No NODE_ENV on the real serverExpress guesses productionIt defaults to developmentVisitors see error details
Leaving x-powered-by onIt is harmlessIt tells attackers what you useEasier targeted attacks
req.params.id + 1Params are numbersParams are strings"7" + 1 gives "71"
Reading req.body without a parserExpress parses bodies by itselfAdd express.json() firstreq.body is undefined
Sending without returnSending stops the functionThe function keeps runningERR_HTTP_HEADERS_SENT
Setting a header after sendingHeaders can change any timeHeaders go out firstError, or the header is lost
Trusting req.query.roleClients send only honest dataAnyone can type any URLUsers give themselves admin rights
req.headers["X-Id"]Header names keep their casereq.headers uses lowercase namesundefined. Use req.get("X-Id")

Check Yourself

Exercises

What to Remember

  • express() returns the application object, app.
  • app.set, app.get(name), app.enable and app.disable manage settings.
  • app.get(name) reads a setting. app.get(path, handler) adds a route.
  • app.locals is shared by the whole app. res.locals is per request.
  • NODE_ENV=production hides error details from visitors.
  • req holds the method, URL, params, query, body, headers and connection details.
  • res.send, res.json and res.sendStatus send the answer.
  • res.status and res.set must come before the body.
  • Send exactly one response, and return when you stop early.

Next: Routing and Routers. You will learn route paths, parameters, query strings and the Express 5 path syntax.