Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:06:02 +08:00
commit 02cab85880
53 changed files with 12367 additions and 0 deletions

View 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.

View 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.

View 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.

View 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.

View 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.

View 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.

View 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

View 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

View 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.