Routing

Routing #

Like in Express or Koa, you can create RESTful routes for your application with a few short lines of code. The following is a list of all the methods you can use to create routes:

/* Create a DELETE route. */
router.delete(String path, Handler handler);
/* Create a GET route. */
router.get(String path, Handler handler);
/* Create a PATCH route. */
router.patch(String path, Handler handler);
/* Create a POST route. */
router.post(String path, Handler handler);
/* Create a PUT route. */
router.put(String path, Handler handler);

Each route must have a path and at least one handler.

The path can be an exact URL pattern (e.g. /about) or use a trailing wildcard character to indicate “a path that begins with…” For example, the path /users/* will match /users/{any/sub/path/}. This path type is more commonly used with Interceptor Routes (explained below).

The handler contains the logic for how to consume a request and generate a response. A route can have more than one handler. Each handler will be executed in the order they were added.

class IndexHander implements Handler {
    public void handle(HttpServerRequest request, HttpServerResponse response) {
        /* Handler logic. */
    }
}

router.get("/", new IndexHandler())

The Handler interface is a functional interface with a single method that accepts an instance of HttpServerRequest and HttpServerResponse . With that in mind, the above code can be rewritten using the more concise lambda syntax:

/* For a more Express-like experience */
router.get("/", (req, res) -> { ... })

In the example above, we created a route that will respond to an HTTP GET on the / path.

The Express style is the format that will be used throughout the documentation.

Path parameters #

You can store parameters in your route paths using the Express path parameter notation.

app.get("/users/:id/show", (req, res) -> {
    Long id = Long.parseLong(req.pathParams["id"]) // Get the path parameter and parse it to a Long.
    // Do stuff with the id
})

Interceptors #

Interceptors allow you to… well, intercept, requests at a given path. Once intercepted, a request can be examined and have business logic applied to it. A popular use case for interceptors is for implementing authorization.

Interceptors can be created using the following methods:

/* Create an interceptor at a specific path. */
router.before(String path, Handler handler);
/* Create an interceptor for a specific path pattern defined using regex string. */
router.beforeRegex(String pattern, Handler handler);

Below is an example of a typical authorization interceptor:

router.before("/private/*", (req, res) -> {
    // Check if user is authorized
    if(!userHasPermissions()) {
        res.unauthorized().send("You do not have permission.");
    } else req.next() // You *must* call req.next() if you wish to pass the request to the next handler.
});

In the above example, any requests going to /private/{any/sub/path/} will be checked for user authorization. If the check fails, a 401 UNAUTHORIZED response is returned. Otherwise, the request is passed to the next route handler that matches the /private/* pattern.

Fundamentally, interceptors are regular routes, but they do not match based on HTTP method, only on path or regex patterns. This means that you can effectively create an interceptor that only matches GET requests to a particular path by doing:

router.get("/*", (req, res) -> {
    Logger.trace("We received a GET request.")
    req.next()
})

When placed correctly, the app will log all GET requests.

Important: Be sure to call next() on the request object to pass the request onto the next request handler.

Passing data between Interceptors #

Interceptors can also be used to pass data to other request handlers further down the chain.

Let’s say we want to ensure that all subsequent handlers for /users/{any/sub/path/} have access to a user object. We can use request.put() and request.get() to store and retrieve data from the request state like so:

router.before("/users/*", (req, res) -> {
    req.put("user", database.findUserById(1)).next(); // Set the data then call next().
})
router.get("/users/profile/", (req, res) -> {
    User user = req.get("user"); // Get the data from the Request's state.
    res.ok().sendJson(user);
})

When using Groovy, the request object implements the required methods to make use of the subscript operator which allows you to access objects on the request state the same way you would a JavaScript object.

/* Instead of */
User user = req.get("user");

/* You can do */
User user = req["user"];

Just another design consideration to ease the transition from JavaScript.

Endpoint interceptors #

If you want to apply an interceptor to a specific route, you can use the route.handler() method to add multiple handlers to a particular route. The first handler you add should serve as an interceptor.

Let’s say we want to check if a user has permissions to access a specific route. We can use the route.handler() method to add an interceptor that checks the role of the logged-in user:

router.get("/admin")
    .handler(checkRole("admin"))
    .hanlder((req, res) -> { res.ok().send("Welcome to the admin section.") })

Route order #

Routes are appended to the application router in the order they were declared. With this in mind, it is a good practice to declare routes with more generalized paths last.

router.get("/*", (req, res) -> res.ok().send("I block everybody 😈"))
router.get("/unreachable", { /* Cannot be reached by any GET requests. */ });

In the above example, the first route will match any GET requests being sent to this application, but because of it’s generalized path, other GET routes further down the chain will become unreachable. It would be more logical to declare this route last.

Organizing routes with Route groups #

Instead of declaring all your routes in the main method of your application, you can create “route groups” and mount each group onto the main application router.

To use a route group, create a class that implements the Consumer<Router> interface:

/* Create a route group for Users*/
class UserRoutes implements Consumer<Router> {
    public void accept(Router router) {
        router.get("/", (req, res) -> ... )
        router.get("/:id/show", (req, res) -> ... )
    }
}

To use this group, use the app.path() method:

router.path('/users/*', UserRoutes)

The routes within the UserRoutes group will be accessible at:

  • /users/
  • /users/:id/show
Thinking of it another way, route groups are like controllers.

Router middleware #

Diego wraps many of the core Vert.x Web routing components to provide a higher-level abstraction. Through certain utility methods you will be able to leverage any existing Vert.x handlers such as the CORSHandler if you need a specific functionality:

router.use(CORSHandler.create('someotherdomain\\.com').allowedHttpMethods(HttpMethod.GET))

This can also be achieved by using the router.path(String, io.vertx.core.Handler<RoutingContext>) method.

This is only left in for compatibility purposes at the moment. This method may be removed in the future as the framework becomes more feature complete.

Next: Serving static assets.