Hi! @mink, I understand the issue you’re facing with TypeScript types when querying related data in Directus. Here are the best solutions:
Solution 1: Let TypeScript Infer Types Automatically (Recommended)
This is the simplest approach. TypeScript will automatically infer the correct types based on your field query:
const players = await directus.request(
readItems('players', {
fields: ['name', { pets: ['name'] }]
})
);
// TypeScript now knows pets contains objects with name
const petName = players[0].pets?.[0]?.name; // ✅ No errors!
Solution 2: Create Specific Response Types
Create interfaces that match your expected response structure:
interface PlayerWithPetNames {
id: string;
name: string;
pets: { name: string }[] | null;
}
const players = await directus.request(
readItems('players', {
fields: ['name', { pets: ['name'] }]
})
) as PlayerWithPetNames[];
Solution 3: Use Type Guards
Create a helper function to check types at runtime:
function isPetObject(pet: string | Pet): pet is Pet {
return typeof pet !== 'string' && 'name' in pet;
}
// In your component:
<div>
{player.pets?.[0] && isPetObject(player.pets[0])
? player.pets[0].name
: 'No pet name'}
</div>
Solution 4: Generic Query Response Type (Advanced)
For complex applications, create a utility type:
type QueryResponse<T> = {
[K in keyof T]: T[K] extends Array<infer U>
? U extends object
? QueryResponse<U>[]
: U[]
: T[K];
};
const players = await directus.request(
readItems('players', {
fields: ['name', { pets: ['name'] }]
})
);
Recommendation
Start with Solution 1 - it’s the cleanest and most maintainable. TypeScript’s automatic type inference will ensure your types always match what you actually requested in your query.
If you need to use the Player type elsewhere in your app, consider creating separate interfaces for your base schema types and your query response types to avoid conflicts.
// For schema definition
interface PlayerBase {
id: string;
name: string;
pets: string[] | Pet[];
}
// For specific queries
interface PlayerWithPets extends Omit<PlayerBase, 'pets'> {
pets: { name: string }[];
}
This keeps your types accurate and your code error-free!