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​