Skip to main content

DynamoDB Utilities

Helper functions for working with DynamoDB data, focusing on cleaning internal keys from items before returning them to clients.

Overview​

The DynamoDB utilities module provides functions for post-processing DynamoDB items to remove internal implementation details:

  • Strip Internal Keys: Remove DynamoDB internal keys and GSI keys from items
  • Flexible Configuration: Specify which keys to remove or preserve
  • Type Safety: Maintains TypeScript type safety while cleaning data

Strip Internal Keys​

Remove DynamoDB internal keys from items before returning them to clients or external APIs.

Basic Usage​

import { stripInternalKeys } from '@leighton-digital/lambda-toolkit';

const dynamoItem = {
pk: 'USER#123',
sk: 'PROFILE',
name: 'Alice Johnson',
email: 'alice@example.com',
gsi1pk: 'EMAIL#alice@example.com',
createdAt: '2025-01-01T00:00:00.000Z',
};

// Remove default internal keys
const cleanItem = stripInternalKeys(dynamoItem);
// Result: `{ name: 'Alice Johnson', email: 'alice@example.com', createdAt: '2025-01-01T00:00:00.000Z' }`

Default Keys Removed​

The function removes these keys by default:

  • pk, PK - Primary partition key
  • sk, SK - Primary sort key
  • ttl, TTL - Time-to-live attributes
  • gsi1pk, gsi1sk - Global Secondary Index 1 keys
  • gsi2pk, gsi2sk - Global Secondary Index 2 keys
  • gsi3pk, gsi3sk - Global Secondary Index 3 keys

Custom Key Removal​

import { stripInternalKeys } from '@leighton-digital/lambda-toolkit';

const item = {
pk: 'USER#123',
sk: 'PROFILE',
name: 'Alice Johnson',
email: 'alice@example.com',
internalFlag: 'secret',
metadata: 'internal-use-only',
};

// Remove additional custom keys
const publicItem = stripInternalKeys(item, undefined, ['internalFlag', 'metadata']);
// Result: `{ name: 'Alice Johnson', email: 'alice@example.com' }`

Override Default Keys​

import { stripInternalKeys } from '@leighton-digital/lambda-toolkit';

const item = {
pk: 'USER#123',
sk: 'PROFILE',
name: 'Alice Johnson',
email: 'alice@example.com',
gsi1pk: 'EMAIL#alice@example.com',
};

// Only remove specific keys (override defaults)
const customClean = stripInternalKeys(item, ['pk', 'sk']);
// Result: `{ name: 'Alice Johnson', email: 'alice@example.com', gsi1pk: 'EMAIL#alice@example.com' }`

Lambda Integration Examples​

API Gateway Response​

import { withHttpHandler } from '@leighton-digital/lambda-toolkit/lambda';
import { stripInternalKeys } from '@leighton-digital/lambda-toolkit';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';

const dynamodb = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const getUserHandler = withHttpHandler(async ({ event }) => {
const { userId } = event.pathParameters;

// Get item from DynamoDB
const result = await dynamodb.send(new GetCommand({
TableName: process.env.USER_TABLE,
Key: { pk: `USER#${userId}`, sk: 'PROFILE' },
}));

if (!result.Item) {
return {
statusCode: 404,
body: { error: 'User not found' },
};
}

// Clean internal keys before returning
const publicUser = stripInternalKeys(result.Item);

return {
statusCode: 200,
body: { user: publicUser },
};
});

List Items Response​

import { withHttpHandler } from '@leighton-digital/lambda-toolkit/lambda';
import { stripInternalKeys } from '@leighton-digital/lambda-toolkit';
import { DynamoDBDocumentClient, QueryCommand } from '@aws-sdk/lib-dynamodb';

export const listUsersHandler = withHttpHandler(async ({ event }) => {
// Query items from DynamoDB
const result = await dynamodb.send(new QueryCommand({
TableName: process.env.USER_TABLE,
KeyConditionExpression: 'pk = :pk',
ExpressionAttributeValues: {
':pk': 'USERS',
},
}));

// Clean all items
const publicUsers = result.Items?.map(item =>
stripInternalKeys(item, undefined, ['internalNotes'])
) || [];

return {
statusCode: 200,
body: {
users: publicUsers,
count: publicUsers.length,
},
};
});

Conditional Key Removal​

import { stripInternalKeys } from '@leighton-digital/lambda-toolkit';

interface UserItem {
pk: string;
sk: string;
name: string;
email: string;
role: 'admin' | 'user';
internalNotes?: string;
adminMetadata?: string;
}

export const getUserHandler = withHttpHandler(async ({ event }) => {
const user: UserItem = await getUserFromDB(event.pathParameters.userId);
const requestingUserRole = event.requestContext.authorizer?.role;

// Admin users see more data
const extraKeysToStrip = requestingUserRole === 'admin'
? []
: ['internalNotes', 'adminMetadata'];

const cleanUser = stripInternalKeys(user, undefined, extraKeysToStrip);

return {
statusCode: 200,
body: { user: cleanUser },
};
});

Type Safety​

The function maintains TypeScript type safety:

import { stripInternalKeys } from '@leighton-digital/lambda-toolkit';

interface UserRecord {
pk: string;
sk: string;
name: string;
email: string;
age: number;
}

const user: UserRecord = {
pk: 'USER#123',
sk: 'PROFILE',
name: 'Alice',
email: 'alice@example.com',
age: 30,
};

// Result type is Partial<UserRecord>
const cleanUser = stripInternalKeys(user);
// TypeScript knows cleanUser has: name?, email?, age? (optional because keys were removed)

// Type assertion if you know what keys remain
const publicUser = cleanUser as Pick<UserRecord, 'name' | 'email' | 'age'>;

Best Practices​

  • Security: Always strip internal keys before returning data to clients
  • Consistency: Use consistent key naming conventions in your DynamoDB design
  • Performance: Strip keys as late as possible in your processing pipeline
  • Validation: Consider validating the cleaned data structure matches expected API schemas
  • Documentation: Document which keys are internal vs. public in your data models

Common Patterns​

Repository Pattern​

class UserRepository {
async getUser(userId: string) {
const result = await this.dynamodb.send(new GetCommand({
TableName: this.tableName,
Key: { pk: `USER#${userId}`, sk: 'PROFILE' },
}));

return result.Item ? stripInternalKeys(result.Item) : null;
}

async listUsers(adminView: boolean = false) {
const result = await this.dynamodb.send(new QueryCommand({
// ... query parameters
}));

const extraKeysToStrip = adminView ? [] : ['internalNotes', 'metadata'];

return result.Items?.map(item =>
stripInternalKeys(item, undefined, extraKeysToStrip)
) || [];
}
}

API Response Transformation​

// Transform DynamoDB items to API response format
const transformUserForAPI = (dynamoItem: any) => {
const cleanItem = stripInternalKeys(dynamoItem, undefined, ['version', 'lastSync']);

return {
id: cleanItem.userId,
name: cleanItem.name,
email: cleanItem.email,
createdAt: cleanItem.createdAt,
// ... other public fields
};
};