Course:Node.js & Express/
Lesson

You deploy an AI-generated APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. and your frontend error tracking shows zero errors. Looks great, until you realize the API returns 200 OK with { "error": "User not found" } for missing users. Your error tracker only counts non-200 responses. The API has been silently failing for weeks.

Reading the request

The request object is where all incoming data lives. Express parses URLs and headers automatically, but body parsing requires middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it..

Route parameters

// URL: GET /users/42/posts/7
app.get('/users/:userId/posts/:postId', (req, res) => {
  console.log(req.params);
  // { userId: "42", postId: "7" }
});

Remember: all params are strings. Always convert them when your database expects numbers.

const userId = parseInt(req.params.userId, 10);
if (isNaN(userId)) {
  return res.status(400).json({ error: 'Invalid user ID' });
}

Query strings

// URL: GET /products?category=electronics&minPrice=100&inStock=true
app.get('/products', (req, res) => {
  console.log(req.query);
  // { category: "electronics", minPrice: "100", inStock: "true" }
});

Everything in req.query is a string too. "true" is not the boolean true, and "100" is not the number 100.

AI pitfall
AI writes if (req.query.inStock) to check a boolean query parameter. But req.query.inStock is the string "false", and the string "false" is truthy in JavaScript. So if ("false") evaluates to true. AI almost never handles boolean query parameters correctly.

Request body

The body is only available if you register express.json() middleware first.

app.use(express.json());

app.post('/users', (req, res) => {
  const { name, email, age } = req.body;
  // Only populated if Content-Type is application/json
});
SourceHow to accessTypeWhen to use
URL pathreq.params.idAlways a stringIdentify a specific resource
Query stringreq.query.sortAlways a stringFiltering, sorting, pagination
Bodyreq.body.nameParsed JSON (any type)Creating or updating resources
Headersreq.headers['content-type']Always a stringAuthentication tokens, content type

Validating request data

AI-generated handlers almost never validate input. They destructure req.body and pass values straight to the database.

// AI-generated (no validation)
app.post('/users', (req, res) => {
  const { name, email } = req.body;
  db.createUser(name, email); // name could be undefined, email could be 5000 chars
  res.json({ success: true });
});

// What it should look like
app.post('/users', (req, res) => {
  const { name, email } = req.body;

  if (!name || typeof name !== 'string' || name.trim().length === 0) {
    return res.status(400).json({ error: 'Name is required' });
  }
  if (!email || !email.includes('@')) {
    return res.status(400).json({ error: 'Valid email is required' });
  }

  db.createUser(name.trim(), email.toLowerCase());
  res.json({ success: true });
});
02

Sending responses

Express gives you several methods to send data back to the client.

// Send JSON (most common for APIs)
res.json({ user: { id: 1, name: 'Alice' } });

// Send with a specific status code
res.status(201).json({ user: { id: 1, name: 'Alice' } });

// Send plain text
res.send('Hello world');

// Send nothing (useful for DELETE)
res.status(204).send();

// Redirect
res.redirect('/new-location');
MethodWhat it doesContent-Type
res.json(data)Sends JSON, sets Content-Type automaticallyapplication/json
res.send(data)Sends string, Buffer, or objectVaries
res.status(code)Sets HTTP status code (chainable)N/A
res.redirect(url)Sends 302 redirectN/A
res.sendStatus(code)Sets status and sends status text as bodytext/plain

Response headers

// Set a custom header
res.set('X-Request-Id', '12345');

// Set cache control
res.set('Cache-Control', 'public, max-age=3600');

// Multiple headers at once
res.set({
  'X-Request-Id': '12345',
  'X-RateLimit-Remaining': '99'
});
03

HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. status codes

Status codes are a three-digit number that tells the client what happened. They are grouped into families.

FamilyRangeMeaningExample
2xx200-299SuccessRequest completed as expected
3xx300-399RedirectionResource moved, follow the new URL
4xx400-499Client errorThe request is wrong (bad input, unauthorized)
5xx500-599Server errorThe server failed (bug, database down)

Status codes you will use constantly

CodeNameWhen to useExample
200OKSuccessful GET or PUTReturn a list of users
201CreatedSuccessful POST that created a resourceNew user created
204No ContentSuccessful DELETE (nothing to return)User deleted
400Bad RequestClient sent invalid dataMissing required field
401UnauthorizedMissing or invalid authenticationNo token provided
403ForbiddenAuthenticated but not permittedRegular user accessing admin route
404Not FoundResource does not existGET /users/999 when user 999 is not in the database
409ConflictRequest conflicts with current stateTrying to create a user with an email that already exists
422Unprocessable EntityValid format but semantically wrongAge is -5
500Internal Server ErrorUnhandled bug in your codeDatabase query throws an exception

The AI status codeWhat is status code?A three-digit number in an HTTP response that tells the client what happened: 200 means success, 404 means not found, 500 means the server broke. problem

AI-generated APIs overwhelmingly default to status 200 for everything. This is not hypothetical, it is the single most consistent pattern in AI-generated Express code.

// AI-generated - returns 200 for errors
app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);
  if (!user) {
    res.json({ error: 'User not found' }); // Status 200!
  }
  res.json(user);
});

// Correct - uses proper status codes
app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }
  res.json(user);
});
AI pitfall
AI returns res.json({ error: '...' }) without setting a status code. The default is 200. This means your monitoring tools report 100% success, your frontend catch blocks never fire (they check for non-2xx responses), and errors go completely unnoticed in production.

Why wrong status codes break things

Status codes are not just for humans reading logs. Automated systems depend on them.

SystemWhat it does with status codes
Frontend fetch()Only rejects on network errors, not 4xx/5xx, but response.ok is false for non-2xx, so client-side error handling relies on correct codes
Browser caching200 responses can be cached; 500 responses are not, returning 200 for errors caches error responses
Load balancers5xx responses trigger health checks and routing changes
Monitoring (Datadog, etc.)Dashboards count 4xx and 5xx as errors, 200 with error body is invisible
API consumersOther services parse status codes to decide retry logic, retry on 503, do not retry on 400
04

Response patterns for CRUDWhat is crud?Create, Read, Update, Delete - the four basic operations almost every application performs on data. operations

Here is what a complete CRUD APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. should return for each operation.

// CREATE - 201 with the created resource
app.post('/books', (req, res) => {
  const book = db.createBook(req.body);
  res.status(201).json(book);
});

// READ (single) - 200 or 404
app.get('/books/:id', (req, res) => {
  const book = db.findBook(req.params.id);
  if (!book) return res.status(404).json({ error: 'Book not found' });
  res.json(book);
});

// READ (list) - 200 with array (empty array is fine, not 404)
app.get('/books', (req, res) => {
  const books = db.findBooks(req.query);
  res.json(books); // [] is a valid, successful response
});

// UPDATE - 200 with updated resource
app.put('/books/:id', (req, res) => {
  const book = db.updateBook(req.params.id, req.body);
  if (!book) return res.status(404).json({ error: 'Book not found' });
  res.json(book);
});

// DELETE - 204 with no body
app.delete('/books/:id', (req, res) => {
  const deleted = db.deleteBook(req.params.id);
  if (!deleted) return res.status(404).json({ error: 'Book not found' });
  res.status(204).send();
});
AI pitfall
AI often returns 200 for successful POST requests instead of 201. It also returns 200 with the deleted object for DELETE instead of 204 with no body. These are not catastrophic, but they confuse API consumers and violate the conventions that every REST client expects.