Quickback Docs

CRUD Endpoints

Auto-generated RESTful CRUD endpoints for each resource. Learn how to use list, get, create, update, and delete operations with filtering and pagination.

Quickback automatically generates RESTful CRUD endpoints for each resource you define. This page covers how to use these endpoints.

Quick Reference

All endpoints require authentication. Include your session cookie or Bearer token:

# List records
curl http://localhost:8787/api/v1/jobs \
  -H "Authorization: Bearer <token>"

# Get a single record
curl http://localhost:8787/api/v1/jobs/job_123 \
  -H "Authorization: Bearer <token>"

# Create a record
curl -X POST http://localhost:8787/api/v1/jobs \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"title": "Senior Engineer", "department": "Engineering", "status": "open"}'

# Update a record
curl -X PATCH http://localhost:8787/api/v1/jobs/job_123 \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"title": "Staff Engineer"}'

# Delete a record
curl -X DELETE http://localhost:8787/api/v1/jobs/job_123 \
  -H "Authorization: Bearer <token>"

# List with filters, sorting, and pagination
curl "http://localhost:8787/api/v1/jobs?status=open&sort=createdAt:desc&limit=25&count=true" \
  -H "Authorization: Bearer <token>"

Endpoint Overview

For a resource named jobs, Quickback generates:

MethodEndpointDescription
GET/jobsList all records
GET/jobs/:idGet a single record
POST/jobsCreate a new record
POST/jobs/batchBatch create multiple records
PATCH/jobs/:idUpdate a record
PATCH/jobs/batchBatch update multiple records
DELETE/jobs/:idDelete a record
DELETE/jobs/batchBatch delete multiple records
PUT/jobs/:idUpsert a record (requires config)
PUT/jobs/batchBatch upsert multiple records (requires config)

List Records

GET /jobs

Returns a paginated list of records the user has access to.

Query Parameters

ParameterDescriptionExample
limitNumber of records to return (default: 50, max: 100)?limit=25
offsetNumber of records to skip?offset=50
sortField to sort by?sort=createdAt
orderSort direction: asc or desc?order=desc

Filtering

Filter records using query parameters:

GET /jobs?status=open                          # Exact match
GET /jobs?salaryMax.gt=100000                  # Greater than
GET /jobs?salaryMin.gte=80000                  # Greater than or equal
GET /jobs?salaryMax.lt=200000                  # Less than
GET /jobs?salaryMax.lte=150000                 # Less than or equal
GET /jobs?status.ne=closed                     # Not equal
GET /jobs?title.like=Engineer                  # Pattern match (LIKE %value%)
GET /jobs?status.in=open,draft                 # IN clause

Response

{
  "data": [
    {
      "id": "job_123",
      "title": "Senior Engineer",
      "department": "Engineering",
      "status": "open",
      "salaryMin": 120000,
      "salaryMax": 180000,
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "count": 1
  }
}

FK Label Resolution

Foreign key columns are automatically enriched with _label fields containing the referenced table's display value. For example, departmentId gets a corresponding department_label field. See Display Column for details.

Get Single Record

GET /jobs/:id

Returns a single record by ID.

Response

{
  "id": "job_123",
  "title": "Senior Engineer",
  "department": "Engineering",
  "status": "open",
  "salaryMin": 120000,
  "salaryMax": 180000,
  "createdAt": "2024-01-15T10:30:00Z"
}

Errors

StatusDescription
404Record not found or not accessible
403User lacks permission to view this record

Create Record

POST /jobs
Content-Type: application/json

{
  "title": "Senior Engineer",
  "department": "Engineering",
  "status": "open",
  "salaryMin": 120000,
  "salaryMax": 180000
}

Creates a new record. Only fields listed in guards.createable are accepted.

Response

{
  "data": {
    "id": "job_456",
    "title": "Senior Engineer",
    "department": "Engineering",
    "status": "open",
    "salaryMin": 120000,
    "salaryMax": 180000,
    "createdAt": "2024-01-15T11:00:00Z",
    "createdBy": "user_789"
  }
}

Errors

StatusDescription
400Invalid field or missing required field
403User lacks permission to create records

Update Record

PATCH /jobs/:id
Content-Type: application/json

{
  "title": "Staff Engineer"
}

Updates an existing record. Only fields listed in guards.updatable are accepted.

Response

{
  "data": {
    "id": "job_123",
    "title": "Staff Engineer",
    "modifiedAt": "2024-01-15T12:00:00Z",
    "modifiedBy": "user_789"
  }
}

Errors

StatusDescription
400Invalid field or field not updatable
403User lacks permission to update this record
404Record not found

Delete Record

DELETE /jobs/:id

Deletes a record. Behavior depends on the delete.mode configuration.

Soft Delete (default)

Sets deletedAt and deletedBy fields. Record remains in database but is filtered from queries.

Hard Delete

Permanently removes the record from the database.

Response

{
  "data": {
    "id": "job_123",
    "deleted": true
  }
}

Errors

StatusDescription
403User lacks permission to delete this record
404Record not found

Upsert Record (PUT)

PUT /jobs/:id
Content-Type: application/json

{
  "title": "Contract Recruiter",
  "department": "Talent Acquisition",
  "status": "open",
  "externalId": "ext-123"
}

Creates or updates a record by ID. Requires special configuration:

  1. generateId: false in database config
  2. guards: false in resource definition

Behavior

  • If record exists: Updates all provided fields
  • If record doesn't exist: Creates with the provided ID

Use Cases

  • Syncing data from external systems
  • Webhook handlers with external IDs
  • Idempotent operations (safe to retry)

See Guards documentation for setup details.

Batch Operations

Quickback provides batch endpoints for efficient bulk operations. Batch operations automatically inherit from their corresponding CRUD operations and maintain full security layer consistency.

Batch Create Records

POST /jobs/batch
Content-Type: application/json

{
  "records": [
    { "title": "Frontend Engineer", "department": "Engineering", "status": "open" },
    { "title": "Product Designer", "department": "Design", "status": "draft" },
    { "title": "Data Analyst", "department": "Analytics", "status": "open" }
  ],
  "options": {
    "atomic": false
  }
}

Creates multiple records in a single request. Each record follows the same validation rules as single create operations.

Request Body

FieldTypeDescription
recordsArrayArray of record objects to create
options.atomicBooleanIf true, all records succeed or all fail (default: false)

Response (Partial Success - Default)

{
  "success": [
    { "id": "job_1", "title": "Frontend Engineer", "department": "Engineering", "status": "open" },
    { "id": "job_2", "title": "Product Designer", "department": "Design", "status": "draft" }
  ],
  "errors": [
    {
      "index": 2,
      "record": { "title": "Data Analyst", "department": "Analytics", "status": "open" },
      "error": {
        "error": "Field cannot be set during creation",
        "layer": "guards",
        "code": "GUARD_FIELD_NOT_CREATEABLE",
        "details": { "fields": ["status"] }
      }
    }
  ],
  "meta": {
    "total": 3,
    "succeeded": 2,
    "failed": 1,
    "atomic": false
  }
}

HTTP Status Codes

  • 201 - All records created successfully
  • 207 - Partial success (some records failed)
  • 400 - Atomic mode enabled and one or more records failed

Batch Size Limit

Default: 100 records per request (configurable via maxBatchSize)

Batch Update Records

PATCH /jobs/batch
Content-Type: application/json

{
  "records": [
    { "id": "job_1", "title": "Senior Frontend Engineer" },
    { "id": "job_2", "department": "Product Design" },
    { "id": "job_3", "status": "closed" }
  ],
  "options": {
    "atomic": false
  }
}

Updates multiple records in a single request. All records must include an id field.

Request Body

FieldTypeDescription
recordsArrayArray of record objects with id and fields to update
options.atomicBooleanIf true, all records succeed or all fail (default: false)

Response

{
  "success": [
    { "id": "job_1", "title": "Senior Frontend Engineer", "modifiedAt": "2024-01-15T14:00:00Z" },
    { "id": "job_2", "department": "Product Design", "modifiedAt": "2024-01-15T14:00:00Z" }
  ],
  "errors": [
    {
      "index": 2,
      "record": { "id": "job_3", "status": "closed" },
      "error": {
        "error": "Not found",
        "layer": "firewall",
        "code": "NOT_FOUND",
        "details": { "id": "job_3" }
      }
    }
  ],
  "meta": {
    "total": 3,
    "succeeded": 2,
    "failed": 1,
    "atomic": false
  }
}

Features

  • Batch fetching: Single database query for all IDs (with firewall)
  • Per-record access: Access checks run with record context
  • Field validation: Guards apply to each record individually

Batch Delete Records

DELETE /jobs/batch
Content-Type: application/json

{
  "ids": ["job_1", "job_2", "job_3"],
  "options": {
    "atomic": false
  }
}

Deletes multiple records in a single request. Supports both soft and hard delete modes.

Request Body

FieldTypeDescription
idsArrayArray of record IDs to delete
options.atomicBooleanIf true, all records succeed or all fail (default: false)

Response (Soft Delete)

{
  "success": [
    { "id": "job_1", "deletedAt": "2024-01-15T15:00:00Z", "deletedBy": "user_789" },
    { "id": "job_2", "deletedAt": "2024-01-15T15:00:00Z", "deletedBy": "user_789" }
  ],
  "errors": [
    {
      "index": 2,
      "id": "job_3",
      "error": {
        "error": "Not found",
        "layer": "firewall",
        "code": "NOT_FOUND",
        "details": { "id": "job_3" }
      }
    }
  ],
  "meta": {
    "total": 3,
    "succeeded": 2,
    "failed": 1,
    "atomic": false
  }
}

Delete Modes

  • Soft delete (default): Sets deletedAt, deletedBy, modifiedAt, modifiedBy fields
  • Hard delete: Permanently removes records from database

Batch Upsert Records

PUT /jobs/batch
Content-Type: application/json

{
  "records": [
    { "id": "job_1", "title": "Updated Frontend Engineer", "department": "Engineering" },
    { "id": "new_job", "title": "New Backend Engineer", "department": "Engineering", "status": "draft" }
  ],
  "options": {
    "atomic": false
  }
}

Creates or updates multiple records in a single request. Creates if ID doesn't exist, updates if it does.

Strict Requirements (same as single PUT):

  1. generateId: false in database config (user provides IDs)
  2. guards: false in resource definition (no field restrictions)
  3. All records must include an id field

Note: System-managed fields (createdAt, createdBy, modifiedAt, modifiedBy, deletedAt, deletedBy) are always protected and will be rejected if included in the request, regardless of guards configuration.

Request Body

FieldTypeDescription
recordsArrayArray of record objects with id and all fields
options.atomicBooleanIf true, all records succeed or all fail (default: false)

Response

{
  "success": [
    { "id": "job_1", "title": "Updated Frontend Engineer", "department": "Engineering", "modifiedAt": "2024-01-15T16:00:00Z" },
    { "id": "new_job", "title": "New Backend Engineer", "department": "Engineering", "status": "draft", "createdAt": "2024-01-15T16:00:00Z" }
  ],
  "errors": [],
  "meta": {
    "total": 2,
    "succeeded": 2,
    "failed": 0,
    "atomic": false
  }
}

How It Works

  1. Batch existence check with firewall
  2. Split records into CREATE and UPDATE batches
  3. Validate new records with validateCreate()
  4. Validate existing records with validateUpdate()
  5. Check CREATE access for new records
  6. Check UPDATE access for existing records (per-record)
  7. Execute bulk insert and individual updates
  8. Return combined results

Batch Operation Features

Partial Success Mode (Default)

By default, batch operations use partial success mode:

  • All records are processed independently
  • Failed records go into errors array with detailed error information
  • Successful records go into success array
  • HTTP status 207 Multi-Status if any errors, 201/200 if all success
{
  "success": [ /* succeeded records */ ],
  "errors": [
    {
      "index": 2,
      "record": { /* original input */ },
      "error": {
        "error": "Human-readable message",
        "layer": "guards",
        "code": "GUARD_FIELD_NOT_CREATEABLE",
        "details": { "fields": ["status"] },
        "hint": "These fields are set automatically or must be omitted"
      }
    }
  ],
  "meta": {
    "total": 10,
    "succeeded": 8,
    "failed": 2,
    "atomic": false
  }
}

Atomic Mode (Opt-in)

Enable atomic mode for all-or-nothing behavior:

{
  "records": [ /* ... */ ],
  "options": {
    "atomic": true
  }
}

Atomic mode behavior:

  • First error immediately stops processing
  • All changes are rolled back (database transaction)
  • HTTP status 400 Bad Request
  • Returns single error with failure details
{
  "error": "Batch operation failed in atomic mode",
  "layer": "validation",
  "code": "BATCH_ATOMIC_FAILED",
  "details": {
    "failedAt": 2,
    "reason": { /* the actual error */ }
  },
  "hint": "Transaction rolled back. Fix the error and retry the entire batch."
}

Human-Readable Errors

All batch operation errors include:

  • Layer identification: Which security layer rejected the request
  • Error code: Machine-readable code for programmatic handling
  • Clear message: Human-readable explanation
  • Details: Contextual information (fields, IDs, reasons)
  • Helpful hints: Actionable guidance for resolution

Performance Optimizations

  • Batch size limits: Default 100 records (prevents memory exhaustion)
  • Single firewall query: WHERE id IN (...) instead of N queries
  • Bulk operations: Single INSERT for multiple records (CREATE, UPSERT)
  • O(1) lookups: Map-based record lookup instead of Array.find()

Configuration

Batch operations are auto-enabled when corresponding CRUD operations exist:

// Auto-enabled - no configuration needed
crud: {
  create: { access: { roles: ['recruiter'] } },
  update: { access: { roles: ['recruiter'] } }
  // batchCreate and batchUpdate automatically available
}

// Customize batch operations
crud: {
  create: { access: { roles: ['recruiter'] } },
  batchCreate: {
    access: { roles: ['hiring-manager'] },  // Different access rules
    maxBatchSize: 50,               // Lower limit
    allowAtomic: false              // Disable atomic mode
  }
}

// Disable batch operations
crud: {
  create: { access: { roles: ['recruiter'] } },
  batchCreate: false  // Explicitly disable
}

Security Layer Application

Batch operations maintain full security layer consistency:

  1. Firewall: Auto-apply ownership fields, batch fetch with isolation
  2. Access: Operation-level for CREATE, per-record for UPDATE/DELETE
  3. Guards: Per-record field validation (same rules as single operations)
  4. Masking: Applied to success array (respects user permissions)
  5. Audit: Single timestamp for entire batch for consistency

Authentication

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

Authorization: Bearer <your-token>

The user's context (userId, roles, organizationId) is extracted from the token and used to:

  1. Apply firewall filters (data isolation)
  2. Check access permissions
  3. Set audit fields (createdBy, modifiedBy)

Error Responses

All errors use a flat structure with contextual fields:

{
  "error": "Insufficient permissions",
  "layer": "access",
  "code": "ACCESS_ROLE_REQUIRED",
  "details": {
    "required": ["hiring-manager"],
    "current": ["interviewer"]
  },
  "hint": "Contact an administrator to grant necessary permissions"
}

See Errors for the complete reference of error codes by security layer.

On this page