The standard way to register routes in Grind is via a Routes provider. The routes provider is located at
Under the hood, Grind’s router is based on Express’s router, however it provides a different interace with some additional functionality. Don’t fear though — this doesn’t come at a performance cost. All Grind route’s immediately turn into Express routes at boot time, think of it as syntactic sugar.
Grind’s router is accessible via
The router supports
DELETE routes. All five have the same method signature:
If you’re coming from Express, note that Grind’s routing methods are available via
app.routes, not directly through
The first parameter,
path, is the routable path.
app.routes.get('/home', …) will be called when you go to
http://host/home — unless you’re nested in a prefixed group (more on that below).
The second parameter,
action, is what tells the router what to do when the route is called.
This can be as simple as a callback function:
Or it can pass a full object with middleware:
Above, we register middleware through the route action, however we can also add middleware to the route after it’s been created:
For more information of middleware, check out the full Middleware guide.
The third and final parameter,
context, is an optional context object that is set on the route itself (accessible via
route.context). By default this does nothing, but it provides a way to send additional context to other Grind providers. For instance, Grind’s Swagger provider leverages the
context param to build out rich documentation around your routes.
Route parameters provide a way for you to build dynamic routes without needing to define every possible route ahead of time, for instance:
This allows for the
:id segment of the URL to be any value. The value of route parameters are available via
In the previous example
:id is a required parameter, but routing also supports optional parameters:
Now the route action will be called for both
So far, in all examples, any arbitrary values can be passed into the routes as parameters. This isn’t ideal if all you’re looking for is a numeric
id, as it forces you to check the data and handle when it’s incorrect.
You can define parameter patterns ahead of time to restrict what data is passed in:
Now this user profile route will only be resolved when
:id is a number, if any non-numeric data is passed, it won’t be captured by this route, and if no other route supports it, it will result in a 404.
Currently Grind requires you define a pattern before you use it in a route. In the future this should allow for patterns to be defined at any point, or even only on the route itself. Pull requests welcome!
You can also bind parameters to a function to transform the value, before your action is called. In the
/users/:id route above, your action would need to go out, find the user and handle a scenario where the user isn’t found. This isn’t ideal as you end up with cluttered, repetitive code.
Using parameter bindings, we can simplify this:
Now the actions of the user routes can safely assume it will have the user object, as they will never be called if the user parameter doesn’t bind.
Once you’ve defined a route, you can also name them for convenient referencing when generating URLs:
Now that your route is named, you can use it to generate URLs via Grind’s URL generator:
Log// Profile URL:
The URL generator also supports passing in a keyed object, which you can use to build a query string:
Log// Profile URL:
Grind’s router also supports groups. Groups allow you to provide a common prefix for a batch of routes, as well as a common controller (more below).
By grouping routes with common prefixes together, you’re able to end up with DRY code that’s much easier to read.
Putting it all together, we can build rich controller routes with everything above:
If the a route group’s controller is set to a class it will create an instance via
new UserController(app). If your controller constructor needs additional parameters, you can also pass in a controller instance directly:
const users = appapproutes
Grind uses a single controller instance for all requests to that controller. If you’re coming from a framework that uses a new controller instance for each request, it’s important to remember how this impacts your code. You can’t assume that state set on a controller will only apply to the current request context.