159 lines
4.2 KiB
Markdown
159 lines
4.2 KiB
Markdown
---
|
|
title: Use getFirstListItem for Single Record Lookups
|
|
impact: MEDIUM
|
|
impactDescription: Cleaner code, automatic error handling for not found
|
|
tags: query, lookup, find, getFirstListItem
|
|
---
|
|
|
|
## Use getFirstListItem for Single Record Lookups
|
|
|
|
Use `getFirstListItem()` when you need to find a single record by a field value other than ID. It's cleaner than `getList()` with limit 1 and provides proper error handling.
|
|
|
|
**Incorrect (manual single-record lookup):**
|
|
|
|
```javascript
|
|
// Using getList with limit 1 - verbose
|
|
async function findUserByEmail(email) {
|
|
const result = await pb.collection('users').getList(1, 1, {
|
|
filter: pb.filter('email = {:email}', { email })
|
|
});
|
|
|
|
if (result.items.length === 0) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
return result.items[0];
|
|
}
|
|
|
|
// Using getFullList then filtering - wasteful
|
|
async function findUserByUsername(username) {
|
|
const users = await pb.collection('users').getFullList({
|
|
filter: pb.filter('username = {:username}', { username })
|
|
});
|
|
return users[0]; // Might be undefined!
|
|
}
|
|
|
|
// Fetching by ID when you have a different identifier
|
|
async function findProductBySku(sku) {
|
|
// Wrong: getOne expects the record ID
|
|
const product = await pb.collection('products').getOne(sku); // Fails!
|
|
}
|
|
```
|
|
|
|
**Correct (using getFirstListItem):**
|
|
|
|
```javascript
|
|
// Clean single-record lookup by any field
|
|
async function findUserByEmail(email) {
|
|
try {
|
|
const user = await pb.collection('users').getFirstListItem(
|
|
pb.filter('email = {:email}', { email })
|
|
);
|
|
return user;
|
|
} catch (error) {
|
|
if (error.status === 404) {
|
|
return null; // Not found
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Lookup by unique field
|
|
async function findProductBySku(sku) {
|
|
return pb.collection('products').getFirstListItem(
|
|
pb.filter('sku = {:sku}', { sku })
|
|
);
|
|
}
|
|
|
|
// Lookup with expand
|
|
async function findOrderByNumber(orderNumber) {
|
|
return pb.collection('orders').getFirstListItem(
|
|
pb.filter('orderNumber = {:num}', { num: orderNumber }),
|
|
{ expand: 'customer,items' }
|
|
);
|
|
}
|
|
|
|
// Complex filter conditions
|
|
async function findActiveSubscription(userId) {
|
|
return pb.collection('subscriptions').getFirstListItem(
|
|
pb.filter(
|
|
'user = {:userId} && status = "active" && expiresAt > @now',
|
|
{ userId }
|
|
)
|
|
);
|
|
}
|
|
|
|
// With field selection
|
|
async function getUserIdByEmail(email) {
|
|
const user = await pb.collection('users').getFirstListItem(
|
|
pb.filter('email = {:email}', { email }),
|
|
{ fields: 'id' }
|
|
);
|
|
return user.id;
|
|
}
|
|
```
|
|
|
|
**Comparison with getOne:**
|
|
|
|
```javascript
|
|
// getOne - fetch by record ID
|
|
const post = await pb.collection('posts').getOne('abc123');
|
|
|
|
// getFirstListItem - fetch by any filter (use pb.filter for safe binding)
|
|
const post = await pb.collection('posts').getFirstListItem(
|
|
pb.filter('slug = {:slug}', { slug: 'hello-world' })
|
|
);
|
|
const user = await pb.collection('users').getFirstListItem(
|
|
pb.filter('username = {:name}', { name: 'john' })
|
|
);
|
|
const order = await pb.collection('orders').getFirstListItem(
|
|
pb.filter('orderNumber = {:num}', { num: 12345 })
|
|
);
|
|
```
|
|
|
|
**Error handling:**
|
|
|
|
```javascript
|
|
// getFirstListItem throws 404 if no match found
|
|
try {
|
|
const user = await pb.collection('users').getFirstListItem(
|
|
pb.filter('email = {:email}', { email })
|
|
);
|
|
return user;
|
|
} catch (error) {
|
|
if (error.status === 404) {
|
|
// No matching record - handle appropriately
|
|
return null;
|
|
}
|
|
// Other error (network, auth, etc.)
|
|
throw error;
|
|
}
|
|
|
|
// Wrapper function for optional lookup
|
|
async function findFirst(collection, filter, options = {}) {
|
|
try {
|
|
return await pb.collection(collection).getFirstListItem(filter, options);
|
|
} catch (error) {
|
|
if (error.status === 404) return null;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
const user = await findFirst('users', pb.filter('email = {:e}', { e: email }));
|
|
if (!user) {
|
|
console.log('User not found');
|
|
}
|
|
```
|
|
|
|
**When to use each method:**
|
|
|
|
| Method | Use When |
|
|
|--------|----------|
|
|
| `getOne(id)` | You have the record ID |
|
|
| `getFirstListItem(filter)` | Finding by unique field (email, slug, sku) |
|
|
| `getList(1, 1, { filter })` | Need pagination metadata |
|
|
| `getFullList({ filter })` | Expecting multiple results |
|
|
|
|
Reference: [PocketBase Records API](https://pocketbase.io/docs/api-records/)
|