← Node & Express

Express Fundamentals

What information do the `req` and `res` objects carry in Express, and what are the most common response methods?

Show answer — try answering out loud first

In each handler (req, res), req (request) represents what the client sends and res (response) is what you return to it. From req you read method, url, params, query, body, and headers. With res you respond using methods like res.json() (sends JSON), res.send() (sends text/HTML/JSON), res.status() (sets the HTTP code), and res.end() (closes without a body). res.status() returns the same res, so you can chain: res.status(201).json(...).

The req object contains everything that arrived from the client:

  • req.method: the HTTP verb, "GET", "POST", etc.
  • req.url: the requested URL, including the query string.
  • req.params: the route params from the URL (/usuarios/:idreq.params.id).
  • req.query: what comes after the ? (?page=2req.query.page).
  • req.body: the body sent (JSON from a POST/PUT). It needs express.json().
  • req.headers: the headers (req.headers["content-type"], etc.).

The res object is used to build the response. Key methods:

  • res.json(obj): sends obj as JSON and sets the correct Content-Type.
  • res.send(algo): sends text, HTML, or even an object (converting it to JSON).
  • res.status(codigo): sets the status code (200, 201, 404, 500...). It doesn't send anything on its own, it only prepares the code; it returns res for chaining.
  • res.end(): ends the response without a body (useful for a 204 No Content).

Golden rule: for each request you must respond exactly once. Sending two responses (for example a res.json and then another one) throws an error.

const express = require("express");
const app = express();

// Required to be able to read req.body in JSON format
app.use(express.json());

// Shows the info that req carries
app.get("/inspeccionar/:id", (req, res) => {
  // We read several pieces of the request
  const info = {
    metodo: req.method,            // "GET"
    url: req.url,                  // "/inspeccionar/7?debug=true"
    parametro: req.params.id,      // "7" (from the :id route)
    query: req.query,              // { debug: "true" } (after the ?)
    userAgent: req.headers["user-agent"], // header sent by the client
  };

  // res.json sends the object as JSON with status 200 by default
  res.json(info);
});

// Chaining example: set status and send JSON in one line
app.post("/usuarios", (req, res) => {
  const nuevo = { id: 1, nombre: req.body.nombre };

  // status(201) prepares the code and returns res; json sends the body
  res.status(201).json(nuevo);
});

// res.send with plain text
app.get("/saludo", (req, res) => {
  res.send("Hola mundo"); // Content-Type: text/html automatically
});

// res.status + res.end: respond without a body (204 = no content)
app.delete("/usuarios/:id", (req, res) => {
  // ...here you would delete the user...
  res.status(204).end();
});

app.listen(3000, () => {
  console.log("Servidor en http://localhost:3000");
});

Testing with curl:

# We pass a route param (7) and a query (?debug=true)
curl "http://localhost:3000/inspeccionar/7?debug=true"

# POST with a JSON body; you should get 201 and the created user
curl -X POST http://localhost:3000/usuarios \
  -H "Content-Type: application/json" \
  -d '{"nombre":"Ana"}'

Believing that res.status(404) on its own sends the response. It doesn't: res.status() only sets the code and returns res; you have to chain a method that does send a body, like res.status(404).json({ error: "No encontrado" }) or res.status(404).end(). If you only call res.status(404), the request stays hanging because it was never closed. Another mistake is responding twice (res.json(...) and then another res.send(...)), which throws "Cannot set headers after they are sent".

"In each handler I have req and res. req is what the client sends: from there I read method, url, params for the route segments, query for what comes after the ?, body for the body, and headers for the headers. res is what I return: I use res.json to send JSON, res.send for text or HTML, res.status to set the code, and res.end to close without a body. Something key: res.status doesn't send anything on its own, it only sets the code and returns res, which is why I chain res.status(201).json(...). And I always respond exactly once per request, because sending two responses throws the headers-already-sent error."

Quick challenge

What problem does this handler have and how do you fix it?

app.get("/usuarios/:id", (req, res) => {
  const usuario = buscarUsuario(req.params.id);
  if (!usuario) {
    res.status(404);
  }
  res.json(usuario);
});
See answer

res.status(404) doesn't send a response, it only sets the code; the flow continues and reaches res.json(usuario), which will send undefined as JSON... with status 200, not 404. The condition didn't stop anything.

It's fixed by sending the body inside the if and halting the function with return:

app.get("/usuarios/:id", (req, res) => {
  const usuario = buscarUsuario(req.params.id);
  if (!usuario) {
    return res.status(404).json({ error: "Usuario no encontrado" });
  }
  res.json(usuario);
});

The return prevents the res.json below from running and from trying to respond twice.