Skip to content

CRUD Endpoints

This page documents every endpoint generated by AsDbController and AsDbReadableController. All paths are relative to the controller prefix (e.g., /todos/).

See HTTP Setup for installation and wiring.

Reading Records

GET /query

Returns an array of records. Supports filtering, sorting, pagination, projection, relation loading, and search.

bash
curl "http://localhost:3000/todos/query?completed=false&\$sort=-createdAt&\$limit=10"

Query parameters:

ParameterTypeDefaultDescription
$sortstringSort expression (e.g., -createdAt for descending)
$limitnumber1000Maximum records to return
$skipnumber0Number of records to skip
$selectstringComma-separated field names for projection
$countbooleanReturn count instead of records
$searchstringFulltext search term
$indexstringNamed search index
$vectorstringVector field name for similarity search
$thresholdstringSimilarity threshold for vector search
$withstringLoad relations (e.g., $with=author,comments)
$groupBystringGroup by fields for aggregation
(other)anyFilter fields (e.g., status=active)

Response (array):

json
[
  { "id": 1, "title": "Buy milk", "completed": false, "priority": "high" },
  { "id": 2, "title": "Write docs", "completed": false, "priority": "medium" }
]

When $count is set, returns a number instead of an array:

bash
curl "http://localhost:3000/todos/query?completed=true&\$count"
json
5

See URL Query Syntax for the full filter syntax and Relations & Search for $with, $search, $vector, and $groupBy.

GET /pages

Returns paginated results with metadata. Uses page-based pagination instead of offset-based.

bash
curl "http://localhost:3000/todos/pages?\$page=2&\$size=10&status=active"

Additional parameters:

ParameterTypeDefaultDescription
$pagenumber1Page number (1-based)
$sizenumber10Items per page

All other query parameters from GET /query (filters, $sort, $select, $search, $with) are also supported, except $skip, $limit, and $count.

Response:

json
{
  "data": [
    { "id": 11, "title": "Task 11", "status": "active" }
  ],
  "page": 2,
  "itemsPerPage": 10,
  "pages": 5,
  "count": 47
}
FieldDescription
dataArray of records for the current page
pageCurrent page number
itemsPerPagePage size
pagesTotal number of pages
countTotal number of matching records

GET /one/:id

Retrieves a single record by primary key. Returns 404 if not found.

bash
curl http://localhost:3000/todos/one/42

Response:

json
{ "id": 42, "title": "Buy milk", "completed": false, "priority": "high" }

Supports $select and $with in the query string:

bash
curl "http://localhost:3000/todos/one/42?\$select=id,title&\$with=project"

No filters

Filter parameters (like status=active) are not allowed on this endpoint. They return a 400 error. Use GET /query with filters instead.

Composite keys — use query parameters instead of a path parameter:

bash
curl "http://localhost:3000/task-tags/one?taskId=1&tagId=2"

The controller matches query parameters against composite primary keys first, then compound unique indexes.

GET /meta

Returns table or view metadata for use by UI tooling or client libraries.

bash
curl http://localhost:3000/todos/meta

Response:

json
{
  "searchable": true,
  "vectorSearchable": false,
  "searchIndexes": [
    { "name": "DEFAULT", "description": "dynamic_text index" }
  ],
  "type": { "...": "serialized Atscript type definition" }
}
FieldDescription
searchableWhether the table has fulltext search indexes
vectorSearchableWhether the table has vector search indexes
searchIndexesArray of available search index definitions
typeFull serialized Atscript type (field names, types, annotations, metadata)

Creating Records

POST /

Insert one or many records. The request body determines single vs. batch mode.

Single insert:

bash
curl -X POST http://localhost:3000/todos/ \
  -H "Content-Type: application/json" \
  -d '{"title": "Buy milk", "priority": "high"}'

Response:

json
{ "insertedId": 1 }

Batch insert:

bash
curl -X POST http://localhost:3000/todos/ \
  -H "Content-Type: application/json" \
  -d '[{"title": "Buy milk"}, {"title": "Write docs"}]'

Response:

json
{ "insertedCount": 2, "insertedIds": [1, 2] }

Default values from @db.default and generated defaults (@db.default.increment, @db.default.uuid, @db.default.now) are applied automatically. Request bodies can contain nested relation data for deep insert operations.

Batch edge cases
  • Empty array [] — behavior is adapter-dependent (may return 200, 201, 400, or 500)
  • Single-item array [{...}] — treated as a batch insert, returns insertedCount / insertedIds
  • Large batches (100+ items) — supported; the entire batch runs in a single transaction
  • Partial failure — if any item fails validation or violates a constraint, the entire batch is rolled back

Updating Records

PUT /

Full replace by primary key. The body must include all required fields and the primary key field(s).

Single replace:

bash
curl -X PUT http://localhost:3000/todos/ \
  -H "Content-Type: application/json" \
  -d '{"id": 1, "title": "Buy oat milk", "completed": true, "priority": "high"}'

Response:

json
{ "matchedCount": 1, "modifiedCount": 1 }

Bulk replace:

bash
curl -X PUT http://localhost:3000/todos/ \
  -H "Content-Type: application/json" \
  -d '[
    {"id": 1, "title": "Buy oat milk", "completed": true, "priority": "high"},
    {"id": 2, "title": "Write tests", "completed": false, "priority": "medium"}
  ]'

Response:

json
{ "matchedCount": 2, "modifiedCount": 2 }

Nested relation data is supported per item — each record goes through the deep replace process.

PATCH /

Partial update by primary key. Only the provided fields are changed.

Single update:

bash
curl -X PATCH http://localhost:3000/todos/ \
  -H "Content-Type: application/json" \
  -d '{"id": 1, "completed": true}'

Response:

json
{ "matchedCount": 1, "modifiedCount": 1 }

Bulk update:

bash
curl -X PATCH http://localhost:3000/todos/ \
  -H "Content-Type: application/json" \
  -d '[{"id": 1, "completed": true}, {"id": 2, "priority": "high"}]'

Response:

json
{ "matchedCount": 2, "modifiedCount": 2 }

See Update & Patch for details on patch semantics and embedded object updates.

Deleting Records

DELETE /:id

Removes a single record by primary key. Returns 404 if the record is not found.

bash
curl -X DELETE http://localhost:3000/todos/42

Response:

json
{ "deletedCount": 1 }

Composite keys — use query parameters:

bash
curl -X DELETE "http://localhost:3000/task-tags/?taskId=1&tagId=2"

Response:

json
{ "deletedCount": 1 }

Error Handling

The controller automatically transforms errors into appropriate HTTP responses:

ErrorHTTP StatusResponse Body
ValidatorError400{ message, statusCode, errors: [{ path, message }] }
DbError (CONFLICT)409{ message, statusCode, errors }
DbError (other)400{ message, statusCode, errors }
Not found404Standard 404

Validation error example:

json
{
  "message": "Validation failed",
  "statusCode": 400,
  "errors": [
    { "path": "title", "message": "Required field" },
    { "path": "project.title", "message": "Expected string, got number" },
    { "path": "tasks.0.status", "message": "Required field" }
  ]
}

Validation runs automatically on POST, PUT, and PATCH using the constraints defined in your .as schema.

Query Validation

Invalid query parameters return 400 errors with descriptive messages:

Invalid queryError reason
$with=nonexistentNavigation property does not exist
$with=projectIdFK field, not a navigation property
$with=tasks($with=nonexistent)Nested relation does not exist
$select=fakefieldField does not exist on the type
$sort=nonexistentCannot sort by unknown field
GET /one/1?status=todoFilters not allowed on getOne endpoint

These validations apply to all endpoints that accept query controls — /query, /pages, and /one/:id.

Next Steps

Released under the MIT License.