Skip to main content

REST JSON API

@xond/api generates RESTful JSON APIs for all your Prisma models. This guide explains how to call these APIs, organized by operation type.

Base URL

All JSON API endpoints follow this pattern:

GET|POST|PUT|DELETE /json/:resource
GET|PUT|DELETE /json/:resource/:id

Where :resource is the camelCase name of your model (e.g., user, product, orderItem).

Authentication

All endpoints require JWT authentication. Include the JWT token in your request:

For GET Requests

Include the JWT token in the Authorization header:

GET /json/user
Authorization: Bearer <your-jwt-token>

For POST/PUT Requests

Include the JWT token in the request body as jwtCookie:

{
"jwtCookie": {
"id": "user-uuid",
"email": "user@example.com",
"role": "admin"
},
"name": "John Doe",
"email": "john@example.com"
}

The jwtCookie object contains user information extracted from the JWT token by your authentication middleware.

Response Format

All API responses follow this format:

{
"success": boolean,
"data": any | null,
"pagination": {
"totalRecords": number,
"totalPages": number,
"currentPage": number,
"pageSize": number
} | null,
"error": string | null
}

Success Response

{
"success": true,
"data": [...],
"pagination": {
"totalRecords": 100,
"totalPages": 10,
"currentPage": 1,
"pageSize": 10
},
"error": null
}

Error Response

{
"success": false,
"data": null,
"pagination": null,
"error": "An error occurred while fetching data."
}

Create

Create new records using POST requests.

Endpoint

POST /json/:resource

Request Body

Include jwtCookie and the data to create:

{
"jwtCookie": {
"id": "user-uuid",
"email": "user@example.com"
},
"name": "John Doe",
"email": "john@example.com",
"organizationId": "00000000-0000-0000-0000-000000000002"
}

Response

{
"success": true,
"data": {
"userId": "00000000-0000-0000-0000-000000000003",
"name": "John Doe",
"email": "john@example.com",
"createdAt": "2024-01-01T00:00:00.000Z",
"lastUpdated": "2024-01-01T00:00:00.000Z",
"updatedBy": "user-uuid",
"dataStatus": 1
},
"pagination": null,
"error": null
}

Auto-Generated Fields

The API automatically sets:

  • createdAt – Current timestamp
  • lastUpdated – Current timestamp
  • updatedBy – User ID from jwtCookie.id
  • dataStatus – Set to 1 (active)
  • Primary key – Generated UUID if not provided

Creating Records with Relations

You can create records with nested relation data:

{
"jwtCookie": {
"id": "user-uuid"
},
"userId": "00000000-0000-0000-0000-000000000001",
"totalAmount": 99.99,
"orderItems": [
{
"productId": "00000000-0000-0000-0000-000000000002",
"quantity": 2,
"price": 49.99
}
]
}

Example

POST /json/user
Content-Type: application/json
Authorization: Bearer <token>

{
"jwtCookie": {
"id": "00000000-0000-0000-0000-000000000001"
},
"name": "John Doe",
"email": "john@example.com",
"organizationId": "00000000-0000-0000-0000-000000000002"
}

Retrieve

Retrieve records using GET requests. All filtering, sorting, paging, and search features are available for retrieve operations.

Endpoints

  • GET /json/:resource – Fetch multiple records
  • GET /json/:resource/:id – Fetch a single record by ID

Fetch All Records

GET /json/:resource

Fetch Single Record

GET /json/:resource/:id

Query Parameters:

ParameterTypeDescriptionExample
includestringComma-separated relations to include?include=organization,roles

Example:

GET /json/user/00000000-0000-0000-0000-000000000001?include=organization

Response:

{
"success": true,
"data": {
"userId": "00000000-0000-0000-0000-000000000001",
"name": "John Doe",
"email": "john@example.com",
"organization": {
"organizationId": "00000000-0000-0000-0000-000000000002",
"name": "Acme Corp"
}
},
"pagination": null,
"error": null
}

Base Filters

Base filters are used to lock the table's context, ensuring users only access data they're authorized to see. Common use cases:

  • Filter by userId to show only records belonging to the current user
  • Filter by organizationId to show only records for the user's organization
  • Filter by year to show only records for a specific year

Base filters are always applied and cannot be overridden by regular filters.

Format

Base filters are passed as an array in the query string:

?baseFilter=[{"field":"userId","value":"user-uuid","comparison":"equals"}]

Or URL-encoded:

?baseFilter=%5B%7B%22field%22%3A%22userId%22%2C%22value%22%3A%22user-uuid%22%2C%22comparison%22%3A%22equals%22%7D%5D

Multiple Base Filters

You can apply multiple base filters (they are combined with AND):

?baseFilter=[
{"field":"userId","value":"user-uuid","comparison":"equals"},
{"field":"year","value":"2024","comparison":"equals"}
]

Comparison Operators

OperatorDescriptionExample
equalsExact match{"field":"status","value":"active","comparison":"equals"}
notEqualsNot equal{"field":"status","value":"deleted","comparison":"notEquals"}
greaterThanGreater than{"field":"age","value":"18","comparison":"greaterThan"}
greaterThanOrEqualGreater than or equal{"field":"age","value":"18","comparison":"greaterThanOrEqual"}
lessThanLess than{"field":"age","value":"65","comparison":"lessThan"}
lessThanOrEqualLess than or equal{"field":"age","value":"65","comparison":"lessThanOrEqual"}
containsString contains{"field":"name","value":"john","comparison":"contains"}
startsWithString starts with{"field":"name","value":"john","comparison":"startsWith"}
endsWithString ends with{"field":"email","value":"@example.com","comparison":"endsWith"}
inValue in array{"field":"status","value":["active","pending"],"comparison":"in"}
notInValue not in array{"field":"status","value":["deleted","archived"],"comparison":"notIn"}
isNullField is null{"field":"deletedAt","value":null,"comparison":"isNull"}
isNotNullField is not null{"field":"deletedAt","value":null,"comparison":"isNotNull"}

Example: Filter by User and Year

GET /json/order?baseFilter=[
{"field":"userId","value":"00000000-0000-0000-0000-000000000001","comparison":"equals"},
{"field":"year","value":"2024","comparison":"equals"}
]

This ensures users only see orders from 2024 that belong to them.

Filters

Filters are used for further filtering of data beyond base filters. They work the same way as base filters but are applied after base filters.

Format

?filter=[{"field":"status","value":"active","comparison":"equals"}]

Multiple Filters

Multiple filters can be combined with AND or OR using filterClause:

AND (default):

?filter=[
{"field":"status","value":"active","comparison":"equals"},
{"field":"category","value":"electronics","comparison":"equals"}
]&filterClause=and

OR:

?filter=[
{"field":"status","value":"active","comparison":"equals"},
{"field":"status","value":"pending","comparison":"equals"}
]&filterClause=or

You can filter by fields in related tables using dot notation:

?filter=[{"field":"organization.name","value":"Acme Corp","comparison":"equals"}]

Example: Filter Active Products in Electronics Category

GET /json/product?filter=[
{"field":"status","value":"active","comparison":"equals"},
{"field":"category","value":"electronics","comparison":"equals"}
]&filterClause=and

Sorting

Sort records by one or more fields.

Format

?sort=[{"field":"name","direction":"asc"}]

Multiple Sorts

Sort by multiple fields:

?sort=[
{"field":"status","direction":"asc"},
{"field":"createdAt","direction":"desc"}
]

Sorts are applied in order: first by status ascending, then by createdAt descending.

Sort Directions

  • asc – Ascending order
  • desc – Descending order

Sort by fields in related tables:

?sort=[{"field":"organization.name","direction":"asc"}]

Example: Sort by Name Ascending

GET /json/user?sort=[{"field":"name","direction":"asc"}]

Example: Sort by Status Then Created Date

GET /json/order?sort=[
{"field":"status","direction":"asc"},
{"field":"createdAt","direction":"desc"}
]

Paging

Control pagination with page and pageSize parameters.

Parameters

  • page – Page number (default: 1)
  • pageSize – Records per page (default: 10)

Example

GET /json/user?page=2&pageSize=20

This returns page 2 with 20 records per page.

Pagination Response

The response includes pagination metadata:

{
"success": true,
"data": [...],
"pagination": {
"totalRecords": 150,
"totalPages": 8,
"currentPage": 2,
"pageSize": 20
},
"error": null
}

Search across multiple fields using text search.

Parameters

  • textSearchFields – Comma-separated list of fields to search
  • textSearchValue – The search term

Format

?textSearchFields=name,email,phone&textSearchValue=john

Search in related table fields using dot notation:

?textSearchFields=name,organization.name,organization.address&textSearchValue=acme

How It Works

Text search uses contains matching (case-insensitive in PostgreSQL). It searches across all specified fields and returns records where any field matches.

Example: Search Users by Name or Email

GET /json/user?textSearchFields=name,email&textSearchValue=john

This returns users where name or email contains "john".

Example: Search Orders by Customer Name

GET /json/order?textSearchFields=customer.name,customer.email&textSearchValue=smith

Including Relations

Include related data using the include parameter.

Format

?include=organization,roles,permissions

Nested Relations

Include nested relations using dot notation:

?include=organization.address,organization.contacts

Example: Include User's Organization

GET /json/user/00000000-0000-0000-0000-000000000001?include=organization

Response:

{
"success": true,
"data": {
"userId": "00000000-0000-0000-0000-000000000001",
"name": "John Doe",
"email": "john@example.com",
"organization": {
"organizationId": "00000000-0000-0000-0000-000000000002",
"name": "Acme Corp"
}
}
}

Selecting Specific Columns

Select only specific columns using the columns parameter.

Format

?columns=name,email,phone

Combining with Include

You can combine columns with include:

?columns=name,email&include=organization

This selects only name and email from the main table, but includes all fields from organization.

Example: Select Only Name and Email

GET /json/user?columns=name,email

Count Only

Get the count of records without fetching data:

?count=true

Example

GET /json/user?baseFilter=[{"field":"status","value":"active","comparison":"equals"}]&count=true

Response:

{
"success": true,
"data": {
"count": 42
},
"pagination": null,
"error": null
}

Complete Retrieve Examples

Example 1: Basic Fetch with Pagination

GET /json/user?page=1&pageSize=20&sort=[{"field":"name","direction":"asc"}]&include=organization

Example 2: Fetch with Base Filter

GET /json/order?baseFilter=[
{"field":"userId","value":"00000000-0000-0000-0000-000000000001","comparison":"equals"},
{"field":"year","value":"2024","comparison":"equals"}
]&page=1&pageSize=20

Example 3: Fetch with Filters

GET /json/product?baseFilter=[{"field":"organizationId","value":"org-uuid","comparison":"equals"}]&filter=[
{"field":"status","value":"active","comparison":"equals"},
{"field":"category","value":"electronics","comparison":"equals"}
]&filterClause=and&page=1&pageSize=10
GET /json/user?baseFilter=[{"field":"organizationId","value":"org-uuid","comparison":"equals"}]&textSearchFields=name,email&textSearchValue=john&sort=[{"field":"name","direction":"asc"}]

Example 5: Complex Query with All Features

GET /json/order?baseFilter=[
{"field":"userId","value":"user-uuid","comparison":"equals"},
{"field":"year","value":"2024","comparison":"equals"}
]&filter=[
{"field":"status","value":"completed","comparison":"equals"},
{"field":"totalAmount","value":"100","comparison":"greaterThan"}
]&filterClause=and&textSearchFields=customer.name,customer.email&textSearchValue=smith&sort=[
{"field":"createdAt","direction":"desc"},
{"field":"totalAmount","direction":"desc"}
]&include=customer,orderItems.product&page=1&pageSize=20

Update

Update existing records using PUT requests.

Endpoint

PUT /json/:resource/:id

Request Body

Include jwtCookie and the fields to update:

{
"jwtCookie": {
"id": "user-uuid"
},
"name": "Jane Doe",
"email": "jane@example.com"
}

Response

{
"success": true,
"data": {
"userId": "00000000-0000-0000-0000-000000000001",
"name": "Jane Doe",
"email": "jane@example.com",
"lastUpdated": "2024-01-02T00:00:00.000Z",
"updatedBy": "user-uuid"
},
"pagination": null,
"error": null
}

Auto-Updated Fields

The API automatically updates:

  • lastUpdated – Current timestamp
  • updatedBy – User ID from jwtCookie.id

Partial Updates

You only need to include the fields you want to update. Other fields remain unchanged.

Example

PUT /json/user/00000000-0000-0000-0000-000000000001
Content-Type: application/json
Authorization: Bearer <token>

{
"jwtCookie": {
"id": "00000000-0000-0000-0000-000000000001"
},
"name": "Jane Doe",
"email": "jane@example.com"
}

Updating Relations

You can update relation fields by providing the foreign key value:

{
"jwtCookie": {
"id": "user-uuid"
},
"organizationId": "00000000-0000-0000-0000-000000000003"
}

Delete

Delete records using DELETE requests.

Endpoint

DELETE /json/:resource/:id

Request

No request body required. Include JWT token in Authorization header.

Example

DELETE /json/user/00000000-0000-0000-0000-000000000001
Authorization: Bearer <your-jwt-token>

Response

{
"success": true,
"data": null,
"pagination": null,
"error": null
}

Error Response

If the record doesn't exist or cannot be deleted:

{
"success": false,
"data": null,
"pagination": null,
"error": "Record not found or cannot be deleted"
}

Best Practices

  1. Always use base filters for context locking (user, organization, etc.) in retrieve operations
  2. Use filters for user-selected filtering options
  3. Combine base filters when you need multiple context locks
  4. Use text search for user-friendly search functionality
  5. Include relations only when needed to reduce payload size
  6. Select specific columns when you don't need all fields
  7. Use pagination for large datasets
  8. Handle errors by checking the success field
  9. Include JWT token in all requests
  10. Use count=true when you only need the count
  11. Only include fields to update in PUT requests (partial updates)
  12. Verify record exists before deleting

Next Steps