Skip to main content

Overview

In a standard MVC (Model, View, Controller) paradigm, a controller represents the entity which responds to an HTTP request. Controllers in Psychic are classes which inherit from the base PsychicController class, which can be imported from Psychic. They will inherit many useful methods for responding to requests, as well as many useful helpers for setting and reading cookies, accessing request parameters, and much more.

In order for a controller's methods to be reached, a corresponding route entry must be established to point to those methods, so the controllers and routes work hand-in-hand. You should never have a route that points to a controller method that doesn't exist, nor should you ever have a controller method with no corresponding route entry.

Connecting routes to your controllers

// conf/routes.ts
export default (r: PsychicRouter) => {
r.get('helloworld', WelcomeController, 'helloWorld')
}

// controllers/WelcomeController.ts
export default class WelcomeController extends ApplicationController {
public async helloWorld() {
this.ok('howyadoin')
}
}

Ordinarily, controllers will be driven by resourceful patterns tied to underlying models. In these cases, we highly recommend you see our generating resources guides, since this will automatically compose sensible default functionality and testing for your controller, as well as the underlying model.

yarn psy g:resource --owning-model=Host v1/host/places Place name:citext style:enum:place_styles:cottage,cabin,lean_to,treehouse,tent,cave,dump sleeps:integer deleted_at:datetime:optional

This will also produce route entries for the new Places controller, using the r.resources('places') call, which will automatically provide sensible routes for indexing, showing, creating, updating, and deleting a place.

Status code responses

Psychic leverages method names which correspond to http status codes to make rendering data a little more human for us.

export default class V1HostPlacesController extends V1HostBaseController {
public async create() {
let place = await this.currentHost.createAssociation(
'places',
this.paramsFor(Place)
)
if (place.isPersisted) place = await place.loadFor('default').execute()
this.created(place)
}

public async update() {
const place = await this.place()
await place.update(this.paramsFor(Place))
this.noContent()
}
}

Learn more about how status codes work in psychic controllers by visiting our status code guides.

OPENAPI

Psychic Controllers contains powerful bindings to openapi through the usage of the @OpenAPI decorator. This enables you to automatically generate openapi documents based on the shape of your models. The below example will auto-generate an openapi document with an array of serialized Places as the response body for this endpoint.

import { OpenAPI } from '@rvoh/psychic'
import Place from '../../../models/Place.js'
import V1HostBaseController from './BaseController.js'

const openApiTags = ['places']

export default class V1HostPlacesController extends V1HostBaseController {
@OpenAPI(Place, {
status: 200,
tags: openApiTags,
description: 'Fetch multiple Places',
many: true,
serializerKey: 'summary',
})
public async index() {
const places = await this.currentHost.associationQuery('places').all()
this.ok(places)
}
}

The OpenAPI decorator is incredibly robust in terms of capabilities, and we recommend diving deep on how to leverage it to empower yourself to write less. See our Openapi guides to learn more.