Routers
Learn about routers and how to implement them
Defining a Router
Routers are the main entry point to a Blueprint application. The router consists of paths (i.e., relative urls) that clients uses to make request against the application. The paths are then bound to controller actions to create routes. Routers are essential application entities. All routers are defined in app/routers
.
You define a router by extending the Router
class with a router specification, and exporting the extended class from its router module. Below is an example router named message
with an empty specification.
Router Paths
The paths of a router are the relative urls that provide an entry point to the application. If you think of an application as a building, then the paths represent the entryways for the building. Each entryway is in a different location and provides access to a different part of the building.
Paths are defined as keys on the specification
property of the router, and begin with a forward slash (/
). The example below updates our message
router with the single path /messages
.
Now, we have an entryway into our application. We, however, do not know what action we need to perform when a clients wants to use this path.
Reactions to Paths
If you are familiar with HTTP requests, each path in the request requires a HTTP verb, such as GET
, POST
, PUT
, and DELETE
. The verb notifies the server (i.e., the application in our case) of what action to execute when a client sends a request to the corresponding path. In our current specification, we have defined a path, but we have not defined what HTTP verb on the path is active, and what action the HTTP verb executes. We call this a reaction.
A reaction is when you define the HTTP verb, and the action its causes.
Let's update our message
router to support creating messages.
Now, our message router has defined its first route POST /messages
. When a client sends this HTTP request to the application, it will execute the create
action on the message controller
. We will visit how to implement this action later in the guide. For now, let's focus on the route definition in the message
router.
As shown in the message
router, the key for nested objects of a path is a HTTP verb. In this example, the HTTP verb is post
.
A Blueprint router supports the HTTP verbs defined in the jshttp module.
Actions
The value (or reaction) of the HTTP verb in the router definition can be a controller action. This is signified by the action
property in the hash associated with the corresponding HTTP verb. In our example above, the controller action is message@create
. This means that POST /messages
is going to invoke the create
action on the message
controller.
There is no one-to-one mapping of paths to controller actions. For example, it is possible to have different paths from the same router, or a different router, bind to the same controller action. This reason for doing so is because either path may have different policies for invoking the action.
Binding to default actions
Some controller may have a single default action named __invoke()
. When binding a path to the default action of a controller, you do not need to provide the action name. Instead, just specify the controller name in the action
property. The following example illustrates binding the path to the default action for the message
controller.
Static Views
A view is a document that captures a reusable representation of a response to a request that can be rendered on demand. An example of a view is an HTML document. Similar to actions, you can specify that a path is bound to a static view. Just use the view
property instead of the action
property for the corresponding route.
Now, the GET /messages
route will use the messages
view to display the messages to the users.
Dynamic Routes
Up until this point, we have been defining static routes. A static route is a route that has a path with no variable parts. A dynamic route therefore is a route that has variable parts. For example, /messages
is a static route. But, /messages/1
and messages/2
are dynamic routes. This is because the part of the path after /messages
is can change depending what context the client is hoping to access.
We define dynamic routes by including a parameter in the path. A parameter begins with a colon (:
). For example, :messageId
is a parameter.
Let's define a route for getting a single message:
In the example above, /messages/:messageId
is dynamic route. Likewise, the :messageId
parameter will be accessible on req.params
as req.params.messageId
.
Nested Routes
You may have noticed that when we defined the dynamic route for getting a single message, we created a route under /messages
. This is call a nested routed. Nested routes is Blueprint's method for allowing you to extend an existing route with a child route, and reduce problems related to defining related routes. The dynamic route from above is the same as this one.
The main difference between the definition above, and the following one:
is we are inheriting the /messages
definition. This means that allow properties, such as policies and Express middleware, of the /messages
route will also apply to the /messages/:messageId
route.
Using Directories
As your Blueprint application grows, you will find that defining all your routes in a single router will not scale to your needs. This will even be the case with nested routes in single router. To assist with this problem, Blueprint allows you to use directories to define nested routes.
For example, let's assume you are working on v1 of your Blueprint application, and the application has 3 different routers. As part of your design, you want all routes to have a /v1
prefix. The simple approach is to just nest all paths in a router under /v1
. This suffices, but it also means you have to do the same for each routers. Likewise, changing the name of the prefix means you have to update each router definition.
An easier, and better, solution would be to not nest all the paths in each router under /v1
, but place all routers under the v1/
directory. For example:
Now, all routes in the message
, comment
, and like
router will be prefixed with /v1
. For example, /v1/messages
and /v1/messages/:messageId
are valid routes.
Using directories for nested routes is a easy way to implement versioned routes (e.g., /v1
vs /v2
).
Middleware
Express middleware are methods that provide domain-specific functionality to an Express application. For example, you have middleware the logs all the requests to a database, or middleware the authenticates access to a given route. Since Blueprint is a framework built atop Express, it is possible to use Express middleware in a Blueprint application.
Blueprint has built-in middleware for logging, parsing requests, cookies, and validating request input because their load order is important.
You use the use
keyword to add middleware to a route. For example, here we are adding the CORS middleware to our route.
The use
property takes an Express middleware function with the signature function (req, res, next)
, or an array of Express middleware functions.
Mounting External Routers
One feature you will learn when working with Blueprint modules is you can define routers inside a module for reuse across different applications. The benefit of this feature is the Blueprint module can provide a public access point for how the module can be used by a client. Routers defined in a Blueprint module, however, are not loaded by default. We do this because we want to allow developers to control what routers (and paths) are exposed by the containing application.
This means that developers need a method for defining what routers (and paths) from a Blueprint module are available via the application. We call this process mounting.
To mount a router, you define the path and use the mount()
method. Here is an example of mounting a router to the /images
path.
The example above will mount the images
router from the blueprint-images-cdn
Blueprint module. If you do not provide a module name (i.e., only use images
), then the router is assumed to be part of the application.
Last updated