Your First Query
Learn how to build type-safe GraphQL queries with Zeus.
Anatomy of a Zeus Query
A Zeus query consists of three parts:
const result = await chain('query')({
// 1. Field name
user: [
// 2. Arguments (optional)
{ id: '123' },
// 3. Selection set
{
id: true,
name: true,
email: true,
},
],
});Simple Query (No Arguments)
For fields without arguments, use a simple selection object:
import { Chain } from './zeus';
const chain = Chain('https://api.com/graphql');
// Query without arguments
const result = await chain('query')({
currentUser: {
id: true,
name: true,
email: true,
role: true,
},
});
// Access the typed result
console.log(result.currentUser.name); // ✅ Fully typedQuery with Arguments
Pass arguments as the first element of an array:
const result = await chain('query')({
user: [
// Arguments object
{ id: '123' },
// Selection set
{
id: true,
name: true,
email: true,
},
],
});Multiple Root Fields
Query multiple fields at once:
const result = await chain('query')({
currentUser: {
id: true,
name: true,
},
posts: [
{ limit: 10 },
{
id: true,
title: true,
},
],
categories: {
id: true,
name: true,
},
});
// Access each field
console.log(result.currentUser.name);
console.log(result.posts);
console.log(result.categories);Nested Selections
Select nested fields with full type safety:
const result = await chain('query')({
user: [
{ id: '123' },
{
id: true,
name: true,
// Nested selection
posts: {
id: true,
title: true,
// Deeply nested selection
comments: {
id: true,
content: true,
author: {
name: true,
avatar: true,
},
},
},
},
],
});
// Fully typed nested access
const firstPost = result.user.posts[0];
const firstComment = firstPost.comments[0];
console.log(firstComment.author.name); // ✅ All typed!Optional Fields
Handle nullable fields safely:
const result = await chain('query')({
user: [
{ id: '123' },
{
id: true,
name: true,
bio: true, // bio might be null
avatar: true, // avatar might be null
},
],
});
// TypeScript knows these might be null/undefined
const bio = result.user.bio ?? 'No bio';
const avatar = result.user.avatar ?? '/default-avatar.png';Understanding the Chain
The chain function is your main entry point:
import { Chain } from './zeus';
// Create a chain instance
const chain = Chain('https://api.com/graphql');
// The chain accepts operation type
chain('query'); // For queries
chain('mutation'); // For mutations
chain('subscription'); // For subscriptionsType-Safe Arguments
Zeus validates argument types at compile time:
// ✅ Correct - id is a string
const result1 = await chain('query')({
user: [{ id: '123' }, { name: true }],
});
// ❌ Error - id should be string, not number
const result2 = await chain('query')({
user: [{ id: 123 }, { name: true }], // TypeScript error!
});
// ✅ Complex arguments are typed too
const result3 = await chain('query')({
users: [
{
filter: {
role: 'ADMIN',
active: true,
createdAfter: '2023-01-01',
},
limit: 10,
offset: 0,
},
{
id: true,
name: true,
},
],
});Type-Safe Response
The response is automatically typed:
const result = await chain('query')({
user: [
{ id: '123' },
{
id: true,
name: true,
email: true,
posts: {
id: true,
title: true,
},
},
],
});
// TypeScript knows the exact shape
type UserResult = typeof result.user;
// {
// id: string;
// name: string;
// email: string;
// posts: Array<{
// id: string;
// title: string;
// }>;
// }Error Handling
Handle GraphQL errors properly:
try {
const result = await chain('query')({
user: [{ id: '123' }, { name: true }],
});
console.log(result.user.name);
} catch (error) {
if (error instanceof Error) {
console.error('Query failed:', error.message);
}
}Variables Pattern
While Zeus uses direct arguments, you can still extract variables:
// Define your variables
const userId = '123';
const postLimit = 10;
// Use in query
const result = await chain('query')({
user: [
{ id: userId },
{
name: true,
posts: [
{ limit: postLimit },
{
title: true,
},
],
},
],
});Aliases
GraphQL aliases work naturally:
const result = await chain('query')({
// Use same field with different arguments
adminUsers: [
{ role: 'ADMIN' },
{
__alias: 'adminUsers', // Explicit alias
id: true,
name: true,
},
],
regularUsers: [
{ role: 'USER' },
{
__alias: 'regularUsers',
id: true,
name: true,
},
],
});
console.log(result.adminUsers);
console.log(result.regularUsers);Fragments with Selectors
Use Selectors for reusable selection sets:
import { Chain, Selector } from './zeus';
const chain = Chain('https://api.com/graphql');
// Define reusable selector
const userFields = Selector('User')({
id: true,
name: true,
email: true,
avatar: true,
});
// Use in query
const result = await chain('query')({
user: [{ id: '123' }, userFields],
});Real-World Example
Here’s a complete example with error handling and types:
import { Chain } from './zeus';
const chain = Chain('https://api.com/graphql', {
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
},
});
interface GetUserPostsParams {
userId: string;
limit?: number;
}
async function getUserPosts({ userId, limit = 10 }: GetUserPostsParams) {
try {
const result = await chain('query')({
user: [
{ id: userId },
{
id: true,
name: true,
email: true,
posts: [
{ limit, orderBy: 'CREATED_DESC' },
{
id: true,
title: true,
content: true,
createdAt: true,
author: {
name: true,
},
tags: {
name: true,
},
},
],
},
],
});
return result.user;
} catch (error) {
console.error('Failed to fetch user posts:', error);
throw error;
}
}
// Usage
getUserPosts({ userId: '123', limit: 5 })
.then((user) => {
console.log(`${user.name}'s posts:`);
user.posts.forEach((post) => {
console.log(`- ${post.title} by ${post.author.name}`);
});
})
.catch(console.error);Best Practices
- Always specify fields - Don’t rely on defaults
- Use Selectors - For reusable selection sets
- Handle nulls - Use optional chaining or null coalescing
- Type your functions - Extract query logic into typed functions
- Error handling - Always wrap in try/catch
- Extract variables - Don’t hardcode values in queries
Common Patterns
Conditional Fields
const includeEmail = true;
const result = await chain('query')({
user: [
{ id: '123' },
{
id: true,
name: true,
...(includeEmail && { email: true }),
},
],
});Pagination
async function fetchPage(page: number, pageSize: number) {
return chain('query')({
users: [
{
limit: pageSize,
offset: page * pageSize,
},
{
id: true,
name: true,
},
],
});
}Search
async function searchUsers(query: string) {
return chain('query')({
users: [
{
where: {
name: { contains: query },
},
},
{
id: true,
name: true,
email: true,
},
],
});
}Next Steps
- Chain Client - Advanced client configuration
- Selectors - Reusable selection patterns
- Mutations - Modify data
- Type Inference - Deep dive into types
Last updated on