v0.21

routing

introduction

routes are defined in the routes directory. dframework automatically discovers and loads every .js file in that directory at boot time without any manual imports. inside each route file you have access to the global Route facade which proxies directly to the internal router instance.

basic routes

the simplest route accepts a url path and a controller string pointing to the method that should handle it.

1// routes/web.js
2import { Route } from 'dframework';
3
4Route.get('/dashboard', 'app.IndexController@dashboard');
5Route.post('/register', 'auth.AuthController@register');
6Route.put('/profile', 'app.UserProfileController@update');
7Route.delete('/session', 'auth.AuthController@logout');

available methods

the router supports four http verbs.

1Route.get(path, handler);
2Route.post(path, handler);
3Route.put(path, handler);
4Route.delete(path, handler);

route parameters

dynamic url segments are prefixed with a colon. the router extracts and decodes these values and makes them available on req.params inside your controller.

1Route.get('/user/:id', 'app.IndexController@user');
2Route.get('/admin/:entity/:id/edit', 'admin.AdminController@editForm');
1// controllers/app/IndexController.js
2export default class IndexController {
3 async user(req) {
4 const user = await User.find(req.params.id);
5 return json({ user });
6 }
7}

named routes

you give a route a name by chaining .name() after its definition. named routes allow you to generate urls programmatically without hardcoding paths.

1Route.get('/dashboard', 'app.IndexController@dashboard').name('dashboard');
2Route.get('/user/:id', 'app.IndexController@user').name('app.user.profile');

to generate a url from a named route you use the global route() helper.

1const dashboardUrl = route('dashboard');
2const profileUrl = route('app.user.profile', { id: 42 });

route groups

groups allow you to share attributes like prefixes and middleware across a set of routes without repeating yourself on every definition.

prefix

1Route.group({ prefix: '/faq' }, (faq) => {
2 faq.get('/', 'app.IndexController@faq').name('app.faq');
3 faq.get('/tos', 'app.IndexController@faq_tos').name('app.faq.tos');
4 faq.get('/privacy', 'app.IndexController@faq_privacy').name('app.faq.privacy');
5});

middleware

1Route.group({ middleware: ['AuthMiddleware@requireAuth'] }, (auth) => {
2 auth.get('/home', 'app.HomeController@home').name('app.home');
3 auth.get('/library', 'app.IndexController@library').name('app.library');
4});

combined attributes

prefix and middleware can be used together in a single group definition.

1Route.group({ prefix: '/admin', middleware: ['AuthMiddleware@requireAuth', 'AdminMiddleware@requireAdmin'] }, (admin) => {
2 admin.get('/', 'admin.AdminController@index').name('admin.index');
3 admin.get('/:entity', 'admin.AdminController@list').name('admin.list');
4 admin.post('/:entity', 'admin.AdminController@store').name('admin.store');
5});

groups can be nested. an inner group inherits all attributes of its parent and may add its own on top.

1Route.group({ middleware: ['AuthMiddleware@requireAuth'] }, (auth) => {
2 auth.group({ prefix: '/api/preferences' }, (prefs) => {
3 prefs.get('/', 'app.PreferencesController@index').name('api.preferences.index');
4 prefs.put('/:key', 'app.PreferencesController@update').name('api.preferences.update');
5 });
6});

middleware ordering

when a route sits inside one or more groups, the runtime middleware stack is outer group middleware, inner group middleware, route level middleware, controller. group middleware is prepended to the route's own handler chain at registration time, in declaration order, so nested groups run their parents' middleware before their own.

1Route.group({ middleware: [a] }, (g1) => {
2 g1.group({ middleware: [b] }, (g2) => {
3 g2.middleware(c).get('/path', 'Ctrl@index');
4 });
5});
6// runtime stack for GET /path: [a, b, c, Ctrl@index]

middleware that returns a response without calling next() (such as the built in RateLimiter rejecting a request) short circuits the rest of the chain: the controller never runs and any later middleware never executes. this matters specifically when both a group and a route inside it apply a RateLimiter. the two limiters do not coordinate: each tracks its own window and its own count, whichever limit trips first wins, and reusing the same RateLimiter instance across both layers doubles the per request increment (so the effective limit becomes max / 2). see the layered rate limiters section in the security docs for the full interaction model.

domain and port routing

dframework supports domain separated and port separated route registration. you can constrain routes to specific hosts subdomains or ports using either group attributes or chainable builder methods.

domain routing

to restrict routes to a specific host or subdomain pass the domain option to Route.group() or call Route.domain().

1Route.group({ domain: 'admin.example.com' }, (admin) => {
2 admin.get('/dashboard', 'admin.AdminController@index').name('admin.dashboard');
3});
4
5Route.domain('api.example.com', (api) => {
6 api.get('/v1/users', 'api.UserController@index').name('api.users');
7});

port routing

you can also constrain routes to a specific listening port using the port option or Route.port(). port matching checks the host header or local server socket port.

1Route.group({ port: 8080 }, (metrics) => {
2 metrics.get('/health', 'app.HealthController@check');
3});
4
5Route.port(9090, (internal) => {
6 internal.get('/metrics', 'app.MetricsController@export');
7});

chainable builders

.domain(), .port(), .basicAuth(), and .middleware() are fully chainable builders and can be composed in any order before registering endpoints or groups.

1Route.domain('shop.example.com').get('/cart', 'shop.CartController@index');
2
3Route.basicAuth('admin', 'secret')
4 .port(8080)
5 .get('/metrics', 'app.MetricsController@export');
6
7Route.domain('secure.example.com')
8 .port(8443)
9 .middleware('AuthMiddleware@requireAuth')
10 .get('/data', 'app.DataController@show');
11
12Route.domain('admin.example.com')
13 .port(8080)
14 .group({ prefix: '/v1' }, (v1) => {
15 v1.get('/users', 'admin.UserController@index');
16 });

parameterized domains

domain definitions support dynamic parameter placeholders using either :param or {param} syntax. extracted domain parameters are automatically made available on req.params alongside path parameters.

1Route.domain(':subdomain.example.com', (tenant) => {
2 tenant.get('/users/:id', 'app.TenantController@showUser');
3});
4
5Route.domain('{tenant}.myapp.com', (app) => {
6 app.get('/settings', 'app.SettingsController@show');
7});
1// GET http://acme.example.com/users/42
2// req.params.subdomain === 'acme'
3// req.params.id === '42'

overlapping route names across domains

WARNING

route names registered with `.name()` reside in a global lookup table. if two routes on different domains share the exact same name, the later definition overwrites the earlier entry. always prefix route names with the target domain or section name (e.g. `admin.dashboard` and `app.dashboard`).

1Route.domain('admin.example.com').get('/dashboard', 'AdminController@dash').name('admin.dashboard');
2Route.domain('app.example.com').get('/dashboard', 'AppController@dash').name('app.dashboard');
3
4// route('admin.dashboard') // '//admin.example.com/dashboard'
5// route('app.dashboard') // '//app.example.com/dashboard'

when generating a url for a domain bound route using route(name, params), the framework automatically returns a protocol relative absolute url (such as //admin.example.com/dashboard), filling in any domain parameters from the supplied params object.

inline middleware chains

instead of a single controller string you can pass an array as the handler. the framework treats all entries before the last as middleware and the final entry as the controller.

1pair.post('/approve', ['AuthMiddleware@requireAuth', 'auth.AuthController@approvePost']).name('approve.post');

you can also apply middleware to a single route without a group by calling .middleware().

1Route.middleware(authLimiter).post('/register', 'auth.AuthController@register');

route modifiers

every route definition returns a chainable object that exposes several modifiers to control framework behavior on that specific route.

csrf

csrf verification is enabled by default on all post, put, and delete routes. you can disable it for a specific route by chaining .csrf(false).

1Route.post('/magic/:token', 'auth.AuthController@magicConsume').csrf(false);

shield

when the shield security system is active it validates incoming mutation requests. you can opt a route out by chaining .shield(false).

1Route.post('/magic/:token', 'auth.AuthController@magicConsume').shield(false);

log

request and response logging is automatic in the local environment. you can force logging for a specific route in production by chaining .log().

1Route.post('/api/playback/track', 'app.PlaybackController@get_track').name('api.playback.track').log();

views

the .views() modifier tells the route compiler exactly which view templates this route renders. during the production build the compiler analyzes those templates to determine what session data locale detection and body parsing the compiled handler actually needs. providing explicit view names is an optimization hint that removes ambiguity from the static analysis step.

1Route.get('/dashboard', 'app.IndexController@dashboard').name('dashboard').views(['app.home.index']);

profile

the .profile() modifier lets you override the behavior profile the compiler derives from its static analysis of the route. you pass an object whose keys replace the analyzed values. this is an advanced escape hatch for routes where the automatic analysis produces an incorrect or suboptimal compiled handler.

the available profile keys are:

1Route.get('/feed', 'app.FeedController@index').profile({ needsSession: true, needsFlash: true });

basic auth

you can protect a specific route with http basic authentication by chaining .basicAuth(). it accepts explicit credentials, a custom validator callback, or defaults to values configured in config/auth.js (auth.basic).

1Route.get('/secret', 'app.SecretController@show').basicAuth('admin', 'secret');
2Route.get('/admin', 'app.AdminController@index').basicAuth();

controller string syntax

controller strings follow the directory.ControllerName@method convention. the directory segment maps to a subdirectory inside controllers/. a controller at controllers/app/IndexController.js is referenced as app.IndexController@index. a controller at controllers/auth/AuthController.js is referenced as auth.AuthController@register.

1Route.get('/dashboard', 'app.IndexController@dashboard');
2Route.post('/register', 'auth.AuthController@register');

for controllers sitting directly in the controllers/ root with no subdirectory you omit the directory prefix entirely.

1Route.get('/locale/:locale', 'LocaleController@set');

multiple route files

you may split your routes across as many files as you like inside the routes directory. the framework loads all .js files it finds there automatically. the order of loading follows the filesystem sort order.

1// routes/web.js (http routes)
2// routes/wire.js (socket routes)