Initial commit
This commit is contained in:
56
skills/pocketbase/references/api/api_backups.md
Normal file
56
skills/pocketbase/references/api/api_backups.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Backups API
|
||||
|
||||
## Overview
|
||||
|
||||
PocketBase ships with a Backups API for full database snapshots. It is distinct from the per-collection import/export workflows described in [Data Migration Workflows](../../core/data_migration.md). Use backups for disaster recovery or environment cloning; use targeted migrations when you need fine-grained control over specific collections.
|
||||
|
||||
## List Backups
|
||||
|
||||
```http
|
||||
GET /api/backups
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
## Create Backup
|
||||
|
||||
```http
|
||||
POST /api/backups
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"name": "backup-2024-01-01"
|
||||
}
|
||||
```
|
||||
|
||||
## Download Backup
|
||||
|
||||
```http
|
||||
GET /api/backups/{backupId}/download
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
## Upload Backup
|
||||
|
||||
```http
|
||||
POST /api/backups/upload
|
||||
Content-Type: multipart/form-data
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
file: backup.sql
|
||||
```
|
||||
|
||||
## Restore Backup
|
||||
|
||||
```http
|
||||
POST /api/backups/{backupId}/restore
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
### Best practices
|
||||
|
||||
- Schedule backups before and after running large data migrations.
|
||||
- Store backups off the instance (object storage or encrypted volumes) and version them alongside schema migrations.
|
||||
- To restore into a clean instance and then migrate selective collections, combine this API with the targeted tools documented in [Data Migration Workflows](../../core/data_migration.md).
|
||||
|
||||
See also [core/going_to_production.md](../core/going_to_production.md#backup-strategy) for operational guidance.
|
||||
158
skills/pocketbase/references/api/api_collections.md
Normal file
158
skills/pocketbase/references/api/api_collections.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Collections API
|
||||
|
||||
## Overview
|
||||
|
||||
The Collections API allows you to programmatically manage PocketBase collections, including creating, updating, deleting, and configuring collections.
|
||||
|
||||
## List Collections
|
||||
|
||||
```http
|
||||
GET /api/collections
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"perPage": 30,
|
||||
"totalItems": 3,
|
||||
"totalPages": 1,
|
||||
"items": [
|
||||
{
|
||||
"id": "_pbc_344172009",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"fields": [
|
||||
{
|
||||
"name": "email",
|
||||
"type": "email",
|
||||
"required": true,
|
||||
"options": {
|
||||
"exceptDomains": null,
|
||||
"onlyDomains": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "verified",
|
||||
"type": "bool",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"created": "2024-01-01 12:00:00Z",
|
||||
"updated": "2024-01-10 08:30:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Get Single Collection
|
||||
|
||||
```http
|
||||
GET /api/collections/{collectionId}
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
## Create Collection
|
||||
|
||||
```http
|
||||
POST /api/collections
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"name": "products",
|
||||
"type": "base",
|
||||
"fields": [
|
||||
{
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 1,
|
||||
"max": 200
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "price",
|
||||
"type": "number",
|
||||
"required": true,
|
||||
"options": {
|
||||
"min": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "status = 'published'",
|
||||
"viewRule": "status = 'published'",
|
||||
"createRule": "@request.auth.id != ''",
|
||||
"updateRule": "@request.auth.role = 'admin'",
|
||||
"deleteRule": "@request.auth.role = 'admin'"
|
||||
}
|
||||
```
|
||||
|
||||
## Update Collection
|
||||
|
||||
```http
|
||||
PATCH /api/collections/{collectionId}
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"name": "products",
|
||||
"fields": [
|
||||
{
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE INDEX idx_products_title ON products (title)"
|
||||
],
|
||||
"listRule": "status = 'published'",
|
||||
"updateRule": "@request.auth.role = 'admin'"
|
||||
}
|
||||
```
|
||||
|
||||
## Delete Collection
|
||||
|
||||
```http
|
||||
DELETE /api/collections/{collectionId}
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
## Import Collections
|
||||
|
||||
```http
|
||||
POST /api/collections/import
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"collections": [
|
||||
{
|
||||
"name": "posts",
|
||||
"type": "base",
|
||||
"fields": [
|
||||
{
|
||||
"name": "title",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"listRule": "",
|
||||
"viewRule": "",
|
||||
"createRule": "@request.auth.id != ''"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
For the full set of fields and options, refer to the [official API Collections reference](https://pocketbase.io/docs/api-collections/).
|
||||
**Note:** This is a placeholder file. See [core/collections.md](../core/collections.md) for comprehensive collection documentation.
|
||||
57
skills/pocketbase/references/api/api_crons.md
Normal file
57
skills/pocketbase/references/api/api_crons.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Crons API
|
||||
|
||||
## Overview
|
||||
|
||||
The Crons API manages background jobs and scheduled tasks.
|
||||
|
||||
## List Crons
|
||||
|
||||
```http
|
||||
GET /api/crons
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
## Get Single Cron
|
||||
|
||||
```http
|
||||
GET /api/crons/{cronId}
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
## Create Cron
|
||||
|
||||
```http
|
||||
POST /api/crons
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"name": "daily-backup",
|
||||
"query": "SELECT 1",
|
||||
"cron": "0 2 * * *",
|
||||
"schedule": "0 2 * * *"
|
||||
}
|
||||
```
|
||||
|
||||
## Update Cron
|
||||
|
||||
```http
|
||||
PATCH /api/crons/{cronId}
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"cron": "0 3 * * *"
|
||||
}
|
||||
```
|
||||
|
||||
## Delete Cron
|
||||
|
||||
```http
|
||||
DELETE /api/crons/{cronId}
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Note:** This is a placeholder file. See [go/jobs_scheduling.md](../go/go_jobs_scheduling.md) for background jobs.
|
||||
58
skills/pocketbase/references/api/api_files.md
Normal file
58
skills/pocketbase/references/api/api_files.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Files API
|
||||
|
||||
## Overview
|
||||
|
||||
The Files API provides endpoints for file upload, download, thumbnail generation, and file management.
|
||||
|
||||
## File Upload
|
||||
|
||||
### Single File Upload
|
||||
|
||||
```http
|
||||
POST /api/collections/{collection}/records/{recordId}/files/{field}
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
file: (binary)
|
||||
```
|
||||
|
||||
### Multiple Files Upload
|
||||
|
||||
```http
|
||||
POST /api/collections/{collection}/records/{recordId}/files/{field}
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
file: (binary)
|
||||
file: (binary)
|
||||
file: (binary)
|
||||
```
|
||||
|
||||
## File URL Generation
|
||||
|
||||
### Get File URL
|
||||
|
||||
```javascript
|
||||
const url = pb.files.getURL(record, fileName);
|
||||
const thumbnailUrl = pb.files.getURL(record, fileName, { thumb: '300x300' });
|
||||
```
|
||||
|
||||
### Signed URLs (Private Files)
|
||||
|
||||
```javascript
|
||||
const signedUrl = pb.files.getURL(record, fileName, { expires: 3600 });
|
||||
```
|
||||
|
||||
## Delete File
|
||||
|
||||
```http
|
||||
DELETE /api/collections/{collection}/records/{recordId}/files/{field}
|
||||
```
|
||||
|
||||
## Download File
|
||||
|
||||
```http
|
||||
GET /api/files/{collectionId}/{recordId}/{fileName}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Note:** This is a placeholder file. See [core/files_handling.md](../core/files_handling.md) for comprehensive file handling documentation.
|
||||
68
skills/pocketbase/references/api/api_health.md
Normal file
68
skills/pocketbase/references/api/api_health.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Health API
|
||||
|
||||
## Overview
|
||||
|
||||
The Health API provides system health checks, metrics, and status information.
|
||||
|
||||
## Check Health Status
|
||||
|
||||
```http
|
||||
GET /api/health
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"status": "ok",
|
||||
"metrics": {
|
||||
"clients": 5,
|
||||
"requests": 1000,
|
||||
"errors": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Detailed Health Check
|
||||
|
||||
```http
|
||||
GET /api/health/detailed
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"version": "0.20.0",
|
||||
"uptime": 3600,
|
||||
"database": {
|
||||
"status": "ok",
|
||||
"size": 1048576,
|
||||
"connections": 5
|
||||
},
|
||||
"cache": {
|
||||
"status": "ok",
|
||||
"hits": 100,
|
||||
"misses": 10
|
||||
},
|
||||
"metrics": {
|
||||
"active_connections": 5,
|
||||
"total_requests": 1000,
|
||||
"error_rate": 0.02
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
```http
|
||||
GET /api/metrics
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Note:** This is a placeholder file. See [core/going_to_production.md](../core/going_to_production.md#monitoring-and-logging) for monitoring best practices.
|
||||
53
skills/pocketbase/references/api/api_logs.md
Normal file
53
skills/pocketbase/references/api/api_logs.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Logs API
|
||||
|
||||
## Overview
|
||||
|
||||
The Logs API provides access to application logs, including authentication logs, request logs, and custom logs.
|
||||
|
||||
## Get Request Logs
|
||||
|
||||
```http
|
||||
GET /api/logs/requests?page=1&perPage=50&filter=created>="2024-01-01"
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"perPage": 50,
|
||||
"totalItems": 100,
|
||||
"totalPages": 2,
|
||||
"items": [
|
||||
{
|
||||
"id": "log_id",
|
||||
"method": "GET",
|
||||
"url": "/api/collections/posts/records",
|
||||
"status": 200,
|
||||
"duration": 15,
|
||||
"remoteIP": "192.168.1.1",
|
||||
"userAgent": "Mozilla/5.0...",
|
||||
"referer": "",
|
||||
"created": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Get Auth Logs
|
||||
|
||||
```http
|
||||
GET /api/logs/auth?page=1&perPage=50
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
## Get Raw Logs
|
||||
|
||||
```http
|
||||
GET /api/logs?type=request&level=error&page=1&perPage=50
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Note:** This is a placeholder file. See [core/going_to_production.md](../core/going_to_production.md) for logging best practices.
|
||||
648
skills/pocketbase/references/api/api_realtime.md
Normal file
648
skills/pocketbase/references/api/api_realtime.md
Normal file
@@ -0,0 +1,648 @@
|
||||
# Realtime API
|
||||
|
||||
## Overview
|
||||
|
||||
PocketBase provides real-time updates via WebSocket connections, allowing your application to receive instant notifications when data changes.
|
||||
|
||||
## Connection
|
||||
|
||||
### Automatic Connection (SDK)
|
||||
|
||||
```javascript
|
||||
// SDK automatically manages WebSocket connection
|
||||
const pb = new PocketBase('http://127.0.0.1:8090');
|
||||
|
||||
// Connection is established automatically
|
||||
pb.realtime.connection.addListener('open', () => {
|
||||
console.log('Connected to realtime');
|
||||
});
|
||||
|
||||
pb.realtime.connection.addListener('close', () => {
|
||||
console.log('Disconnected from realtime');
|
||||
});
|
||||
|
||||
pb.realtime.connection.addListener('error', (error) => {
|
||||
console.error('Realtime error:', error);
|
||||
});
|
||||
```
|
||||
|
||||
### Manual WebSocket Connection
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://127.0.0.1:8090/api/realtime');
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('WebSocket connected');
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
console.log('WebSocket disconnected');
|
||||
};
|
||||
|
||||
ws.onerror = function(error) {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('Real-time event:', data);
|
||||
};
|
||||
```
|
||||
|
||||
## Subscriptions
|
||||
|
||||
### Subscribe to Collection
|
||||
|
||||
Listen to all changes in a collection:
|
||||
|
||||
```javascript
|
||||
// Subscribe to all posts changes
|
||||
pb.collection('posts').subscribe('*', function (e) {
|
||||
console.log(e.action); // 'create', 'update', or 'delete'
|
||||
console.log(e.record); // Changed record
|
||||
|
||||
if (e.action === 'create') {
|
||||
// New post created
|
||||
} else if (e.action === 'update') {
|
||||
// Post updated
|
||||
} else if (e.action === 'delete') {
|
||||
// Post deleted
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Subscribe to Specific Record
|
||||
|
||||
Listen to changes for a specific record:
|
||||
|
||||
```javascript
|
||||
// Subscribe to specific post
|
||||
pb.collection('posts').subscribe('RECORD_ID', function (e) {
|
||||
console.log('Post changed:', e.record);
|
||||
});
|
||||
|
||||
// Multiple records
|
||||
pb.collection('posts').subscribe('ID1', callback);
|
||||
pb.collection('posts').subscribe('ID2', callback);
|
||||
```
|
||||
|
||||
### Subscribe via Admin Client
|
||||
|
||||
```javascript
|
||||
// Subscribe using admin client
|
||||
pb.admin.onChange('records', 'posts', (action, record) => {
|
||||
console.log(`${action} on posts:`, record);
|
||||
});
|
||||
```
|
||||
|
||||
## Event Object
|
||||
|
||||
### Create Event
|
||||
|
||||
```javascript
|
||||
{
|
||||
"action": "create",
|
||||
"record": {
|
||||
"id": "RECORD_ID",
|
||||
"title": "New Post",
|
||||
"content": "Hello",
|
||||
"created": "2024-01-01T00:00:00.000Z",
|
||||
"updated": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Update Event
|
||||
|
||||
```javascript
|
||||
{
|
||||
"action": "update",
|
||||
"record": {
|
||||
"id": "RECORD_ID",
|
||||
"title": "Updated Post",
|
||||
"content": "Updated content",
|
||||
"created": "2024-01-01T00:00:00.000Z",
|
||||
"updated": "2024-01-01T12:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Event
|
||||
|
||||
```javascript
|
||||
{
|
||||
"action": "delete",
|
||||
"record": {
|
||||
"id": "RECORD_ID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Unsubscribing
|
||||
|
||||
### Unsubscribe from Specific Record
|
||||
|
||||
```javascript
|
||||
// Unsubscribe from specific record
|
||||
pb.collection('posts').unsubscribe('RECORD_ID');
|
||||
|
||||
// Or using the subscription object
|
||||
const unsubscribe = pb.collection('posts').subscribe('*', callback);
|
||||
unsubscribe(); // Stop listening
|
||||
```
|
||||
|
||||
### Unsubscribe from Collection
|
||||
|
||||
```javascript
|
||||
// Unsubscribe from all collection changes
|
||||
pb.collection('posts').unsubscribe();
|
||||
```
|
||||
|
||||
### Unsubscribe from All Collections
|
||||
|
||||
```javascript
|
||||
// Stop all subscriptions
|
||||
pb.collections.unsubscribe();
|
||||
```
|
||||
|
||||
## Realtime with React
|
||||
|
||||
### Hook Example
|
||||
|
||||
```javascript
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
function useRealtime(collection, recordId, callback) {
|
||||
useEffect(() => {
|
||||
let unsubscribe;
|
||||
|
||||
if (recordId) {
|
||||
// Subscribe to specific record
|
||||
unsubscribe = pb.collection(collection).subscribe(recordId, callback);
|
||||
} else {
|
||||
// Subscribe to all collection changes
|
||||
unsubscribe = pb.collection(collection).subscribe('*', callback);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
}
|
||||
};
|
||||
}, [collection, recordId]);
|
||||
}
|
||||
|
||||
// Usage
|
||||
function PostList() {
|
||||
const [posts, setPosts] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
// Load initial data
|
||||
loadPosts();
|
||||
|
||||
// Subscribe to realtime updates
|
||||
pb.collection('posts').subscribe('*', (e) => {
|
||||
if (e.action === 'create') {
|
||||
setPosts(prev => [e.record, ...prev]);
|
||||
} else if (e.action === 'update') {
|
||||
setPosts(prev => prev.map(p => p.id === e.record.id ? e.record : p));
|
||||
} else if (e.action === 'delete') {
|
||||
setPosts(prev => prev.filter(p => p.id !== e.record.id));
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
pb.collection('posts').unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function loadPosts() {
|
||||
const records = await pb.collection('posts').getList(1, 50);
|
||||
setPosts(records.items);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{posts.map(post => (
|
||||
<PostCard key={post.id} post={post} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Optimized React Example
|
||||
|
||||
```javascript
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
function PostDetails({ postId }) {
|
||||
const [post, setPost] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!postId) return;
|
||||
|
||||
// Load initial data
|
||||
loadPost();
|
||||
|
||||
// Subscribe to this specific post
|
||||
const unsubscribe = pb.collection('posts').subscribe(postId, (e) => {
|
||||
setPost(e.record);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [postId]);
|
||||
|
||||
async function loadPost() {
|
||||
const record = await pb.collection('posts').getOne(postId);
|
||||
setPost(record);
|
||||
}
|
||||
|
||||
if (!post) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{post.title}</h1>
|
||||
<p>{post.content}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Realtime with Vue.js
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
posts: []
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadPosts();
|
||||
|
||||
// Subscribe to realtime updates
|
||||
pb.collection('posts').subscribe('*', (e) => {
|
||||
if (e.action === 'create') {
|
||||
this.posts.unshift(e.record);
|
||||
} else if (e.action === 'update') {
|
||||
const index = this.posts.findIndex(p => p.id === e.record.id);
|
||||
if (index !== -1) {
|
||||
this.posts.splice(index, 1, e.record);
|
||||
}
|
||||
} else if (e.action === 'delete') {
|
||||
this.posts = this.posts.filter(p => p.id !== e.record.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
pb.collection('posts').unsubscribe();
|
||||
},
|
||||
methods: {
|
||||
async loadPosts() {
|
||||
const records = await pb.collection('posts').getList(1, 50);
|
||||
this.posts = records.items;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Realtime with Vanilla JavaScript
|
||||
|
||||
```javascript
|
||||
const postsList = document.getElementById('posts');
|
||||
|
||||
async function loadPosts() {
|
||||
const response = await pb.collection('posts').getList(1, 50);
|
||||
renderPosts(response.items);
|
||||
}
|
||||
|
||||
function renderPosts(posts) {
|
||||
postsList.innerHTML = posts.map(post => `
|
||||
<div class="post">
|
||||
<h3>${post.title}</h3>
|
||||
<p>${post.content}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Subscribe to realtime updates
|
||||
pb.collection('posts').subscribe('*', (e) => {
|
||||
if (e.action === 'create') {
|
||||
prependPost(e.record);
|
||||
} else if (e.action === 'update') {
|
||||
updatePost(e.record);
|
||||
} else if (e.action === 'delete') {
|
||||
removePost(e.record.id);
|
||||
}
|
||||
});
|
||||
|
||||
function prependPost(post) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'post';
|
||||
div.innerHTML = `<h3>${post.title}</h3><p>${post.content}</p>`;
|
||||
postsList.prepend(div);
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadPosts();
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Live Chat
|
||||
|
||||
```javascript
|
||||
// Subscribe to messages
|
||||
pb.collection('messages').subscribe('*', (e) => {
|
||||
if (e.action === 'create') {
|
||||
addMessageToUI(e.record);
|
||||
}
|
||||
});
|
||||
|
||||
// Send message
|
||||
async function sendMessage(content) {
|
||||
await pb.collection('messages').create({
|
||||
content: content,
|
||||
user: pb.authStore.model.id,
|
||||
room: roomId
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Notification System
|
||||
|
||||
```javascript
|
||||
// Subscribe to notifications
|
||||
pb.collection('notifications').subscribe('*', (e) => {
|
||||
if (e.action === 'create' && e.record.user_id === pb.authStore.model.id) {
|
||||
showNotification(e.record.message);
|
||||
updateBadge();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Collaborative Editing
|
||||
|
||||
```javascript
|
||||
// Subscribe to document changes
|
||||
pb.collection('documents').subscribe('DOCUMENT_ID', (e) => {
|
||||
if (e.action === 'update') {
|
||||
updateEditor(e.record.content);
|
||||
}
|
||||
});
|
||||
|
||||
// Debounce updates
|
||||
let updateTimeout;
|
||||
function onEditorChange(content) {
|
||||
clearTimeout(updateTimeout);
|
||||
updateTimeout = setTimeout(async () => {
|
||||
await pb.collection('documents').update('DOCUMENT_ID', {
|
||||
content: content
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Live Dashboard
|
||||
|
||||
```javascript
|
||||
// Subscribe to metrics changes
|
||||
pb.collection('metrics').subscribe('*', (e) => {
|
||||
if (e.action === 'update') {
|
||||
updateDashboard(e.record);
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to events
|
||||
pb.collection('events').subscribe('*', (e) => {
|
||||
if (e.action === 'create') {
|
||||
addEventToFeed(e.record);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Shopping Cart Updates
|
||||
|
||||
```javascript
|
||||
// Subscribe to cart changes
|
||||
pb.collection('cart_items').subscribe('*', (e) => {
|
||||
if (e.action === 'create' && e.record.user_id === pb.authStore.model.id) {
|
||||
updateCartCount();
|
||||
} else if (e.action === 'delete') {
|
||||
updateCartCount();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Authentication and Realtime
|
||||
|
||||
### Authenticated Subscriptions
|
||||
|
||||
```javascript
|
||||
// Subscribe only after authentication
|
||||
pb.collection('users').authWithPassword('email', 'password').then(() => {
|
||||
// Now subscribe to private data
|
||||
pb.collection('messages').subscribe('*', (e) => {
|
||||
// Will only receive messages user has access to
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Multiple User Types
|
||||
|
||||
```javascript
|
||||
// Different subscriptions based on role
|
||||
if (pb.authStore.model.role === 'admin') {
|
||||
// Admin sees all updates
|
||||
pb.collection('posts').subscribe('*', handleAdminUpdate);
|
||||
} else {
|
||||
// Regular users see limited updates
|
||||
pb.collection('posts').subscribe('*', handleUserUpdate);
|
||||
}
|
||||
```
|
||||
|
||||
## Filtering Realtime Events
|
||||
|
||||
```javascript
|
||||
// Client-side filtering
|
||||
pb.collection('posts').subscribe('*', (e) => {
|
||||
// Only show published posts
|
||||
if (e.record.status === 'published') {
|
||||
updateUI(e.record);
|
||||
}
|
||||
});
|
||||
|
||||
// Or use server-side rules (better)
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### 1. Limit Subscriptions
|
||||
|
||||
```javascript
|
||||
// Good - subscribe to specific records needed
|
||||
pb.collection('posts').subscribe('POST_ID', callback);
|
||||
|
||||
// Bad - subscribe to everything
|
||||
pb.collection('posts').subscribe('*', callback); // Only when necessary
|
||||
```
|
||||
|
||||
### 2. Unsubscribe When Done
|
||||
|
||||
```javascript
|
||||
useEffect(() => {
|
||||
const unsubscribe = pb.collection('posts').subscribe('*', callback);
|
||||
|
||||
return () => {
|
||||
unsubscribe(); // Clean up
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
|
||||
### 3. Batch UI Updates
|
||||
|
||||
```javascript
|
||||
// Instead of updating on every event
|
||||
pb.collection('posts').subscribe('*', (e) => {
|
||||
updateUI(e.record); // Triggers re-render every time
|
||||
});
|
||||
|
||||
// Batch updates
|
||||
const updates = [];
|
||||
pb.collection('posts').subscribe('*', (e) => {
|
||||
updates.push(e.record);
|
||||
|
||||
if (updates.length >= 10) {
|
||||
batchUpdateUI(updates);
|
||||
updates.length = 0;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Use Debouncing for Frequent Updates
|
||||
|
||||
```javascript
|
||||
let updateTimeout;
|
||||
|
||||
pb.collection('metrics').subscribe('*', (e) => {
|
||||
clearTimeout(updateTimeout);
|
||||
|
||||
updateTimeout = setTimeout(() => {
|
||||
updateDashboard();
|
||||
}, 100); // Update at most every 100ms
|
||||
});
|
||||
```
|
||||
|
||||
## Connection Management
|
||||
|
||||
### Reconnection Strategy
|
||||
|
||||
```javascript
|
||||
pb.realtime.connection.addListener('close', () => {
|
||||
// Attempt reconnection
|
||||
setTimeout(() => {
|
||||
pb.realtime.connect();
|
||||
}, 5000); // Reconnect after 5 seconds
|
||||
});
|
||||
```
|
||||
|
||||
### Manual Connection Control
|
||||
|
||||
```javascript
|
||||
// Disconnect
|
||||
pb.realtime.disconnect();
|
||||
|
||||
// Reconnect
|
||||
pb.realtime.connect();
|
||||
|
||||
// Check connection status
|
||||
const isConnected = pb.realtime.connection.isOpen;
|
||||
```
|
||||
|
||||
### Heartbeat
|
||||
|
||||
```javascript
|
||||
// Keep connection alive
|
||||
setInterval(() => {
|
||||
if (pb.realtime.connection.isOpen) {
|
||||
pb.realtime.send({ action: 'ping' });
|
||||
}
|
||||
}, 30000); // Every 30 seconds
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```javascript
|
||||
pb.collection('posts').subscribe('*', (e) => {
|
||||
try {
|
||||
handleEvent(e);
|
||||
} catch (error) {
|
||||
console.error('Error handling event:', error);
|
||||
// Don't let errors break the subscription
|
||||
}
|
||||
});
|
||||
|
||||
// Handle connection errors
|
||||
pb.realtime.connection.addListener('error', (error) => {
|
||||
console.error('Realtime connection error:', error);
|
||||
// Show error to user or attempt reconnection
|
||||
});
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Server-Side Security
|
||||
|
||||
Realtime events respect collection rules:
|
||||
|
||||
```javascript
|
||||
// Users will only receive events for records they can access
|
||||
// No need for additional client-side filtering based on permissions
|
||||
```
|
||||
|
||||
### Client-Side Validation
|
||||
|
||||
```javascript
|
||||
pb.collection('posts').subscribe('*', (e) => {
|
||||
// Validate event data
|
||||
if (!e.record || !e.action) {
|
||||
console.warn('Invalid event:', e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process event
|
||||
handleEvent(e);
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Not receiving events**
|
||||
- Check if subscribed to correct collection
|
||||
- Verify user is authenticated
|
||||
- Check console for errors
|
||||
- Ensure WebSocket connection is open
|
||||
|
||||
**Receiving too many events**
|
||||
- Unsubscribe from unnecessary subscriptions
|
||||
- Filter events client-side
|
||||
- Use more specific subscriptions
|
||||
|
||||
**Memory leaks**
|
||||
- Always unsubscribe in component cleanup
|
||||
- Check for duplicate subscriptions
|
||||
- Use useEffect cleanup function
|
||||
|
||||
**Disconnections**
|
||||
- Implement reconnection logic
|
||||
- Add heartbeat/ping
|
||||
- Show connection status to user
|
||||
|
||||
## Related Topics
|
||||
|
||||
- [Records API](api_records.md) - CRUD operations
|
||||
- [API Rules & Filters](../core/api_rules_filters.md) - Security
|
||||
- [Collections](../core/collections.md) - Collection setup
|
||||
637
skills/pocketbase/references/api/api_records.md
Normal file
637
skills/pocketbase/references/api/api_records.md
Normal file
@@ -0,0 +1,637 @@
|
||||
# Records API
|
||||
|
||||
## Overview
|
||||
|
||||
The Records API provides CRUD operations for collection records. It handles:
|
||||
- Creating records
|
||||
- Reading records (single or list)
|
||||
- Updating records
|
||||
- Deleting records
|
||||
- Batch operations
|
||||
- Real-time subscriptions
|
||||
|
||||
## Authentication
|
||||
|
||||
Most record operations require authentication:
|
||||
|
||||
```javascript
|
||||
// Include JWT token in requests
|
||||
const token = pb.authStore.token;
|
||||
|
||||
// Or use SDK which handles it automatically
|
||||
const pb = new PocketBase('http://127.0.0.1:8090');
|
||||
```
|
||||
|
||||
## Create Record
|
||||
|
||||
### Create Single Record
|
||||
|
||||
```javascript
|
||||
const record = await pb.collection('posts').create({
|
||||
title: 'My Post',
|
||||
content: 'Hello world!',
|
||||
author: pb.authStore.model.id,
|
||||
published: true
|
||||
});
|
||||
|
||||
// Returns full record with ID, timestamps, etc.
|
||||
console.log(record.id);
|
||||
console.log(record.created);
|
||||
console.log(record.updated);
|
||||
```
|
||||
|
||||
### Create with Files
|
||||
|
||||
```javascript
|
||||
const formData = new FormData();
|
||||
formData.append('title', 'Post with Image');
|
||||
formData.append('image', fileInput.files[0]);
|
||||
|
||||
const record = await pb.collection('posts').create(formData);
|
||||
const imageUrl = pb.files.getURL(record, record.image);
|
||||
```
|
||||
|
||||
### Create with Relations
|
||||
|
||||
```javascript
|
||||
const post = await pb.collection('posts').create({
|
||||
title: 'My Post',
|
||||
author: authorId, // User ID
|
||||
category: categoryId // Category ID
|
||||
});
|
||||
```
|
||||
|
||||
## Read Records
|
||||
|
||||
### Get Single Record
|
||||
|
||||
```javascript
|
||||
const record = await pb.collection('posts').getOne('RECORD_ID');
|
||||
|
||||
// Get with expand
|
||||
const record = await pb.collection('posts').getOne('RECORD_ID', {
|
||||
expand: 'author,comments'
|
||||
});
|
||||
```
|
||||
|
||||
### Get Multiple Records (List)
|
||||
|
||||
```javascript
|
||||
const records = await pb.collection('posts').getList(1, 50);
|
||||
|
||||
// Returns:
|
||||
// {
|
||||
// page: 1,
|
||||
// perPage: 50,
|
||||
// totalItems: 100,
|
||||
// totalPages: 2,
|
||||
// items: [ ... array of records ... ]
|
||||
// }
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
```javascript
|
||||
// Page 1
|
||||
const page1 = await pb.collection('posts').getList(1, 50);
|
||||
|
||||
// Page 2
|
||||
const page2 = await pb.collection('posts').getList(2, 50);
|
||||
|
||||
// Large perPage
|
||||
const all = await pb.collection('posts').getList(1, 200);
|
||||
|
||||
// Get all records (use carefully)
|
||||
const allRecords = await pb.collection('posts').getFullList();
|
||||
```
|
||||
|
||||
### Filtering
|
||||
|
||||
```javascript
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
filter: 'status = "published"'
|
||||
});
|
||||
|
||||
// Multiple conditions
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
filter: 'status = "published" && created >= "2024-01-01"'
|
||||
});
|
||||
|
||||
// With OR
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
filter: 'category = "tech" || category = "programming"'
|
||||
});
|
||||
|
||||
// By relation field
|
||||
const records = await pb.collection('comments').getList(1, 50, {
|
||||
filter: 'expand.post.title ~ "PocketBase"'
|
||||
});
|
||||
```
|
||||
|
||||
### Sorting
|
||||
|
||||
```javascript
|
||||
// Sort by created date descending
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
sort: '-created'
|
||||
});
|
||||
|
||||
// Sort by title ascending
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
sort: 'title'
|
||||
});
|
||||
|
||||
// Multiple fields
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
sort: 'status,-created' // status ascending, then created descending
|
||||
});
|
||||
```
|
||||
|
||||
### Field Selection
|
||||
|
||||
```javascript
|
||||
// Select specific fields
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
fields: 'id,title,author,created'
|
||||
});
|
||||
|
||||
// Exclude large fields
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
fields: 'id,title,author,created,-content'
|
||||
});
|
||||
|
||||
// Select with expand
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
fields: 'id,title,expand.author.name'
|
||||
});
|
||||
```
|
||||
|
||||
### Relation Expansion
|
||||
|
||||
```javascript
|
||||
// Expand single relation
|
||||
const posts = await pb.collection('posts').getList(1, 50, {
|
||||
expand: 'author'
|
||||
});
|
||||
|
||||
// Expand multiple relations
|
||||
const posts = await pb.collection('posts').getList(1, 50, {
|
||||
expand: 'author,comments'
|
||||
});
|
||||
|
||||
// Expand nested relations
|
||||
const comments = await pb.collection('comments').getList(1, 50, {
|
||||
expand: 'post.author'
|
||||
});
|
||||
|
||||
// Use expand in filters
|
||||
const posts = await pb.collection('posts').getList(1, 50, {
|
||||
expand: 'author',
|
||||
filter: 'expand.author.role = "admin"'
|
||||
});
|
||||
```
|
||||
|
||||
### Cursor-Based Pagination (PocketBase 0.20+)
|
||||
|
||||
```javascript
|
||||
// First page
|
||||
const page1 = await pb.collection('posts').getList(1, 50, {
|
||||
sort: 'created'
|
||||
});
|
||||
|
||||
// Get cursor (last item's sort value)
|
||||
const cursor = page1.items[page1.items.length - 1].created;
|
||||
|
||||
// Next page
|
||||
const page2 = await pb.collection('posts').getList(1, 50, {
|
||||
filter: `created < "${cursor}"`,
|
||||
sort: 'created'
|
||||
});
|
||||
```
|
||||
|
||||
## Update Record
|
||||
|
||||
### Update Single Record
|
||||
|
||||
```javascript
|
||||
const updated = await pb.collection('posts').update('RECORD_ID', {
|
||||
title: 'Updated Title',
|
||||
status: 'published'
|
||||
});
|
||||
|
||||
// Returns updated record
|
||||
console.log(updated.title);
|
||||
console.log(updated.updated);
|
||||
```
|
||||
|
||||
### Update with Files
|
||||
|
||||
```javascript
|
||||
const formData = new FormData();
|
||||
formData.append('title', 'Updated Post');
|
||||
formData.append('image', newFile); // Replace image
|
||||
// or
|
||||
formData.append('image', null); // Remove image
|
||||
|
||||
const updated = await pb.collection('posts').update('RECORD_ID', formData);
|
||||
```
|
||||
|
||||
### Update Relations
|
||||
|
||||
```javascript
|
||||
// Update relation
|
||||
const updated = await pb.collection('posts').update('RECORD_ID', {
|
||||
author: newAuthorId
|
||||
});
|
||||
|
||||
// Add to one-to-many relation
|
||||
const comment = await pb.collection('comments').create({
|
||||
post: postId,
|
||||
content: 'New comment'
|
||||
});
|
||||
|
||||
// Update comment
|
||||
await pb.collection('comments').update(comment.id, {
|
||||
content: 'Updated comment'
|
||||
});
|
||||
```
|
||||
|
||||
## Delete Record
|
||||
|
||||
```javascript
|
||||
// Delete single record
|
||||
await pb.collection('posts').delete('RECORD_ID');
|
||||
|
||||
// Returns true on success, throws on failure
|
||||
```
|
||||
|
||||
## Batch Operations
|
||||
|
||||
### Create Multiple Records
|
||||
|
||||
```javascript
|
||||
const records = await pb.collection('posts').createBatch([
|
||||
{
|
||||
title: 'Post 1',
|
||||
content: 'Content 1'
|
||||
},
|
||||
{
|
||||
title: 'Post 2',
|
||||
content: 'Content 2'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log(records.length); // 2
|
||||
```
|
||||
|
||||
### Update Multiple Records
|
||||
|
||||
```javascript
|
||||
const records = await pb.collection('posts').updateBatch([
|
||||
{
|
||||
id: 'RECORD_ID_1',
|
||||
title: 'Updated Title 1'
|
||||
},
|
||||
{
|
||||
id: 'RECORD_ID_2',
|
||||
title: 'Updated Title 2'
|
||||
}
|
||||
]);
|
||||
```
|
||||
|
||||
### Delete Multiple Records
|
||||
|
||||
```javascript
|
||||
await pb.collection('posts').deleteBatch([
|
||||
'RECORD_ID_1',
|
||||
'RECORD_ID_2',
|
||||
'RECORD_ID_3'
|
||||
]);
|
||||
```
|
||||
|
||||
## Real-time Subscriptions
|
||||
|
||||
### Subscribe to All Collection Changes
|
||||
|
||||
```javascript
|
||||
// Subscribe to all changes
|
||||
pb.collection('posts').subscribe('*', function (e) {
|
||||
console.log(e.action); // 'create', 'update', or 'delete'
|
||||
console.log(e.record); // Changed record
|
||||
|
||||
if (e.action === 'create') {
|
||||
console.log('New post created:', e.record);
|
||||
} else if (e.action === 'update') {
|
||||
console.log('Post updated:', e.record);
|
||||
} else if (e.action === 'delete') {
|
||||
console.log('Post deleted:', e.record);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Subscribe to Specific Record
|
||||
|
||||
```javascript
|
||||
// Subscribe to specific record
|
||||
pb.collection('posts').subscribe('RECORD_ID', function (e) {
|
||||
console.log('Record changed:', e.record);
|
||||
});
|
||||
```
|
||||
|
||||
### Unsubscribe
|
||||
|
||||
```javascript
|
||||
// Unsubscribe from specific record
|
||||
pb.collection('posts').unsubscribe('RECORD_ID');
|
||||
|
||||
// Unsubscribe from all collection changes
|
||||
pb.collection('posts').unsubscribe();
|
||||
|
||||
// Unsubscribe from all collections
|
||||
pb.collections.unsubscribe();
|
||||
```
|
||||
|
||||
### Real-time with React
|
||||
|
||||
```javascript
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
function PostsList() {
|
||||
const [posts, setPosts] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
loadPosts();
|
||||
|
||||
// Subscribe to real-time updates
|
||||
pb.collection('posts').subscribe('*', function (e) {
|
||||
if (e.action === 'create') {
|
||||
setPosts(prev => [e.record, ...prev]);
|
||||
} else if (e.action === 'update') {
|
||||
setPosts(prev => prev.map(p => p.id === e.record.id ? e.record : p));
|
||||
} else if (e.action === 'delete') {
|
||||
setPosts(prev => prev.filter(p => p.id !== e.record.id));
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
pb.collection('posts').unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function loadPosts() {
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
expand: 'author'
|
||||
});
|
||||
setPosts(records.items);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{posts.map(post => (
|
||||
<div key={post.id}>
|
||||
<h3>{post.title}</h3>
|
||||
<p>By {post.expand?.author?.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```javascript
|
||||
try {
|
||||
const record = await pb.collection('posts').getOne('INVALID_ID');
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
// Handle specific errors
|
||||
if (error.status === 404) {
|
||||
console.log('Record not found');
|
||||
} else if (error.status === 403) {
|
||||
console.log('Access denied');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Common Error Codes
|
||||
|
||||
- `400` - Bad Request (validation error)
|
||||
- `403` - Forbidden (access denied)
|
||||
- `404` - Not Found (record doesn't exist)
|
||||
- `422` - Unprocessable Entity (validation failed)
|
||||
|
||||
## REST API Reference
|
||||
|
||||
### Direct HTTP Requests
|
||||
|
||||
```javascript
|
||||
// Create
|
||||
fetch('http://127.0.0.1:8090/api/collections/posts/records', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${pb.authStore.token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: 'My Post',
|
||||
content: 'Hello world!'
|
||||
})
|
||||
});
|
||||
|
||||
// Read
|
||||
fetch('http://127.0.0.1:8090/api/collections/posts/records/RECORD_ID', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${pb.authStore.token}`
|
||||
}
|
||||
});
|
||||
|
||||
// Update
|
||||
fetch('http://127.0.0.1:8090/api/collections/posts/records/RECORD_ID', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${pb.authStore.token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: 'Updated Title'
|
||||
})
|
||||
});
|
||||
|
||||
// Delete
|
||||
fetch('http://127.0.0.1:8090/api/collections/posts/records/RECORD_ID', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${pb.authStore.token}`
|
||||
}
|
||||
});
|
||||
|
||||
// List
|
||||
fetch('http://127.0.0.1:8090/api/collections/posts/records?page=1&perPage=50', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${pb.authStore.token}`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Query Parameters for List
|
||||
|
||||
```
|
||||
GET /api/collections/{collection}/records
|
||||
|
||||
Query Parameters:
|
||||
- page : Page number (default: 1)
|
||||
- perPage : Items per page (default: 50, max: 500)
|
||||
- filter : Filter expression
|
||||
- sort : Sort expression
|
||||
- fields : Fields to return
|
||||
- expand : Relations to expand
|
||||
- skip : Number of records to skip (alternative to cursor)
|
||||
```
|
||||
|
||||
### Filtering Examples
|
||||
|
||||
```javascript
|
||||
// Via SDK
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
filter: 'status = "published" && views > 100'
|
||||
});
|
||||
|
||||
// Via REST
|
||||
fetch('http://127.0.0.1:8090/api/collections/posts/records?filter=(status="published" && views>100)')
|
||||
```
|
||||
|
||||
### Sorting Examples
|
||||
|
||||
```javascript
|
||||
// Via SDK
|
||||
const records = await pb.collection('posts').getList(1, 50, {
|
||||
sort: '-created,title'
|
||||
});
|
||||
|
||||
// Via REST
|
||||
fetch('http://127.0.0.1:8090/api/collections/posts/records?sort=-created,title')
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### 1. Use Pagination
|
||||
|
||||
```javascript
|
||||
// Instead of getting all records
|
||||
const all = await pb.collection('posts').getFullList(1000);
|
||||
|
||||
// Use pagination
|
||||
let page = 1;
|
||||
let allRecords = [];
|
||||
|
||||
while (true) {
|
||||
const records = await pb.collection('posts').getList(page, 50);
|
||||
allRecords = allRecords.concat(records.items);
|
||||
|
||||
if (page >= records.totalPages) break;
|
||||
page++;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Select Only Needed Fields
|
||||
|
||||
```javascript
|
||||
// Instead of fetching everything
|
||||
const posts = await pb.collection('posts').getList(1, 50);
|
||||
|
||||
// Select only needed fields
|
||||
const posts = await pb.collection('posts').getList(1, 50, {
|
||||
fields: 'id,title,author,created'
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Use Filters Efficiently
|
||||
|
||||
```javascript
|
||||
// Good - uses indexes
|
||||
const posts = await pb.collection('posts').getList(1, 50, {
|
||||
filter: 'status = "published" && created >= "2024-01-01"'
|
||||
});
|
||||
|
||||
// Slow - can't use indexes well
|
||||
const posts = await pb.collection('posts').getList(1, 50, {
|
||||
filter: 'title ~ ".*pattern.*"'
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Limit Expand Depth
|
||||
|
||||
```javascript
|
||||
// Good - limit to 2 levels
|
||||
const posts = await pb.collection('posts').getList(1, 50, {
|
||||
expand: 'author,comments'
|
||||
});
|
||||
|
||||
// Slower - 3 levels
|
||||
const posts = await pb.collection('posts').getList(1, 50, {
|
||||
expand: 'author,comments,comments.author'
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Use Batch Operations
|
||||
|
||||
```javascript
|
||||
// Instead of multiple requests
|
||||
await pb.collection('posts').create({ title: 'Post 1' });
|
||||
await pb.collection('posts').create({ title: 'Post 2' });
|
||||
await pb.collection('posts').create({ title: 'Post 3' });
|
||||
|
||||
// Use batch
|
||||
await pb.collection('posts').createBatch([
|
||||
{ title: 'Post 1' },
|
||||
{ title: 'Post 2' },
|
||||
{ title: 'Post 3' }
|
||||
]);
|
||||
```
|
||||
|
||||
## WebSocket Connections
|
||||
|
||||
### Manual WebSocket Connection
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://127.0.0.1:8090/api/realtime');
|
||||
|
||||
ws.onopen = function() {
|
||||
// Subscribe to collection
|
||||
ws.send(JSON.stringify({
|
||||
action: 'subscribe',
|
||||
collection: 'posts'
|
||||
}));
|
||||
};
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('Real-time update:', data);
|
||||
};
|
||||
```
|
||||
|
||||
### Connection Status
|
||||
|
||||
```javascript
|
||||
pb.realtime.connection.addListener('open', () => {
|
||||
console.log('Realtime connected');
|
||||
});
|
||||
|
||||
pb.realtime.connection.addListener('close', () => {
|
||||
console.log('Realtime disconnected');
|
||||
});
|
||||
|
||||
pb.realtime.connection.addListener('error', (error) => {
|
||||
console.log('Realtime error:', error);
|
||||
});
|
||||
```
|
||||
|
||||
## Related Topics
|
||||
|
||||
- [Collections](../core/collections.md) - Collection configuration
|
||||
- [API Rules & Filters](../core/api_rules_filters.md) - Security and filtering
|
||||
- [Authentication](../core/authentication.md) - User authentication
|
||||
- [Working with Relations](../core/working_with_relations.md) - Relations
|
||||
- [Real-time API](api_realtime.md) - WebSocket subscriptions
|
||||
- [Files API](api_files.md) - File uploads
|
||||
97
skills/pocketbase/references/api/api_settings.md
Normal file
97
skills/pocketbase/references/api/api_settings.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Settings API
|
||||
|
||||
## Overview
|
||||
|
||||
The Settings API allows you to manage PocketBase application settings including app configuration, CORS, SMTP, admin accounts, and more.
|
||||
|
||||
## Get All Settings
|
||||
|
||||
```http
|
||||
GET /api/settings
|
||||
Authorization: Bearer {admin_token}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"appName": "My App",
|
||||
"appUrl": "http://localhost:8090",
|
||||
"hideControls": false,
|
||||
"pageDirection": "ltr",
|
||||
"default.lang": "en",
|
||||
"smtp": {
|
||||
"enabled": false,
|
||||
"host": "",
|
||||
"port": 587,
|
||||
"username": "",
|
||||
"password": "",
|
||||
"tls": true,
|
||||
"fromEmail": "",
|
||||
"fromName": ""
|
||||
},
|
||||
"cors": {
|
||||
"enabled": true,
|
||||
"allowedOrigins": ["http://localhost:3000"],
|
||||
"allowedMethods": ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
||||
"allowedHeaders": ["Content-Type", "Authorization"]
|
||||
},
|
||||
"auth": {
|
||||
"passwordMinLength": 8,
|
||||
"passwordUppercase": false,
|
||||
"passwordLowercase": false,
|
||||
"passwordNumbers": false,
|
||||
"passwordSymbols": false,
|
||||
"requireEmailVerification": true,
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": false,
|
||||
"onlyEmailDomains": [],
|
||||
"exceptEmailDomains": [],
|
||||
"manageAccounts": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Update Settings
|
||||
|
||||
```http
|
||||
PATCH /api/settings
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"appName": "My App",
|
||||
"appUrl": "https://myapp.com",
|
||||
"cors": {
|
||||
"allowedOrigins": ["https://myapp.com", "https://admin.myapp.com"]
|
||||
},
|
||||
"smtp": {
|
||||
"enabled": true,
|
||||
"host": "smtp.gmail.com",
|
||||
"port": 587,
|
||||
"username": "noreply@myapp.com",
|
||||
"password": "password",
|
||||
"tls": true,
|
||||
"fromEmail": "noreply@myapp.com",
|
||||
"fromName": "My App"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test SMTP Configuration
|
||||
|
||||
```http
|
||||
POST /api/settings/test/smtp
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer {admin_token}
|
||||
|
||||
{
|
||||
"to": "test@example.com",
|
||||
"subject": "Test Email",
|
||||
"html": "<p>This is a test email</p>"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Note:** This is a placeholder file. See [core/going_to_production.md](../core/going_to_production.md) for configuration guidance.
|
||||
Reference in New Issue
Block a user