Skip to main content

How-to Guides

Practical solutions to common DynamicPick problems.

API Response Sanitization​

Problem​

You need to remove sensitive fields before sending data to clients.

Solution​

import { createPicker } from '@noony-serverless/type-builder';
import express from 'express';

// Define safe projections once
const toPublicUser = createPicker(['id', 'name', 'email', 'avatar', 'createdAt']);

const app = express();

app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
// Database user has: password, passwordHash, salt, sessionToken, etc.

res.json(toPublicUser(user));
// Response only has: id, name, email, avatar, createdAt
});

Why this works:

  • createPicker caches the schema for performance
  • All fields not in the projection are automatically stripped
  • Original database object is never modified

Dynamic Field Selection (GraphQL-style)​

Problem​

Let clients specify which fields they want via API query parameters.

Solution​

import { customPicker } from '@noony-serverless/type-builder';

app.get('/api/posts', async (req, res) => {
const posts = await db.posts.findMany();

// Parse fields from query: /api/posts?fields=id,title,author.name
const requestedFields = req.query.fields
? req.query.fields.split(',')
: ['id', 'title', 'excerpt']; // Default fields

// Whitelist allowed fields for security
const allowedFields = [
'id',
'title',
'excerpt',
'content',
'author.id',
'author.name',
'author.email',
'comments[].id',
'comments[].text',
'createdAt',
];

const safeFields = requestedFields.filter((field) => allowedFields.includes(field));

res.json(customPicker(posts, safeFields));
});

Security Note: Always whitelist allowed fields to prevent exposing sensitive data.


Database JOIN Result Projection​

Problem​

Database JOINs return flat objects with prefixed column names. You need structured nested objects.

Solution​

interface FlatDBResult {
user_id: number;
user_name: string;
user_email: string;
order_id: number;
order_total: number;
order_created_at: string;
}

interface NestedAPIResponse {
user: {
id: number;
name: string;
email: string;
};
order: {
id: number;
total: number;
createdAt: string;
};
}

// First, reshape the data
const dbResults = await db.query(`
SELECT
u.id as user_id,
u.name as user_name,
u.email as user_email,
o.id as order_id,
o.total as order_total,
o.created_at as order_created_at
FROM users u
JOIN orders o ON u.id = o.user_id
`);

// Transform flat structure to nested
const apiResponse = dbResults.map((row) => ({
user: customPicker(
{
id: row.user_id,
name: row.user_name,
email: row.user_email,
},
['id', 'name', 'email']
),
order: customPicker(
{
id: row.order_id,
total: row.order_total,
createdAt: row.order_created_at,
},
['id', 'total', 'createdAt']
),
}));

Remove Null/Undefined Fields​

Problem​

You want to strip out null or undefined fields from responses.

Solution​

import { customPicker } from '@noony-serverless/type-builder';

function removeNullish<T>(obj: T): Partial<T> {
const cleaned: any = {};

for (const [key, value] of Object.entries(obj)) {
if (value !== null && value !== undefined) {
cleaned[key] = value;
}
}

return cleaned;
}

// Combine projection with null removal
const user = {
id: 1,
name: 'John',
email: 'john@example.com',
phone: null,
address: undefined,
password: 'secret',
};

const projected = customPicker(user, ['id', 'name', 'email', 'phone', 'address']);
const cleaned = removeNullish(projected);

console.log(cleaned);
// { id: 1, name: 'John', email: 'john@example.com' }

Shape-based Projection (Reference Objects)​

Problem​

You want to define projection using a reference object instead of listing field names.

Solution​

import { projectByShape, createShapeProjector } from '@noony-serverless/type-builder';

const fullUser = {
id: 1,
name: 'John',
email: 'john@example.com',
password: 'secret',
internalId: 'USR-001',
};

// Define shape with example values (values don't matter, only keys)
const publicUserShape = {
id: 0,
name: '',
email: '',
};

// Project using shape
const publicUser = projectByShape(fullUser, publicUserShape);
// { id: 1, name: 'John', email: 'john@example.com' }

// Create reusable shape projector
const toPublicUser = createShapeProjector(publicUserShape);

const user1 = toPublicUser(dbUser1);
const user2 = toPublicUser(dbUser2);

When to use:

  • When you have existing type definitions or example objects
  • For better IDE autocomplete on shapes
  • When field names are more readable as objects than arrays

Multi-level Array Projection​

Problem​

You have deeply nested arrays and need to project fields from all levels.

Solution​

const blogPost = {
id: 1,
title: 'My Blog Post',
author: {
id: 123,
name: 'John',
email: 'john@example.com',
privateKey: 'secret',
},
comments: [
{
id: 1,
text: 'Great post!',
author: {
id: 456,
name: 'Jane',
ipAddress: '192.168.1.1',
},
replies: [
{
id: 1,
text: 'Thanks!',
author: { id: 123, name: 'John', sessionToken: 'xyz' },
},
],
},
],
};

const publicBlogPost = customPicker(blogPost, [
'id',
'title',
'author.id',
'author.name',
'comments[].id',
'comments[].text',
'comments[].author.name',
'comments[].replies[].id',
'comments[].replies[].text',
'comments[].replies[].author.name',
]);

console.log(publicBlogPost);
// All sensitive fields (privateKey, ipAddress, sessionToken) removed
// Structure preserved across all nesting levels

Validation with Custom Error Handling​

Problem​

You need projection with validation but want to handle errors gracefully.

Solution​

import { z } from 'zod';
import { customPicker } from '@noony-serverless/type-builder';

const UserResponseSchema = z.object({
id: z.number(),
name: z.string().min(1),
email: z.string().email(),
});

app.get('/api/users/:id', async (req, res) => {
try {
const user = await db.users.findById(req.params.id);

// Project and validate
const publicUser = customPicker(user, UserResponseSchema);

res.json(publicUser);
} catch (error) {
if (error instanceof z.ZodError) {
// Handle validation errors
return res.status(500).json({
error: 'Data validation failed',
details: error.errors,
});
}
throw error;
}
});

Disable validation for performance:

// Skip validation (faster)
const publicUser = customPicker(user, UserResponseSchema, { validate: false });

Combine Multiple Projections​

Problem​

You need to apply different projections based on user roles.

Solution​

import { createPicker } from '@noony-serverless/type-builder';

const publicFields = createPicker(['id', 'name', 'email']);

const memberFields = createPicker([
'id',
'name',
'email',
'phone',
'address.city',
'address.country',
]);

const adminFields = createPicker([
'id',
'name',
'email',
'phone',
'address.street',
'address.city',
'address.zipCode',
'address.country',
'createdAt',
'lastLogin',
'role',
]);

app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
const requestingUser = req.user;

let projectedUser;

if (requestingUser.role === 'admin') {
projectedUser = adminFields(user);
} else if (requestingUser.role === 'member') {
projectedUser = memberFields(user);
} else {
projectedUser = publicFields(user);
}

res.json(projectedUser);
});

Batch Projection with Different Schemas​

Problem​

You have an array of objects that need different projections based on their type.

Solution​

import { customPicker, createPicker } from '@noony-serverless/type-builder';

interface Activity {
type: 'post' | 'comment' | 'like';
data: any;
}

const projectPost = createPicker(['id', 'title', 'excerpt', 'author.name']);
const projectComment = createPicker(['id', 'text', 'author.name', 'post.title']);
const projectLike = createPicker(['id', 'user.name', 'target.title']);

const activities: Activity[] = await db.activities.findMany();

const projected = activities.map((activity) => {
switch (activity.type) {
case 'post':
return { type: 'post', data: projectPost(activity.data) };
case 'comment':
return { type: 'comment', data: projectComment(activity.data) };
case 'like':
return { type: 'like', data: projectLike(activity.data) };
default:
return activity;
}
});

res.json(projected);

Projection with Computed Fields​

Problem​

You want to add computed fields during projection.

Solution​

import { customPicker } from '@noony-serverless/type-builder';

app.get('/api/products', async (req, res) => {
const products = await db.products.findMany();

const projected = products.map((product) => {
// First, project the fields you need
const base = customPicker(product, ['id', 'name', 'price', 'discount', 'stock']);

// Then, add computed fields
return {
...base,
finalPrice: base.price * (1 - base.discount),
inStock: base.stock > 0,
};
});

res.json(projected);
});

Omit Nested Fields​

Problem​

You want to keep most fields but remove specific nested ones.

Solution​

import { customPicker } from '@noony-serverless/type-builder';

const user = {
id: 1,
profile: {
name: 'John',
email: 'john@example.com',
settings: {
theme: 'dark',
notifications: true,
privateKey: 'secret',
},
},
};

// Keep everything except privateKey
const safe = customPicker(user, [
'id',
'profile.name',
'profile.email',
'profile.settings.theme',
'profile.settings.notifications',
// Omit 'profile.settings.privateKey'
]);

console.log(safe);
// {
// id: 1,
// profile: {
// name: 'John',
// email: 'john@example.com',
// settings: { theme: 'dark', notifications: true }
// }
// }

Cache Performance Monitoring​

Problem​

You want to monitor projection performance and cache effectiveness.

Solution​

import {
getGlobalSchemaCacheStats,
resetGlobalSchemaCacheStats,
clearGlobalSchemaCache,
} from '@noony-serverless/type-builder';

// Middleware to log cache stats
app.use((req, res, next) => {
res.on('finish', () => {
const stats = getGlobalSchemaCacheStats();
console.log(`Cache stats: ${JSON.stringify(stats)}`);
// { size: 15, hits: 1234, misses: 20, hitRate: 0.984 }
});
next();
});

// Periodic cache cleanup (prevent memory growth)
setInterval(() => {
const stats = getGlobalSchemaCacheStats();

if (stats.size > 500) {
console.log('Cache size exceeded limit, clearing...');
clearGlobalSchemaCache();
}
}, 60000); // Every minute

// Reset stats for metrics
app.get('/api/metrics/projection', (req, res) => {
const stats = getGlobalSchemaCacheStats();
resetGlobalSchemaCacheStats();
res.json(stats);
});

Testing with Projection​

Problem​

You need to test projection behavior in your test suite.

Solution​

import { describe, it, expect, beforeEach } from 'vitest';
import { customPicker, clearGlobalSchemaCache } from '@noony-serverless/type-builder';

describe('User API Projection', () => {
beforeEach(() => {
// Clear cache before each test for isolation
clearGlobalSchemaCache();
});

it('should remove sensitive fields', () => {
const dbUser = {
id: 1,
name: 'John',
email: 'john@example.com',
password: 'secret',
sessionToken: 'abc123',
};

const publicUser = customPicker(dbUser, ['id', 'name', 'email']);

expect(publicUser).toEqual({
id: 1,
name: 'John',
email: 'john@example.com',
});
expect(publicUser).not.toHaveProperty('password');
expect(publicUser).not.toHaveProperty('sessionToken');
});

it('should project nested arrays correctly', () => {
const order = {
id: 1,
items: [
{ id: 101, name: 'Item 1', price: 100, cost: 50 },
{ id: 102, name: 'Item 2', price: 200, cost: 100 },
],
};

const projected = customPicker(order, ['id', 'items[].id', 'items[].name']);

expect(projected.items[0]).not.toHaveProperty('price');
expect(projected.items[0]).not.toHaveProperty('cost');
});
});

Next Steps​