On this page

Routing

An App selects handlers using an HTTP method and the decoded URL path. Registrations run in order. Call req.next() to continue to the next matching handler; returning without it stops dispatch. See basic routing and middleware.

The snippets below are partial examples for the hello world sketch: declare handlers and routers at file scope and put registrations in setup() before server.begin().

Route methods

Use get, head, post, put, patch, del, and options for the corresponding methods. There is no app.all() method: use app.use() for all supported methods. A registration without a path matches every path for that method. HEAD requires an explicit head() route and suppresses payload output.

void readIndex(Request& req, Response& res) {
  res.print("GET request to the homepage");
}

void createItem(Request& req, Response& res) {
  res.status(201);
  res.print("Created an item");
}

void headIndex(Request& req, Response& res) {
  res.set("Content-Type", "text/plain");
  res.set("Content-Length", "27");
  res.status(200);
}
// Add inside setup().
app.get("/", readIndex);
app.post("/", createItem);
app.head("/", headIndex);

Route paths

Paths are case sensitive. Query strings do not participate in route matching: /about?view=short matches /about. req.path() returns a percent-decoded path; req.query() returns the raw query without ?. A literal + remains + in the path. See request data for decoding.

Callback registrations use a complete route pattern. For example, app.use("/secret", handler) matches /secret and /secret/, but does not match /secret/page. app.use(handler) has no path restriction. A single trailing slash on the request is accepted for a pattern without one: /about matches /about/. A pattern ending in / requires that slash. The root patterns "" and "/" also match the root of a mounted router.

Patterns support :name parameters for path segments. They do not implement Express regular expressions, wildcards, or optional segments.

Route parameters

For /users/34/books/8989, this partial handler extracts both named segments. The capacity includes the NUL terminator; always check req.route() before using the result.

void usersBook(Request& req, Response& res) {
  char user[10];
  char book[10];
  if (!req.route("userId", user, sizeof(user)) ||
      !req.route("bookId", book, sizeof(book))) {
    res.sendStatus(400);
    return;
  }
  res.print("User: ");
  res.println(user);
  res.print("Book: ");
  res.println(book);
}
// Add inside setup().
app.get("/users/:userId/books/:bookId", usersBook);

Route values come from the decoded path. Encoded slashes become segment separators during decoding; they cannot hide a slash inside a parameter.

Router

Router groups registrations. Mount a router with app.use("/prefix", &router). Mount matching uses a literal prefix at a path segment boundary, rather than a callback route pattern. /cats matches /cats, /cats/, and /cats/long, but not /catsup. Trailing slashes on a mount are ignored. Mounts do not capture :name parameters.

Router cats;
Router details;

void catIndex(Request& req, Response& res) {
  res.print("Try /cats/long or /cats/details/nyan");
}

void longCat(Request& req, Response& res) {
  res.set("Content-Type", "text/html");
  res.print("<p>looooong cat is long!</p>");
}

void catDetails(Request& req, Response& res) {
  char name[32];
  if (!req.route("name", name, sizeof(name))) {
    res.sendStatus(400);
    return;
  }
  res.print("Details for ");
  res.print(name);
}
// Add inside setup().
cats.get("/", catIndex);
cats.get("/long", longCat);
details.get("/:name", catDetails);
cats.use("/details", &details);
app.use("/cats", &cats);

Nested mounts accumulate prefixes: catDetails handles /cats/details/nyan. req.path() still exposes the complete decoded path; req.route() uses the pattern inside the currently executing router. A mounted handler that calls req.next() allows matching registrations after the mount to run too.

Apps and routers cannot be copied. They own their registration nodes but borrow mounted routers and registered path strings. Keep routers and path strings alive while requests may use them; file-scope declarations and string literals satisfy that requirement.

Member callbacks and lambdas

On platforms with automatic std::function support, including ESP32, aWOT accepts member callbacks and capturing lambdas. This partial example belongs at file scope, with its registration in setup():

class Counter {
 public:
  void handle(Request& req, Response& res) {
    res.print(++count);
  }
 private:
  unsigned long count = 0;
};

Counter counter;
// Add inside setup() on a std::function build.
app.get("/count", &counter, &Counter::handle);

Keep the instance alive as long as its registration. Function-pointer builds accept ordinary functions and noncapturing lambdas. See configuration before changing callback macros.