Files
gh-whamp-whamp-claude-tools…/skills/pocketbase/references/api/api_records.md
2025-11-30 09:06:02 +08:00

13 KiB

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:

// 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

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

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

const post = await pb.collection('posts').create({
  title: 'My Post',
  author: authorId,  // User ID
  category: categoryId  // Category ID
});

Read Records

Get Single Record

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)

const records = await pb.collection('posts').getList(1, 50);

// Returns:
// {
//   page: 1,
//   perPage: 50,
//   totalItems: 100,
//   totalPages: 2,
//   items: [ ... array of records ... ]
// }

Pagination

// 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

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

// 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

// 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

// 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+)

// 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

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

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

// 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

// Delete single record
await pb.collection('posts').delete('RECORD_ID');

// Returns true on success, throws on failure

Batch Operations

Create Multiple Records

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

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

await pb.collection('posts').deleteBatch([
  'RECORD_ID_1',
  'RECORD_ID_2',
  'RECORD_ID_3'
]);

Real-time Subscriptions

Subscribe to All Collection Changes

// 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

// Subscribe to specific record
pb.collection('posts').subscribe('RECORD_ID', function (e) {
  console.log('Record changed:', e.record);
});

Unsubscribe

// 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

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

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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

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

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);
});