Documentation
API Reference
Database

Database API

Path-based document database.

Create/Update

POST /api/db
Authorization: Bearer <token>
Content-Type: application/json
 
{
  "collection": "users/user_123",
  "data": {
    "name": "John",
    "email": "john@example.com"
  }
}

Important — POST /api/db replaces the entire document

When the document already exists, POST /api/db replaces the entire document with the data you send. Any existing fields that are not included in data will be lost.

Use POST /api/db for:

  • Creating a new document
  • Replacing a document completely (when you have the full object)

Do NOT use POST /api/db for:

  • Updating a single field — use PUT /api/db instead
  • Partial updates — any missing field will be dropped
// BAD — overwrites the whole document
// If `data` is incomplete, other fields get dropped
POST /api/db
{
  "collection": "users/user_123",
  "data": { "lastSeenAt": "2026-09-20T07:26:16.709Z" }
}
 
// GOOD — updates only lastSeenAt, rest of the document stays intact
PUT /api/db?collection=users/user_123
{
  "oldKey": "lastSeenAt",
  "newKey": "lastSeenAt",
  "value": "2026-09-20T07:26:16.709Z",
  "type": "string"
}

Read

GET /api/db?collection=users/user_123
Authorization: Bearer <token>

Update Field

PUT /api/db?collection=users/user_123
Content-Type: application/json
 
{
  "oldKey": "status",
  "newKey": "status",
  "value": "verified",
  "type": "string"
}

PUT /api/db updates only the specified field

PUT /api/db updates only the specified field, leaving all other fields in the document untouched. It is the safe way to do partial updates.

Use PUT /api/db for:

  • Updating a single field (e.g. lastSeenAt, status, avatarUrl)
  • Updating a deeply nested field (e.g. address/city/zip)
  • Any change where you want to keep the rest of the document intact

Field path support:

  • Top-level field: "status"
  • Nested field: "address/city/zip"

Supported types: string, number, integer, float, boolean, date, datetime, timestamp, array, object, null, email, url, phone, uuid, json

Delete Document

DELETE /api/db?collection=users/user_123
Authorization: Bearer <token>

Delete Field

DELETE /api/db?collection=users/user_123&field=phone
Authorization: Bearer <token>

★ Query — Filter, Sort, Paginate

Fetch multiple documents from a collection with dynamic filters, sorting, and pagination. No schema required — any field can be queried.

POST /api/db/query
Authorization: Bearer <token>
X-Project-Id: <project_id>
Content-Type: application/json
 
{
  "collection": "chats",
  "where": {
    "OR": [
      { "fromUid": "user_123" },
      { "toUid": "user_123" }
    ]
  },
  "orderBy": { "createdAt": "desc" },
  "limit": 20,
  "offset": 0
}

Response:

{
  "results": [
    {
      "_id": "msg_001",
      "fromUid": "user_123",
      "toUid": "user_456",
      "text": "Hello!",
      "createdAt": "2026-09-24T10:00:00.000Z",
      "_createdAt": "2026-09-24T10:00:00.000Z",
      "_updatedAt": "2026-09-24T10:00:00.000Z"
    }
  ],
  "total": 142,
  "limit": 20,
  "offset": 0,
  "hasMore": true,
  "took": 7
}

Request body fields

FieldTypeDefaultDescription
collectionstring—Required. Collection path (supports nested: chats/uid1/messages)
whereobjectnullFilter conditions (see operators below)
orderByobjectnullSort fields: { "field": "asc" | "desc" }
limitnumber20Max documents per page (1–500)
offsetnumber0Skip N documents (for pagination)

Response fields

FieldTypeDescription
resultsarrayMatching documents
totalnumberTotal matching documents (ignoring limit/offset)
limitnumberEcho of request limit
offsetnumberEcho of request offset
hasMorebooleantrue if more pages exist
tooknumberServer processing time in milliseconds

Filter operators

All operators are optional. Mix and match as needed.

OperatorExampleSQL equivalent
Direct equality{ "status": "active" }= 'active'
eq{ "status": { "eq": "active" } }= 'active'
neq / ne{ "status": { "neq": "banned" } }<> 'banned'
gt{ "age": { "gt": 18 } }> 18
gte{ "age": { "gte": 18 } }>= 18
lt{ "price": { "lt": 1000 } }< 1000
lte{ "price": { "lte": 1000 } }<= 1000
contains{ "name": { "contains": "john" } }ILIKE '%john%'
startsWith{ "name": { "startsWith": "j" } }ILIKE 'j%'
endsWith{ "email": { "endsWith": "@gmail.com" } }ILIKE '%@gmail.com'
in{ "role": { "in": ["admin", "mod"] } }IN (...)
nin{ "role": { "nin": ["banned"] } }NOT IN (...)
exists{ "email": { "exists": true } }jsonb_exists(...)
OR{ "OR": [{...}, {...}] }(...) OR (...)
AND{ "AND": [{...}, {...}] }(...) AND (...)
NOT{ "NOT": {...} }NOT (...)
Nested field{ "address.city": "Mumbai" }JSON path address.city
Null check{ "deletedAt": null }IS NULL

Examples

Simple equality:

POST /api/db/query
{
  "collection": "users",
  "where": { "role": "admin" }
}

Multiple conditions (AND implicit):

POST /api/db/query
{
  "collection": "orders",
  "where": {
    "status": "pending",
    "amount": { "gt": 5000 }
  }
}

OR conditions:

POST /api/db/query
{
  "collection": "chats",
  "where": {
    "OR": [
      { "fromUid": "user_123" },
      { "toUid": "user_123" }
    ]
  }
}

Complex nested:

POST /api/db/query
{
  "collection": "posts",
  "where": {
    "AND": [
      { "status": "published" },
      {
        "OR": [
          { "authorUid": "user_123" },
          { "isPublic": true }
        ]
      },
      { "createdAt": { "gte": "2026-01-01" } }
    ]
  },
  "orderBy": { "createdAt": "desc" },
  "limit": 50
}

In array:

POST /api/db/query
{
  "collection": "products",
  "where": {
    "category": { "in": ["electronics", "gadgets"] },
    "stock": { "gt": 0 }
  }
}

Text search:

POST /api/db/query
{
  "collection": "users",
  "where": {
    "name": { "contains": "john" }
  }
}

Pagination

POST /api/db/query
{
  "collection": "posts",
  "orderBy": { "createdAt": "desc" },
  "limit": 20,
  "offset": 0
}

Next page:

POST /api/db/query
{
  "collection": "posts",
  "orderBy": { "createdAt": "desc" },
  "limit": 20,
  "offset": 20
}

Sorting

POST /api/db/query
{
  "collection": "products",
  "orderBy": {
    "category": "asc",
    "price": "desc"
  }
}

Sort directions: "asc" / "ASC", "desc" / "DESC".


★ Count

Use limit: 1 with the query endpoint to get the total count without downloading all documents.

POST /api/db/query
{
  "collection": "chats",
  "where": {
    "toUid": "user_123",
    "read": false
  },
  "limit": 1,
  "offset": 0
}

Response:

{
  "results": [ { "_id": "...", ... } ],
  "total": 5,
  "limit": 1,
  "offset": 0,
  "hasMore": true,
  "took": 3
}

Read total for the count. Use this for badges, unread counters, dashboard stats — without fetching all documents.


SDK Example

await moon.db.set('users/user_123', { name: 'John' });
const user = await moon.db.get('users/user_123');
await moon.db.updateField('users/user_123', 'status', 'verified');
await moon.db.delete('users/user_123');
 
// Query
const chats = await moon.db.query('chats', {
  where: {
    OR: [
      { fromUid: 'user_123' },
      { toUid: 'user_123' },
    ],
  },
  orderBy: { createdAt: 'desc' },
  limit: 20,
});
 
// Count
const unread = await moon.db.count('chats', {
  where: { toUid: 'user_123', read: false },
});

SDK mapping:

SDK methodHTTP endpoint
moon.db.set(path, data)POST /api/db
moon.db.get(path)GET /api/db?collection=path
moon.db.updateField(path, field, value)PUT /api/db?collection=path
moon.db.deleteField(path, field)DELETE /api/db?collection=path&field=field
moon.db.delete(path)DELETE /api/db?collection=path
moon.db.query(collection, options)POST /api/db/query
moon.db.count(collection, options)POST /api/db/query (with limit: 1)