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/dbreplaces the entire documentWhen the document already exists,
POST /api/dbreplaces the entire document with thedatayou send. Any existing fields that are not included indatawill be lost.Use
POST /api/dbfor:
- Creating a new document
- Replacing a document completely (when you have the full object)
Do NOT use
POST /api/dbfor:
- Updating a single field — use
PUT /api/dbinstead- 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/dbupdates only the specified field
PUT /api/dbupdates only the specified field, leaving all other fields in the document untouched. It is the safe way to do partial updates.Use
PUT /api/dbfor:
- 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,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
| Field | Type | Default | Description |
|---|---|---|---|
collection | string | — | Required. Collection path (supports nested: chats/uid1/messages) |
where | object | null | Filter conditions (see operators below) |
orderBy | object | null | Sort fields: { "field": "asc" | "desc" } |
limit | number | 20 | Max documents per page (1–500) |
offset | number | 0 | Skip N documents (for pagination) |
Response fields
| Field | Type | Description |
|---|---|---|
results | array | Matching documents |
total | number | Total matching documents (ignoring limit/offset) |
limit | number | Echo of request limit |
offset | number | Echo of request offset |
hasMore | boolean | true if more pages exist |
took | number | Server processing time in milliseconds |
Filter operators
All operators are optional. Mix and match as needed.
| Operator | Example | SQL 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 method HTTP endpoint moon.db.set(path, data)POST /api/dbmoon.db.get(path)GET /api/db?collection=pathmoon.db.updateField(path, field, value)PUT /api/db?collection=pathmoon.db.deleteField(path, field)DELETE /api/db?collection=path&field=fieldmoon.db.delete(path)DELETE /api/db?collection=pathmoon.db.query(collection, options)POST /api/db/querymoon.db.count(collection, options)POST /api/db/query(withlimit: 1)