Skip to main content
This guide walks you through creating your first type-safe filter with filter-def. We’ll start with in-memory filtering and show examples for all adapters.

Prerequisites

  • Node.js 18+
  • TypeScript 5.0+
  • Basic TypeScript knowledge

In-Memory Filtering

Let’s build a user filter for an in-memory array.
1

Install the package

Install @filter-def/in-memory:
npm install @filter-def/in-memory
2

Define your entity type

Create a TypeScript interface for your data:
interface User {
    name: string;
    email: string;
    age: number;
    isActive: boolean;
}
3

Create a filter definition

Import inMemoryFilter and define your filter schema:
import { inMemoryFilter } from "@filter-def/in-memory";

const userFilter = inMemoryFilter<User>().def({
    name: { kind: "eq" },
    emailContains: { kind: "contains", field: "email" },
    minAge: { kind: "gte", field: "age" },
    isActive: { kind: "eq" },
});
When the filter name matches the field name (like name and isActive), the field property is automatically inferred!
4

Create test data

Set up an array of users:
const users: User[] = [
    { name: "John Doe", email: "[email protected]", age: 30, isActive: true },
    { name: "Jane Doe", email: "[email protected]", age: 25, isActive: true },
    { name: "Bob Smith", email: "[email protected]", age: 40, isActive: false },
    { name: "Alice Johnson", email: "[email protected]", age: 28, isActive: true },
];
5

Apply the filter

Use the filter with native array methods:
// Find all active users with @example.com emails
const results = users.filter(
    userFilter({
        emailContains: "@example.com",
        isActive: true,
    })
);

console.log(results);
// Output: [
//   { name: "John Doe", email: "[email protected]", age: 30, isActive: true },
//   { name: "Jane Doe", email: "[email protected]", age: 25, isActive: true },
//   { name: "Alice Johnson", email: "[email protected]", age: 28, isActive: true }
// ]
6

Try different filters

All filter properties are optional. Try different combinations:
// Find users 30 or older
const older = users.filter(
    userFilter({ minAge: 30 })
);

// Find a specific user
const john = users.find(
    userFilter({ name: "John Doe" })
);

// Check if any user matches
const hasInactive = users.some(
    userFilter({ isActive: false })
);

// Combine multiple conditions
const specific = users.filter(
    userFilter({
        emailContains: "example",
        minAge: 28,
        isActive: true,
    })
);

Drizzle ORM (SQL Databases)

Use the same filter patterns with Drizzle ORM for SQL databases.
1

Install dependencies

Install the Drizzle adapter and Drizzle ORM:
npm install @filter-def/drizzle drizzle-orm
You’ll also need a database driver (e.g., pg for PostgreSQL, better-sqlite3 for SQLite).
2

Define your database schema

Create a Drizzle table schema:
import { pgTable, text, integer, boolean } from "drizzle-orm/pg-core";

const usersTable = pgTable("users", {
    id: integer("id").primaryKey(),
    name: text("name").notNull(),
    email: text("email").notNull(),
    age: integer("age").notNull(),
    isActive: boolean("is_active").notNull().default(true),
});
3

Create a filter definition

Import drizzleFilter and pass your table schema:
import { drizzleFilter } from "@filter-def/drizzle";

const userFilter = drizzleFilter(usersTable).def({
    name: { kind: "eq" },
    emailContains: { kind: "contains", field: "email" },
    minAge: { kind: "gte", field: "age" },
    isActive: { kind: "eq" },
});
4

Query the database

Use the filter to generate SQL WHERE clauses:
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";

const pool = new Pool({
    connectionString: process.env.DATABASE_URL,
});
const db = drizzle(pool);

// Generate WHERE clause
const where = userFilter({
    emailContains: "@example.com",
    isActive: true,
});

// Execute query
const results = await db
    .select()
    .from(usersTable)
    .where(where);

console.log(results);
5

Try complex queries

Combine filters with Drizzle’s query builder:
import { desc } from "drizzle-orm";

// Filter and sort
const sortedUsers = await db
    .select()
    .from(usersTable)
    .where(userFilter({ minAge: 25 }))
    .orderBy(desc(usersTable.age))
    .limit(10);

// Count matching records
const count = await db
    .select({ count: sql<number>`count(*)` })
    .from(usersTable)
    .where(userFilter({ isActive: true }));

BigQuery

Generate parameterized SQL for Google BigQuery.
1

Install dependencies

Install the BigQuery adapter and Google Cloud SDK:
npm install @filter-def/bigquery @google-cloud/bigquery
2

Define your entity type

Create a TypeScript interface matching your BigQuery table:
interface User {
    name: string;
    email: string;
    age: number;
    isActive: boolean;
}
3

Create a filter definition

Import bigqueryFilter and define your filter schema:
import { bigqueryFilter } from "@filter-def/bigquery";

const userFilter = bigqueryFilter<User>().def({
    name: { kind: "eq" },
    emailContains: { kind: "contains", field: "email" },
    minAge: { kind: "gte", field: "age" },
    isActive: { kind: "eq" },
});
4

Query BigQuery

Generate parameterized SQL and execute the query:
import { BigQuery } from "@google-cloud/bigquery";

const bigquery = new BigQuery({
    projectId: process.env.GCP_PROJECT_ID,
});

// Generate parameterized SQL
const where = userFilter({
    emailContains: "@example.com",
    isActive: true,
});

// Execute query
const [rows] = await bigquery.query({
    query: `
        SELECT name, email, age
        FROM \`myproject.dataset.users\`
        WHERE ${where.sql}
    `,
    params: where.params,
});

console.log(rows);
The filter returns both sql (the WHERE clause) and params (the query parameters) to prevent SQL injection.
5

Use with complex queries

Combine filters with aggregations and joins:
const where = userFilter({ minAge: 25, isActive: true });

const [rows] = await bigquery.query({
    query: `
        SELECT
            age,
            COUNT(*) as user_count,
            AVG(engagement_score) as avg_engagement
        FROM \`myproject.dataset.users\`
        WHERE ${where.sql}
        GROUP BY age
        ORDER BY user_count DESC
    `,
    params: where.params,
});

Advanced Examples

Combining Multiple Filters with OR Logic

Search across multiple fields:
const userFilter = inMemoryFilter<User>().def({
    searchTerm: {
        kind: "or",
        conditions: [
            { kind: "contains", field: "name" },
            { kind: "contains", field: "email" },
        ],
    },
});

// Matches users with "john" in name OR email
const results = users.filter(
    userFilter({ searchTerm: "john" })
);

Age Range with AND Logic

Filter values between a range:
const userFilter = inMemoryFilter<User>().def({
    ageRange: {
        kind: "and",
        conditions: [
            { kind: "gte", field: "age" },
            { kind: "lte", field: "age" },
        ],
    },
});

// Matches users aged 25-35
const results = users.filter(
    userFilter({ ageRange: [25, 35] })
);

Custom Filter Logic

Implement complex business rules:
interface BlogPost {
    title: string;
    tags: string[];
    publishedAt: Date;
    viewCount: number;
    likeCount: number;
}

const postFilter = inMemoryFilter<BlogPost>().def({
    // Check if post has a specific tag
    hasTag: (post, tag: string) => post.tags.includes(tag),

    // Check if published within X days
    publishedWithinDays: (post, days: number) => {
        const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
        return post.publishedAt >= cutoff;
    },

    // Calculate engagement rate
    minEngagementRate: (post, minRate: number) => {
        return post.likeCount / post.viewCount >= minRate;
    },
});

// Find trending posts
const trending = posts.filter(
    postFilter({
        publishedWithinDays: 7,
        minEngagementRate: 0.1,
        hasTag: "featured",
    })
);

Nested Field Access

Filter on deeply nested properties:
interface Employee {
    name: { first: string; last: string };
    address: { city: string; geo: { lat: number; lng: number } };
}

const employeeFilter = inMemoryFilter<Employee>().def({
    firstName: { kind: "eq", field: "name.first" },
    city: { kind: "contains", field: "address.city" },
    minLat: { kind: "gte", field: "address.geo.lat" },
});

const employees: Employee[] = [
    {
        name: { first: "John", last: "Doe" },
        address: { city: "San Francisco", geo: { lat: 37.7749, lng: -122.4194 } },
    },
    {
        name: { first: "Jane", last: "Smith" },
        address: { city: "New York", geo: { lat: 40.7128, lng: -74.0060 } },
    },
];

const results = employees.filter(
    employeeFilter({
        firstName: "John",
        city: "Francisco",
    })
);

Type-Safe Filter Inputs

Extract the input type for type-safe function parameters:
import type { InMemoryFilterInput } from "@filter-def/in-memory";

const userFilter = inMemoryFilter<User>().def({
    name: { kind: "eq" },
    minAge: { kind: "gte", field: "age" },
    isActive: { kind: "eq" },
});

// Extract the filter input type
type UserFilterInput = InMemoryFilterInput<typeof userFilter>;
// Type: { name?: string; minAge?: number; isActive?: boolean }

// Use in function signatures
function filterUsers(users: User[], filters: UserFilterInput): User[] {
    return users.filter(userFilter(filters));
}

// TypeScript validates the input
const results = filterUsers(users, {
    name: "John",      // ✓ valid
    minAge: 25,        // ✓ valid
    // minAge: "25",   // ✗ Type error: string not assignable to number
});

Common Patterns

API Query Parameters

Build REST APIs with filter support:
import express from "express";
import { drizzleFilter } from "@filter-def/drizzle";

const app = express();

const userFilter = drizzleFilter(usersTable).def({
    name: { kind: "contains" },
    minAge: { kind: "gte", field: "age" },
    isActive: { kind: "eq" },
});

app.get("/api/users", async (req, res) => {
    const { name, minAge, isActive } = req.query;

    const where = userFilter({
        name: name as string | undefined,
        minAge: minAge ? Number(minAge) : undefined,
        isActive: isActive === "true" ? true : undefined,
    });

    const users = await db.select().from(usersTable).where(where);
    res.json(users);
});

Reusable Filter Helpers

Create wrapper functions for cleaner code:
import { inMemoryFilter, makeFilterHelpers } from "@filter-def/in-memory";

const userFilter = inMemoryFilter<User>().def({
    name: { kind: "eq" },
    isActive: { kind: "eq" },
});

const {
    filter: filterUsers,
    find: findUser,
    some: someUsers,
} = makeFilterHelpers(userFilter);

// Cleaner API
const activeUsers = filterUsers(users, { isActive: true });
const john = findUser(users, { name: "John" });
const hasActive = someUsers(users, { isActive: true });

Next Steps

Filter Types Reference

Explore all available filter types and operators

In-Memory Adapter

Deep dive into in-memory filtering

Drizzle Adapter

Learn about SQL filtering with Drizzle

BigQuery Adapter

Explore BigQuery integration

Build docs developers (and LLMs) love