Skip to content

Deno Integration

Guide for using @mcabreradev/filter with Deno.

Overview

@mcabreradev/filter works seamlessly with Deno, supporting Oak framework, Fresh framework, standard HTTP server, and Deno Deploy edge functions.

Installation

Using deno.land/x

typescript
import { filter } from 'https://deno.land/x/mcabreradev_filter/mod.ts';
import type { Expression } from 'https://deno.land/x/mcabreradev_filter/mod.ts';

Using npm: specifier

typescript
import { filter } from 'npm:@mcabreradev/filter';
import type { Expression } from 'npm:@mcabreradev/filter';

Using import map (deno.json)

json
{
  "imports": {
    "@mcabreradev/filter": "npm:@mcabreradev/filter@^5.4.0"
  }
}
typescript
import { filter } from '@mcabreradev/filter';

Oak Framework Integration

Basic Router

typescript
import { Application, Router } from 'https://deno.land/x/oak/mod.ts';
import { filter } from 'npm:@mcabreradev/filter';
import type { Expression } from 'npm:@mcabreradev/filter';

interface User {
  id: number;
  name: string;
  email: string;
  status: 'active' | 'inactive';
  role: string;
}

const users: User[] = [
  { id: 1, name: 'John Doe', email: 'john@example.com', status: 'active', role: 'admin' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'active', role: 'user' }
];

const router = new Router();

router.get('/api/users', (ctx) => {
  const expression: Expression<User> = {
    status: { $eq: 'active' }
  };

  const filtered = filter(users, expression);

  ctx.response.body = {
    success: true,
    data: filtered,
    count: filtered.length
  };
});

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

console.log('Server running on http://localhost:8000');
await app.listen({ port: 8000 });

Query Parameter Filtering

typescript
router.get('/api/users', (ctx) => {
  const params = ctx.request.url.searchParams;
  const status = params.get('status');
  const role = params.get('role');
  const search = params.get('search');

  const conditions: any[] = [];

  if (status) {
    conditions.push({ status: { $eq: status } });
  }

  if (role) {
    conditions.push({ role: { $eq: role } });
  }

  if (search) {
    conditions.push({
      $or: [
        { name: { $regex: new RegExp(search, 'i') } },
        { email: { $regex: new RegExp(search, 'i') } }
      ]
    });
  }

  const expression: Expression<User> =
    conditions.length > 0 ? { $and: conditions } : {};

  const filtered = filter(users, expression);

  ctx.response.body = {
    success: true,
    data: filtered,
    count: filtered.length
  };
});

POST with Request Body

typescript
router.post('/api/users/filter', async (ctx) => {
  try {
    const body = await ctx.request.body().value;
    const expression: Expression<User> = body.expression;

    if (!expression || typeof expression !== 'object') {
      ctx.response.status = 400;
      ctx.response.body = {
        success: false,
        error: 'Invalid expression provided'
      };
      return;
    }

    const filtered = filter(users, expression);

    ctx.response.body = {
      success: true,
      data: filtered,
      count: filtered.length
    };
  } catch (error) {
    ctx.response.status = 500;
    ctx.response.body = {
      success: false,
      error: 'Failed to filter users'
    };
  }
});

Middleware Patterns

Filter Middleware

typescript
import { Context, Next } from 'https://deno.land/x/oak/mod.ts';

interface FilterMiddlewareOptions {
  dataKey: string;
  expressionKey?: string;
}

function filterMiddleware(options: FilterMiddlewareOptions) {
  return async (ctx: Context, next: Next) => {
    const { dataKey, expressionKey = 'expression' } = options;

    const data = (ctx.state as any)[dataKey];
    const body = await ctx.request.body().value;
    const expression = body[expressionKey] || {};

    if (!data || !Array.isArray(data)) {
      return await next();
    }

    try {
      const filtered = filter(data, expression);
      ctx.state.filtered = filtered;
      await next();
    } catch (error) {
      ctx.response.status = 400;
      ctx.response.body = {
        success: false,
        error: 'Invalid filter expression'
      };
    }
  };
}

router.post('/api/users/filter',
  async (ctx, next) => {
    ctx.state.users = users;
    await next();
  },
  filterMiddleware({ dataKey: 'users' }),
  (ctx) => {
    ctx.response.body = {
      success: true,
      data: ctx.state.filtered
    };
  }
);

Authentication Middleware

typescript
async function authMiddleware(ctx: Context, next: Next) {
  const token = ctx.request.headers.get('authorization')?.split(' ')[1];

  if (!token) {
    ctx.response.status = 401;
    ctx.response.body = {
      success: false,
      error: 'Unauthorized'
    };
    return;
  }

  ctx.state.user = { id: 1, role: 'admin' };
  await next();
}

router.get('/api/users',
  authMiddleware,
  (ctx) => {
    const userRole = ctx.state.user?.role;

    let expression: Expression<User>;

    if (userRole === 'admin') {
      expression = {};
    } else {
      expression = {
        $and: [
          { status: { $eq: 'active' } },
          { id: { $eq: ctx.state.user.id } }
        ]
      };
    }

    const filtered = filter(users, expression);

    ctx.response.body = {
      success: true,
      data: filtered
    };
  }
);

Fresh Framework Integration

API Route Handler

typescript
import { Handlers } from '$fresh/server.ts';
import { filter } from 'npm:@mcabreradev/filter';
import type { Expression } from 'npm:@mcabreradev/filter';

interface User {
  id: number;
  name: string;
  email: string;
  status: 'active' | 'inactive';
}

const users: User[] = [];

export const handler: Handlers = {
  GET(req, _ctx) {
    const url = new URL(req.url);
    const status = url.searchParams.get('status');
    const search = url.searchParams.get('search');

    const conditions: any[] = [];

    if (status) {
      conditions.push({ status: { $eq: status } });
    }

    if (search) {
      conditions.push({
        $or: [
          { name: { $regex: new RegExp(search, 'i') } },
          { email: { $regex: new RegExp(search, 'i') } }
        ]
      });
    }

    const expression: Expression<User> =
      conditions.length > 0 ? { $and: conditions } : {};

    const filtered = filter(users, expression);

    return new Response(JSON.stringify({
      success: true,
      data: filtered,
      count: filtered.length
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  },

  async POST(req, _ctx) {
    try {
      const body = await req.json();
      const expression: Expression<User> = body.expression;

      const filtered = filter(users, expression);

      return new Response(JSON.stringify({
        success: true,
        data: filtered,
        count: filtered.length
      }), {
        headers: { 'Content-Type': 'application/json' }
      });
    } catch (error) {
      return new Response(JSON.stringify({
        success: false,
        error: 'Invalid request'
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
  }
};

Standard HTTP Server

Basic Server

typescript
import { filter } from 'npm:@mcabreradev/filter';
import type { Expression } from 'npm:@mcabreradev/filter';

interface User {
  id: number;
  name: string;
  email: string;
  status: 'active' | 'inactive';
}

const users: User[] = [];

async function handler(req: Request): Promise<Response> {
  const url = new URL(req.url);

  if (url.pathname === '/api/users' && req.method === 'GET') {
    const status = url.searchParams.get('status');
    const role = url.searchParams.get('role');

    const conditions: any[] = [];

    if (status) {
      conditions.push({ status: { $eq: status } });
    }

    if (role) {
      conditions.push({ role: { $eq: role } });
    }

    const expression: Expression<User> =
      conditions.length > 0 ? { $and: conditions } : {};

    const filtered = filter(users, expression);

    return new Response(JSON.stringify({
      success: true,
      data: filtered,
      count: filtered.length
    }), {
      headers: { 'Content-Type': 'application/json' }
    });
  }

  if (url.pathname === '/api/users/filter' && req.method === 'POST') {
    try {
      const body = await req.json();
      const expression: Expression<User> = body.expression;

      const filtered = filter(users, expression);

      return new Response(JSON.stringify({
        success: true,
        data: filtered
      }), {
        headers: { 'Content-Type': 'application/json' }
      });
    } catch (error) {
      return new Response(JSON.stringify({
        success: false,
        error: 'Invalid request'
      }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' }
      });
    }
  }

  return new Response('Not Found', { status: 404 });
}

console.log('Server running on http://localhost:8000');
Deno.serve({ port: 8000 }, handler);

Database Integration

Deno KV

typescript
const kv = await Deno.openKv();

router.get('/api/users', async (ctx) => {
  const entries = kv.list({ prefix: ['users'] });
  const users: User[] = [];

  for await (const entry of entries) {
    users.push(entry.value as User);
  }

  const params = ctx.request.url.searchParams;
  const status = params.get('status');

  const expression: Expression<User> = status
    ? { status: { $eq: status } }
    : {};

  const filtered = filter(users, expression);

  ctx.response.body = {
    success: true,
    data: filtered
  };
});

PostgreSQL

typescript
import { Client } from 'https://deno.land/x/postgres/mod.ts';

const client = new Client({
  user: 'user',
  database: 'test',
  hostname: 'localhost',
  port: 5432,
  password: 'password'
});

await client.connect();

router.get('/api/users', async (ctx) => {
  const result = await client.queryObject<User>('SELECT * FROM users');
  const users = result.rows;

  const params = ctx.request.url.searchParams;
  const status = params.get('status');
  const role = params.get('role');

  const conditions: any[] = [];

  if (status) conditions.push({ status: { $eq: status } });
  if (role) conditions.push({ role: { $eq: role } });

  const expression: Expression<User> =
    conditions.length > 0 ? { $and: conditions } : {};

  const filtered = filter(users, expression);

  ctx.response.body = {
    success: true,
    data: filtered
  };
});

Advanced Patterns

WebSocket Filtering

typescript
import { WebSocketServer } from 'https://deno.land/x/websocket/mod.ts';

const wss = new WebSocketServer(8080);

wss.on('connection', (ws) => {
  ws.on('message', (message: string) => {
    try {
      const { action, expression } = JSON.parse(message);

      if (action === 'filter') {
        const filtered = filter(users, expression);

        ws.send(JSON.stringify({
          success: true,
          data: filtered
        }));
      }
    } catch (error) {
      ws.send(JSON.stringify({
        success: false,
        error: 'Invalid message'
      }));
    }
  });
});

Server-Sent Events

typescript
router.get('/api/users/stream', (ctx) => {
  const target = ctx.sendEvents();

  const params = ctx.request.url.searchParams;
  const status = params.get('status');

  const expression: Expression<User> = status
    ? { status: { $eq: status } }
    : {};

  const filtered = filter(users, expression);

  target.dispatchMessage({
    data: JSON.stringify({
      success: true,
      data: filtered
    })
  });
});

Edge Function Filtering (Deno Deploy)

typescript
import { serve } from 'https://deno.land/std/http/server.ts';
import { filter } from 'npm:@mcabreradev/filter';

serve(async (req) => {
  const url = new URL(req.url);

  if (url.pathname === '/api/filter') {
    try {
      const body = await req.json();
      const { data, expression } = body;

      if (!Array.isArray(data) || !expression) {
        return new Response(JSON.stringify({
          success: false,
          error: 'Invalid request'
        }), {
          status: 400,
          headers: { 'Content-Type': 'application/json' }
        });
      }

      const filtered = filter(data, expression, {
        memoize: true
      });

      return new Response(JSON.stringify({
        success: true,
        data: filtered,
        count: filtered.length
      }), {
        headers: {
          'Content-Type': 'application/json',
          'Cache-Control': 'public, max-age=60'
        }
      });
    } catch (error) {
      return new Response(JSON.stringify({
        success: false,
        error: 'Processing error'
      }), {
        status: 500,
        headers: { 'Content-Type': 'application/json' }
      });
    }
  }

  return new Response('Not Found', { status: 404 });
});

Pagination with Filtering

typescript
router.get('/api/users', (ctx) => {
  const params = ctx.request.url.searchParams;
  const page = parseInt(params.get('page') || '1');
  const limit = parseInt(params.get('limit') || '10');
  const status = params.get('status');
  const role = params.get('role');

  const conditions: any[] = [];

  if (status) conditions.push({ status: { $eq: status } });
  if (role) conditions.push({ role: { $eq: role } });

  const expression: Expression<User> =
    conditions.length > 0 ? { $and: conditions } : {};

  const filtered = filter(users, expression);

  const startIndex = (page - 1) * limit;
  const endIndex = startIndex + limit;
  const paginated = filtered.slice(startIndex, endIndex);

  ctx.response.body = {
    success: true,
    data: paginated,
    pagination: {
      page,
      limit,
      total: filtered.length,
      totalPages: Math.ceil(filtered.length / limit),
      hasNext: endIndex < filtered.length,
      hasPrev: page > 1
    }
  };
});

Performance Tips

1. Enable Memoization

typescript
const filtered = filter(users, expression, {
  memoize: true
});

2. Implement Caching

typescript
const cache = new Map<string, { data: any[]; timestamp: number }>();
const CACHE_TTL = 60000;

router.get('/api/users', (ctx) => {
  const cacheKey = ctx.request.url.search;
  const cached = cache.get(cacheKey);

  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    ctx.response.body = {
      success: true,
      data: cached.data,
      cached: true
    };
    return;
  }

  const filtered = filter(users, expression);

  cache.set(cacheKey, {
    data: filtered,
    timestamp: Date.now()
  });

  ctx.response.body = {
    success: true,
    data: filtered,
    cached: false
  };
});

3. Use Response Compression

typescript
import { compress } from 'https://deno.land/x/oak_compress/mod.ts';

app.use(compress());

4. Limit Response Size

typescript
router.get('/api/users', (ctx) => {
  const filtered = filter(users, expression);
  const limited = filtered.slice(0, 100);

  ctx.response.body = {
    success: true,
    data: limited,
    total: filtered.length,
    hasMore: filtered.length > 100
  };
});

TypeScript Configuration

Create a deno.json file:

json
{
  "compilerOptions": {
    "allowJs": true,
    "lib": ["deno.window"],
    "strict": true
  },
  "imports": {
    "@mcabreradev/filter": "npm:@mcabreradev/filter@^5.4.0",
    "oak": "https://deno.land/x/oak@v12.6.1/mod.ts"
  },
  "tasks": {
    "dev": "deno run --allow-net --allow-read --watch server.ts",
    "start": "deno run --allow-net --allow-read server.ts"
  }
}

Testing

Basic Test

typescript
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { filter } from 'npm:@mcabreradev/filter';

Deno.test('filter users by status', () => {
  const users = [
    { id: 1, name: 'John', status: 'active' },
    { id: 2, name: 'Jane', status: 'inactive' }
  ];

  const expression = { status: { $eq: 'active' } };
  const filtered = filter(users, expression);

  assertEquals(filtered.length, 1);
  assertEquals(filtered[0].name, 'John');
});

API Testing

typescript
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { Application } from 'https://deno.land/x/oak/mod.ts';
import { superoak } from 'https://deno.land/x/superoak/mod.ts';

Deno.test('GET /api/users with filters', async () => {
  const app = new Application();

  const request = await superoak(app);
  const response = await request
    .get('/api/users?status=active')
    .expect(200);

  assertEquals(response.body.success, true);
  assertEquals(Array.isArray(response.body.data), true);
});

Complete Example

typescript
import { Application, Router } from 'https://deno.land/x/oak/mod.ts';
import { filter } from 'npm:@mcabreradev/filter';
import type { Expression } from 'npm:@mcabreradev/filter';

interface User {
  id: number;
  name: string;
  email: string;
  status: 'active' | 'inactive';
  role: string;
}

const users: User[] = [
  { id: 1, name: 'John Doe', email: 'john@example.com', status: 'active', role: 'admin' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'active', role: 'user' }
];

const router = new Router();

router.get('/api/users', (ctx) => {
  const params = ctx.request.url.searchParams;
  const status = params.get('status');
  const role = params.get('role');
  const search = params.get('search');
  const page = parseInt(params.get('page') || '1');
  const limit = parseInt(params.get('limit') || '10');

  const conditions: any[] = [];

  if (status) conditions.push({ status: { $eq: status } });
  if (role) conditions.push({ role: { $eq: role } });
  if (search) {
    conditions.push({
      $or: [
        { name: { $regex: new RegExp(search, 'i') } },
        { email: { $regex: new RegExp(search, 'i') } }
      ]
    });
  }

  const expression: Expression<User> =
    conditions.length > 0 ? { $and: conditions } : {};

  const filtered = filter(users, expression, { memoize: true });

  const startIndex = (page - 1) * limit;
  const paginated = filtered.slice(startIndex, startIndex + limit);

  ctx.response.body = {
    success: true,
    data: paginated,
    pagination: {
      page,
      limit,
      total: filtered.length,
      totalPages: Math.ceil(filtered.length / limit)
    }
  };
});

router.post('/api/users/filter', async (ctx) => {
  try {
    const body = await ctx.request.body().value;
    const expression: Expression<User> = body.expression;

    const filtered = filter(users, expression);

    ctx.response.body = {
      success: true,
      data: filtered,
      count: filtered.length
    };
  } catch (error) {
    ctx.response.status = 400;
    ctx.response.body = {
      success: false,
      error: 'Invalid expression'
    };
  }
});

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

console.log('Server running on http://localhost:8000');
await app.listen({ port: 8000 });

Released under the MIT License.