In modern application development, data isn't a monolith. It’s a constellation of databases, third-party services, internal APIs, and data warehouses. The challenge isn't just accessing this data; it's retrieving it intelligently, efficiently, and consistently. One-off scripts and tightly-coupled service logic create a brittle system that's difficult to maintain and scale.
The solution is to stop thinking about data retrieval as a series of isolated queries and start thinking about it as a collection of composable, intelligent services. This is the core philosophy behind Searches.do — a platform for building, deploying, and managing data retrieval services as code. By defining your data logic as reusable Search Agents, you can create a powerful, flexible API layer that unlocks your data's true potential.
Let's explore five powerful patterns, from simple lookups to complex AI-driven workflows, that you can implement with Searches.do to build a truly composable data retrieval architecture.
The simplest yet most fundamental pattern is the Direct Lookup. This is an atomic service that encapsulates the logic for retrieving a specific piece of data based on a well-defined key. It’s the "noun" of your data layer—findUser, getProduct, getArticle.
Why it's powerful:
Implementation with Searches.do:
This pattern is the bedrock of the platform. You define the expected parameters and the handler logic to fetch the data.
import { Search } from 'searches.do';
import { db } from './your-database-connector';
const findUserByEmail = new Search({
name: 'Find User By Email',
description: 'Retrieves a single user record by their email address.',
parameters: {
email: { type: 'string', required: true, format: 'email' }
},
handler: async ({ email }) => {
// Your specific data retrieval logic is encapsulated here
const user = await db.collection('users').findOne({ email });
if (!user) {
throw new Error('User not found.');
}
return user;
}
});
// This is now a production-ready API:
// POST /findUserByEmail
// { "email": "jane.doe@example.com" }
Rarely does one piece of data tell the whole story. The Aggregator pattern creates a single, unified view by fanning out to multiple data sources and combining the results. Think of it as a Backend-for-Frontend (BFF) pattern, where you build a tailored data shape for a specific UI component, like a user dashboard.
Why it's powerful:
Implementation with Searches.do:
Your search handler orchestrates calls to different data sources and composes the final response.
import { Search } from 'searches.do';
import { usersDb, ordersApi, supportApi } from './data-sources';
const getUserDashboard = new Search({
name: 'Get User Dashboard',
description: 'Aggregates user profile, recent orders, and support tickets.',
parameters: {
userId: { type: 'string', required: true }
},
handler: async ({ userId }) => {
// Fire off all data requests in parallel for performance
const [profile, recentOrders, ticketCount] = await Promise.all([
usersDb.collection('profiles').findOne({ userId }),
ordersApi.get(`/users/${userId}/orders?limit=5`),
supportApi.countTicketsForUser(userId)
]);
return {
profile,
recentOrders,
ticketCount
};
}
});
Some business questions can't be answered in a single step. The Chained Workflow pattern executes a sequence of data lookups where the output of one step becomes the input for the next. This allows you to encapsulate complex, multi-step business logic into a single API call.
Why it's powerful:
Implementation with Searches.do:
The handler function becomes a small, readable script that describes the sequence of operations.
import { Search } from 'searches.do';
import { salesDb, customersDb } from './data-sources';
const findTopCustomersOfBestProduct = new Search({
name: 'Find Top Customers Of Best Product',
description: 'Identifies the best-selling product and returns its top customers.',
handler: async () => {
// Step 1: Find the best-selling product
const bestProduct = await salesDb.getBestSellingProduct();
if (!bestProduct) {
return [];
}
// Step 2: Use the result of Step 1 to find its top customers
const topCustomers = await customersDb.findTopPurchasers(bestProduct.id, 5);
return {
product: bestProduct,
customers: topCustomers
};
}
});
A truly flexible data layer can adapt to its inputs. The Conditional Fetch pattern uses branching logic within a search to decide how or where to retrieve data. This allows you to create a single, intelligent endpoint that can handle multiple variations of a request.
Why it's powerful:
Implementation with Searches.do:
Simple if/else or switch statements inside your handler can direct the flow of data retrieval.
import { Search } from 'searches.do';
import { fastCache, mainDatabase } from './data-sources';
const getDocument = new Search({
name: 'Get Document',
description: 'Retrieves a document, optionally from the cache.',
parameters: {
documentId: { type: 'string', required: true },
fromCache: { type: 'boolean', default: true }
},
handler: async ({ documentId, fromCache }) => {
// Conditionally decide which data source to use
if (fromCache) {
const cachedDoc = await fastCache.get(documentId);
if (cachedDoc) return cachedDoc;
}
// Fall back to the main database if not in cache or if cache is disabled
const doc = await mainDatabase.findDocumentById(documentId);
return doc;
}
});
This is the ultimate evolution of data retrieval—transforming natural language questions into executable queries. The Agentic Search pattern integrates a Large Language Model (LLM) to parse a user's intent, construct a precise query plan, execute it against your data sources, and return a human-readable answer.
Why it's powerful:
Implementation with Searches.do:
Your search handler acts as an AI agent, using an LLM to interpret the request and then fetching the data accordingly.
import { Search } from 'searches.do';
import { llm } from './ai-provider';
import { analyticsDb } from './data-sources';
const askAnalytics = new Search({
name: 'Ask Analytics',
description: 'Answers natural language questions about business analytics.',
parameters: {
question: { type: 'string', required: true }
},
handler: async ({ question }) => {
// Step 1: Use an LLM to translate the question into a structured query
const prompt = `Based on our schema (sales, customers), translate this question into a SQL query: "${question}"`;
const sqlQuery = await llm.generateSql(prompt);
// Step 2: Safely execute the generated query
const results = await analyticsDb.execute(sqlQuery);
// Step 3 (Optional): Use the LLM to summarize the results in plain English
const summary = await llm.summarize(`Results for "${question}": ${JSON.stringify(results)}`);
return {
summary,
rawData: results
};
}
});
// Now you can power a search bar with:
// POST /askAnalytics
// { "question": "What were our total sales in Q2 for the European region?" }
These five patterns provide a roadmap for evolving your data access strategy. By starting with simple, reusable Direct Lookups and progressively composing them into more complex Aggregators, Workflows, and even AI-powered Agents, you can build a robust and scalable data retrieval layer.
With Searches.do, each pattern becomes a managed, versionable, and secure API endpoint. You stop writing one-off scripts and start building a library of intelligent data services that power your entire organization.
Ready to unlock your data? Get started with Searches.do and transform your queries into code.