Benefits
Why Use Builder?
@ibnlanre/builder transforms how you manage keys in your application. Instead of scattering string literals and manual key construction throughout your code, you define everything once and let Builder handle the rest.
Type Safety
Without Builder:
// String keys are prone to mistakes
useQuery({
queryKey: ['users', userId],
queryFn: fetchUser
}); // Missing 'detail' segment
useQuery({
queryKey: ['user', 'detail', userId],
queryFn: fetchUser
}); // Singular instead of plural
useQuery({
queryKey: ['users', 'detail'],
queryFn: fetchUser
}); // Forgot the userId parameterWith Builder:
const api = createBuilder({
users: {
detail: (id: number) => fetchUser(id)
}
});
// TypeScript catches all of these:
api.users.detail.$use(); // ❌ Error: Expected 1 argument
api.users.detial.$use(userId); // ❌ Error: 'detial' doesn't exist
api.users.detail.$use('abc'); // ❌ Error: Expected number, got string
api.users.detail.$use(userId); // ✅ Type-safe and correctSimplicity & Efficiency
Without Builder:
// Repetitive and fragile code
const userListKey = ['users', 'list', { page, filter }];
const userDetailKey = ['users', 'detail', userId];
const userCreateKey = ['users', 'create'];
// Functions live in different files
const fetchUsers = (page, filter) => fetch(...); // users/api.ts
const fetchUser = (id) => fetch(...); // users/detail-api.tsWith Builder:
// Everything in one place
const api = createBuilder({
users: {
list: (page: number, filter: string) => fetch(`/users?page=${page}&filter=${filter}`),
detail: (id: number) => fetch(`/users/${id}`),
create: (data: User) => fetch('/users', { method: 'POST', body: JSON.stringify(data) })
}
});
// Use anywhere
api.users.list.$use(1, 'active'); // Key generation
api.$use.users.list(1, 'active'); // Function callEasy Refactoring
Without Builder:
// Changing 'users' to 'accounts' requires updating dozens of files:
// File 1
useQuery({
queryKey: ['users', 'list'],
queryFn: fetchUsers
});
// File 2
useQuery({
queryKey: ['users', 'detail', id],
queryFn: () => fetchUser(id)
});
// File 3
useMutation({
mutationKey: ['users', 'create'],
mutationFn: createUser
});
// ... 20 more filesWith Builder:
// Change once, update everywhere
const api = createBuilder({
accounts: { // Changed from 'users' to 'accounts'
list: ...,
detail: ...,
create: ...
}
});
// All usages automatically reflect the change
api.accounts.list.$use(); // ['accounts', 'list']
api.accounts.detail.$use(5); // ['accounts', 'detail', 5]Self-Documenting Code
Without Builder:
// What does this key do? Where's the function?
useQuery({
queryKey: ['posts', 'feed', 'trending', 10],
queryFn: mysteryFunction
});With Builder:
// Structure serves as documentation
const api = createBuilder({
posts: {
feed: {
trending: (limit: number) => fetchTrendingPosts(limit),
recent: () => fetchRecentPosts(),
following: (userId: number) => fetchFollowingPosts(userId)
}
}
});
// Clear what each key represents
api.posts.feed.trending.$use(10); // ✅ Self-explanatoryConsistency Across Teams
Without Builder:
// Different developers, different key patterns
Developer A: ['user-list', page]
Developer B: ['users', 'list', { page }]
Developer C: ['userList', { page, limit }]With Builder:
// One source of truth for the whole team
const api = createBuilder({
users: {
list: (params: { page: number; limit?: number }) => fetchUsers(params)
}
});
// Everyone uses the same keys
api.users.list.$use({ page: 1 }); // ✅ Consistent everywhereZero Runtime Overhead
Builder is incredibly lightweight (0.4KB gzipped (opens in a new tab)) and adds virtually no performance cost:
// Builder just creates a proxy object
// Key generation is a simple array operation
api.users.detail.$use(123); // ['users', 'detail', 123]
// No heavy transformations or processing
// Works great even in performance-critical applicationsFramework Agnostic
Works seamlessly with any library or framework:
// TanStack Query
useQuery({
queryKey: api.posts.list.$use(),
queryFn: api.$use.posts.list
});
// SWR
useSWR(api.$get('posts.list'), api.$use.posts.list);
// Zustand
const useStore = create((set) => ({
[api.$get('user.profile')]: null
}));
// Plain JavaScript
const cacheKey = api.products.detail.$use(id).join('.');
localStorage.setItem(cacheKey, data);Great for Teams of Any Size
Large Teams
- Enforces consistent patterns across the codebase
- Makes onboarding easier with self-documenting code
- Reduces code review friction (no more debates about key formats)
- Simplifies refactoring when requirements change
Small Teams & Solo Developers
- Speeds up development with autocomplete
- Catches bugs before runtime
- Reduces mental overhead of remembering key patterns
- Makes code easier to maintain long-term
Summary
| Benefit | Without Builder | With Builder |
|---|---|---|
| Type Safety | ❌ Manual checks | ✅ Compile-time validation |
| Refactoring | ❌ Find & replace strings | ✅ Change once, update everywhere |
| Consistency | ❌ Varies by developer | ✅ Enforced by structure |
| Documentation | ❌ External docs needed | ✅ Self-documenting |
| Autocomplete | ❌ None | ✅ Full IDE support |
| Bundle Size | N/A | ✅ 0.4KB gzipped |
Next Steps
Ready to experience these benefits in your project?
- Install Builder and set up your first register
- Learn basic usage to start building
- Explore real-world examples with data fetching libraries