Skip to main content

namespacing

You can also use namespacing to reflect folder and URI structures at once. The following example would require the respective users controller to be located at app/controllers/Api/V1/UsersController.ts:

import { PsychicRouter } from '@rvoh/psychic'

export default (r: PsychicRouter) => {
r.namespace('api', (r) => {
r.namespace('v1', (r) => {
r.namespace('users', (r) => {
r.get('hi')
})
})
})
}

GET api/v1/users/hi Api/V1/Users#hi

Namespacing will always enforce the folder structure when presented with multiple route segments, which means you are required to put controllers in the expected folder structure in order for an action to be called when its corresponding route is hit. This can, of course, be overridden by passing an explicit controller to the namespace call:

r.namespace('api', (r) => {
r.namespace('v1', (r) => {
// providing an explicit controller to the namespace
r.namespace('users', { controller: HelloWorldController }, (r) => {
r.get('hi')

// you can even provide nested koa middleware
r.get('my-middleware', async (ctx, next) => {
console.log('hello from GET:/api/v1/users/my-middleware')
await next()
})
})
})
})

GET api/v1/users/hi HelloWorld#hi

Controller overrides

Additionally, controller overrides can be passed to the child routes:

r.namespace('api', (r) => {
r.namespace('v1', (r) => {
r.namespace('users', (r) => {
// providing an explicit controller to the
// individual child route of a namespace
r.get('hi', HelloWorldController, 'hi')
})
})
})

GET api/v1/users/hi HelloWorld#hi

Keeping an auth-context directory out of the URL

Controller directories don't only exist to name a URL segment — a directory branch is also how Psychic expresses its authentication architecture (see controller overview — authentication architecture). Sometimes the two disagree: a controller needs to live under a directory like Visitor/ so it inherits the right auth base controller, but Visitor isn't a segment that should appear in the URL.

Rather than using r.namespace('visitor', ...) — which would enforce both the folder and the URL segment — pass the controller class explicitly to resources/resource/a verb method, inside a namespace that only covers the URL segments you actually want:

// BAD — "visitor" leaks into the URL: /v1/visitor/places
r.namespace('v1', r => {
r.namespace('visitor', r => {
r.resources('places', { only: ['index', 'show'] })
})
})

// GOOD — clean URL: /v1/places; controller comes from the auth-context directory
import VisitorV1PlacesController from '@controllers/Visitor/V1/PlacesController.js'

r.namespace('v1', r => {
r.resources('places', { only: ['index', 'show'], controller: VisitorV1PlacesController })
})

The controller's directory (Visitor/V1/) still enforces the auth inheritance chain; only the URL changes. Treat the URL namespace, the controller file namespace, and the auth inheritance chain as three independent concerns — a versioned URL doesn't require a matching controller ancestry, and an auth-context directory doesn't require a matching URL segment.

scope

In addition to namespace, Psychic also provides the scope method, which behaves identically to namespace, except that it does not enforce that particular segment within the path of the controller. In the following example, the UsersController will be expected to be located at app/controllers/UsersController.ts:

r.scope('api', (r) => {
r.scope('v1', (r) => {
r.scope('users', (r) => {
r.get('hi')
})
})
})

GET api/v1/users/hi Users#hi