Phestus v0.1 is just weeks away from being released! Explore the latest modules and documentation.Learn more

Data Management

Data

Phestus provides a data service for interacting with application records.

The data service is exposed through:

context.service.data

It provides a consistent API for querying and mutating data without requiring modules to communicate directly with a specific database, CMS, ORM, or storage provider.

The primary operations are:

find
findById
count
create
update
delete

Finding Data

Records can be queried using find.

const users = await context.service.data.find(
  'users',
)

A query can optionally be provided:

const users = await context.service.data.find(
  'users',
  {
    limit: 10,
    offset: 0,
  },
)

The result is a FindResult:

interface FindResult<T> {
  docs: T[];
  totalDocs: number;
  limit: number;
  offset: number;
  hasNextPage: boolean;
  hasPrevPage: boolean;
}

This provides both the returned records and pagination information.

Finding a Record by ID

When a specific record is needed, findById can be used.

const user = await context.service.data.findById(
  'users',
  userId,
)

The result is either the requested record or null when no matching record exists.

Counting Records

The count method returns the number of records matching an optional query.

const total = await context.service.data.count(
  'users',
)

A query can also be provided:

const total = await context.service.data.count(
  'users',
  {
    where: {
      fields: {
        status: {
          equals: 'active',
        },
      },
    },
  },
)

Queries

Queries are represented by PhestusQuery.

interface PhestusQuery {
  where?: PhestusWhere;
  sort?: string | string[];
  limit?: number;
  offset?: number;
  select?: string[];
}

Queries can therefore control:

  • Filtering
  • Sorting
  • Pagination
  • Selected fields

Filtering

Filtering is performed through where.

Field conditions are defined through fields.

const users = await context.service.data.find(
  'users',
  {
    where: {
      fields: {
        email: {
          equals: 'john@example.com',
        },
      },
    },
  },
)

Multiple fields can be queried at the same time:

const users = await context.service.data.find(
  'users',
  {
    where: {
      fields: {
        status: {
          equals: 'active',
        },
        age: {
          greaterThanOrEqual: 18,
        },
      },
    },
  },
)

Query Operators

Phestus defines the following query operators:

equals
notEquals
contains
startsWith
endsWith
greaterThan
greaterThanOrEqual
lessThan
lessThanOrEqual
in
notIn
exists

For example:

const users = await context.service.data.find(
  'users',
  {
    where: {
      fields: {
        name: {
          startsWith: 'John',
        },
      },
    },
  },
)

Values can be strings, numbers, booleans, dates, null, or arrays of supported query values.

Combining Conditions

Queries can combine conditions using and, or, and not.

For example:

const users = await context.service.data.find(
  'users',
  {
    where: {
      and: [
        {
          fields: {
            status: {
              equals: 'active',
            },
          },
        },
        {
          fields: {
            age: {
              greaterThanOrEqual: 18,
            },
          },
        },
      ],
    },
  },
)

An or condition can be used when any of several conditions should match:

const users = await context.service.data.find(
  'users',
  {
    where: {
      or: [
        {
          fields: {
            role: {
              equals: 'admin',
            },
          },
        },
        {
          fields: {
            role: {
              equals: 'moderator',
            },
          },
        },
      ],
    },
  },
)

A condition can also be negated using not.

Sorting

Records can be sorted using sort.

const users = await context.service.data.find(
  'users',
  {
    sort: 'createdAt',
  },
)

Multiple sort values can be provided:

const users = await context.service.data.find(
  'users',
  {
    sort: ['createdAt', 'name'],
  },
)

The interpretation of sort values is handled by the service implementation.

Pagination

Pagination is controlled through limit and offset.

const users = await context.service.data.find(
  'users',
  {
    limit: 20,
    offset: 40,
  },
)

The returned FindResult contains the pagination state:

{
  docs,
  totalDocs,
  limit,
  offset,
  hasNextPage,
  hasPrevPage,
}

Selecting Fields

Specific fields can be requested using select.

const users = await context.service.data.find(
  'users',
  {
    select: [
      'name',
      'email',
    ],
  },
)

The service implementation determines how field selection is translated to the underlying provider.

Creating Data

Records are created with create.

const user = await context.service.data.create(
  'users',
  {
    name: 'John Doe',
    email: 'john@example.com',
  },
)

The returned value is the created record.

Updating Data

Existing records can be updated with update.

const user = await context.service.data.update(
  'users',
  userId,
  {
    name: 'Jane Doe',
  },
)

The collection and record ID identify the record, while the final argument contains the data to update.

Deleting Data

Records can be deleted with delete.

const user = await context.service.data.delete(
  'users',
  userId,
)

The deleted record is returned by the service.

Data and Schemas

Data management and schema management represent two different responsibilities within the service layer.

Schema management defines what the data looks like:

Schema
  ↓
Fields
  ↓
Types
  ↓
Validation
  ↓
Relationships

Data management operates on records conforming to that schema:

Data
  ↓
Find
  ↓
Create
  ↓
Update
  ↓
Delete

Together, they provide the foundation for provider-independent data management in Phestus.

Service Abstraction

The data service does not require modules to know how records are stored.

The architecture is:

Module
  ↓
PhestusService
  ↓
DataService
  ↓
Service Adapter
  ↓
Data Provider

A module can therefore use:

await context.service.data.find(
  'users',
)

without knowing whether the underlying implementation uses Payload, PostgreSQL, another database, or another data source.

The service adapter is responsible for translating Phestus queries and data operations into the implementation-specific API.

This keeps modules focused on application capabilities while the service layer manages the details of data storage and retrieval.