JSON REST API

Query Projections

Query projections are SQL-backed, output-only query fields provided by the optional QueryProjectionsPlugin. They are different from normal computed fields.

If you want to build your own plugin on the same query-field seam, see Writing Plugins.

Use query projection fields when a derived value must behave like a real list field.

Typical cases:

Do not use query projection fields for normal response decoration. Regular computed fields are simpler and remain the right default for that.

Setup

import { RestApiPlugin, QueryProjectionsPlugin } from 'json-rest-api'

await api.use(RestApiPlugin, { simplifiedApi: false, simplifiedTransport: false })
await api.use(QueryProjectionsPlugin)

Defining a query field

await api.addResource('authors', {
  schema: {
    id: { type: 'id' },
    first_name: { type: 'string', required: true },
    last_name: { type: 'string', required: true }
  },

  queryFields: {
    full_name: {
      type: 'string',
      sortable: true,
      select: ({ knex, column }) => knex.raw(
        "trim(coalesce(??, '') || ' ' || coalesce(??, ''))",
        [column('first_name'), column('last_name')]
      )
    }
  },

  sortableFields: ['id', 'first_name', 'last_name'],
  defaultSort: ['full_name']
})

select() is called at query time. It can return:

The callback receives:

{
  knex,      // current knex/db handle
  db,        // same as knex
  context,   // request context
  scopeName,
  tableName,
  fieldName,
  schemaInfo,
  adapter,   // storage adapter
  column,    // helper: logical field -> qualified storage column
  ref        // helper: logical field -> knex ref
}

Behavior

Query projection fields are:

They are not stored columns, and they are not part of write validation.

Sparse fieldsets

Query projection fields can be requested explicitly:

const result = await api.resources.authors.get({
  id: '1',
  queryParams: {
    fields: {
      authors: 'first_name,full_name'
    }
  }
})

If a query field is needed only for sorting, it is still selected internally, but it is removed from the response unless the sparse fieldset asked for it.

Sorting and cursor pagination

Query projection fields are useful when a derived value must drive list behavior:

const result = await api.resources.authors.query({
  queryParams: {
    sort: ['full_name'],
    page: { size: 20 }
  }
})

json-rest-api automatically appends id as a stable tie-breaker for cursor ordering. That prevents duplicate or skipped rows when multiple records share the same projected value.

Included resources

Query projection fields can be selected for included resources too:

const result = await api.resources.books.get({
  id: '1',
  queryParams: {
    include: ['author'],
    fields: {
      books: 'title',
      authors: 'full_name'
    }
  }
})

Write behavior

Query projection fields are output-only. If a client sends them in POST, PUT, or PATCH, they are ignored.

That keeps the boundary clean:

Limits

Projected fields are an advanced query feature. They are intentionally backend-sensitive.

Important limits:

For display-only derived values, prefer normal computed fields from Field Transformations.