params
Requests to your server will often contain either a request body, path params, or query params. Psychic controllers provide special helper methods for interacting with these params. They enable the developer to both keep DRY, as well as providing implicit param validation.
castParam
The castParam method provides the developer with a unique way to both absorb and validate a specific param simultaneously. In the following example, the show
action will find any places belonging to the currentHost with the id provided in the params. Psychic is param-location ambiguous, meaning that it will search the path, query, and body for a param with the key id
, and will then cast it to a string
type. However, if the param passed is not a valid string, Psychic will raise a 400
status code error.
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 a Place',
})
public async show() {
const place = await this.place()
this.ok(place)
}
private async place() {
return await this.currentHost
.associationQuery('places')
.findOrFail(this.castParam('id', 'string'))
}
}
Psychic supports many different data types for validation. Here are a few others that can be used:
export default class V1HostPlacesController extends V1HostBaseController {
public async show() {
const uuid = this.castParam('id', 'uuid')
const int = this.castParam('id', 'integer')
const bigint = this.castParam('id', 'bigint')
const date = this.castParam('id', 'date')
const datetime = this.castParam('id', 'datetime')
const number = this.castParam('id', 'number')
const string = this.castParam('id', 'string')
const enum = this.castParam('id', 'string', { enum: ['basic', 'premium']})
}
}
In addition to these primitive types, Psychic also supports array variants:
export default class V1HostPlacesController extends V1HostBaseController {
public async show() {
const uuids = this.castParam('id', 'uuid[]')
const ints = this.castParam('id', 'integer[]')
const bigints = this.castParam('id', 'bigint[]')
const dates = this.castParam('id', 'date[]')
const datetimes = this.castParam('id', 'datetime[]')
const numbers = this.castParam('id', 'number[]')
const strings = this.castParam('id', 'string[]')
const enums = this.castParam('id', 'string[]', {
enum: ['basic', 'premium'],
})
}
}
paramsFor
In addition to individual param casting, Psychic also provides the ability to automatically ingest all params for a given Dream model. This enables developers to easily bring in params for a model without having to specify their types manually:
export default class V1HostPlacesController extends V1HostBaseController {
@OpenAPI(Place, {
status: 201,
tags: openApiTags,
description: 'Create a Place',
})
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)
}
}
This will automatically bring in all params for the Place model, barring generated fields like id
, createdAt
, updatedAt
, deletedAt
, or any belongs-to foreign keys or foreign key types.
The attributes that can be updated via params can be further restricted by providing a paramSafeColumns
getter in any Dream model:
export default class User extends ApplicationModel {
public get paramSafeColumns() {
return ['email'] as const
}
}
Note that paramSafeColumns
cannot be used to allow setting of generated columns (id
, createdAt
, updatedAt
, deletedAt
, or any belongs-to foreign keys or foreign key types) via params.
The paramsFor method also accepts the ability to customize which params should be allowed, enabling you to only bring in specific params without needing to cast their types:
export default class V1HostPlacesController extends V1HostBaseController {
public async create() {
let place = await this.currentHost.createAssociation(
'places',
this.paramsFor(Place, { only: ['name', 'description'] })
)
if (place.isPersisted) place = await place.loadFor('default').execute()
this.created(place)
}
}
Additionally, the including
option can be provided to enable columns to flow through that would otherwise be blocked, such as foreign keys, primary keys, timestamps, etc...
export default class V1HostPlacesController extends V1HostBaseController {
public async create() {
let place = await this.currentHost.createAssociation(
'places',
this.paramsFor(Place, {
only: ['name', 'description'],
including: ['hostId'],
})
)
if (place.isPersisted) place = await place.loadFor('default').execute()
this.created(place)
}
}