4.2 KiB
4.2 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Use getFirstListItem for Single Record Lookups | MEDIUM | Cleaner code, automatic error handling for not found | 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):
// 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):
// 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:
// 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:
// 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