Express 24,430 gives you one registration function per HTTP method, named in lowercase: app.get, app.post, app.put, app.patch, app.delete, app.head, app.options, and the rarer ones down to app.mkcol. The list comes from Node's own http.METHODS array. app.all registers a handler for every method at that path, which is what you want for a health check or an authorization gate.
Method and path are matched independently. A request for a path you registered, using a method you did not, does not get a 405 — it finds no matching layer and falls through to the 404.
import express from 'express';
const app = express();
app.use(express.json());
const books = [{ id: 1, title: 'Eloquent JavaScript' }];
app.get('/books', (req, res) => res.json(books));
app.post('/books', (req, res) => {
const book = { id: books.length + 1, title: req.body.title };
books.push(book);
res.status(201).location(`/books/${book.id}`).json(book);
});
app.all('/health', (req, res) => res.json({ ok: true, method: req.method }));
app.listen(3000);$ curl -i -X POST localhost:3000/books -H "Content-Type: application/json" \
-d '{"title":"Node.js Design Patterns"}'
HTTP/1.1 201 Created
Location: /books/2
$ curl -X DELETE localhost:3000/health -> {"ok":true,"method":"DELETE"}
$ curl -i -X PATCH localhost:3000/books -> HTTP/1.1 404 Not Found
$ curl -i -X OPTIONS localhost:3000/books -> Allow: GET, HEAD, POSTHEAD and OPTIONS are the exceptions. Express answers a HEAD request from the matching GET route, running the handler normally and then discarding the body while keeping the headers — including the Content-Length your res.json computed. Unmatched OPTIONS requests get an automatic Allow header listing the methods registered at that path, which is why the last line above reports GET, HEAD, POST for a route file that only declares two. You almost never write app.head or app.options yourself.
A plain string path matches the whole path, exactly, after the query string is stripped. Dots and hyphens are literal, so app.get('/random.text', ...) matches /random.text and nothing else. Trailing slashes are ignored by default and matching is case-insensitive; Mounting and 404s shows how to change both.