KV Store Example
This example demonstrates how to use DynamoDB as a key-value store with Nimbus, including automatic environment variable injection and IAM permissions.
Overview
The KV store example shows:
- DynamoDB table creation and management
- CRUD operations (Create, Read, Update, Delete)
- Automatic environment variable injection
- IAM permissions handling
- Error handling and validation
Code
typescript
import Nimbus from '@hillock-tech/nimbus-js';
/**
* Example: API with DynamoDB KV store
*/
async function kvExample() {
const nimbus = new Nimbus({
region: 'us-east-1',
projectName: 'kv-app',
});
// Create a KV store (DynamoDB table)
const usersKV = nimbus.KV({
name: 'users',
primaryKey: 'userId',
});
// Create an API
const api = nimbus.API({
name: 'users-api',
description: 'User management API with DynamoDB',
stage: 'prod',
});
// GET /users/{id} - Retrieve a user
api.route('GET', '/users/{id}', async (event: any) => {
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, GetCommand } = require('@aws-sdk/lib-dynamodb');
const userId = event.pathParameters?.id;
const tableName = process.env.KV_USERS; // Auto-set by Nimbus
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
try {
const result = await docClient.send(new GetCommand({
TableName: tableName,
Key: { userId },
}));
if (!result.Item) {
return {
statusCode: 404,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'User not found' }),
};
}
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(result.Item),
};
} catch (error: any) {
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: error.message }),
};
}
}, { cors: true });
// POST /users - Create a user
api.route('POST', '/users', async (event: any) => {
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, PutCommand } = require('@aws-sdk/lib-dynamodb');
const { nanoid } = require('nanoid');
const body = JSON.parse(event.body || '{}');
const tableName = process.env.KV_USERS; // Auto-set by Nimbus
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const userId = nanoid();
const user = {
userId,
name: body.name,
email: body.email,
createdAt: new Date().toISOString(),
};
try {
await docClient.send(new PutCommand({
TableName: tableName,
Item: user,
}));
return {
statusCode: 201,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
};
} catch (error: any) {
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: error.message }),
};
}
}, { cors: true });
// GET /users - List all users
api.route('GET', '/users', async (event: any) => {
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, ScanCommand } = require('@aws-sdk/lib-dynamodb');
const tableName = process.env.KV_USERS; // Auto-set by Nimbus
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
try {
const result = await docClient.send(new ScanCommand({
TableName: tableName,
}));
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
users: result.Items || [],
count: result.Count || 0,
}),
};
} catch (error: any) {
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: error.message }),
};
}
}, { cors: true });
// DELETE /users/{id} - Delete a user
api.route('DELETE', '/users/{id}', async (event: any) => {
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, DeleteCommand } = require('@aws-sdk/lib-dynamodb');
const userId = event.pathParameters?.id;
const tableName = process.env.KV_USERS; // Auto-set by Nimbus
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
try {
await docClient.send(new DeleteCommand({
TableName: tableName,
Key: { userId },
}));
return {
statusCode: 204,
body: '',
};
} catch (error: any) {
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: error.message }),
};
}
}, { cors: true });
// Export for CLI deployment
export default nimbus;
console.log(`\nAPI URL: ${result.apis[0].url}`);
console.log(`\nKV Stores created:`);
result.kvStores.forEach(kv => console.log(` - ${kv.name}`));
}
// Run the example
kvExample()
.then(() => process.exit(0))
.catch((error) => {
console.error('Error:', error);
process.exit(1);
});Setup
Create project directory:
bashmkdir kv-store-example cd kv-store-exampleInitialize project:
bashnpm init -y npm install nimbus @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb nanoid npm install -D @types/node tsx typescriptInitialize Nimbus state:
bashnpx nimbus initCreate the application file: Save the code above as
index.tsAdd package.json scripts:
json{ "scripts": { "deploy": "npx nimbus deploy", "destroy": "npx nimbus destroy --project kv-app --region us-east-1 --force" } }
Deploy
bash
npm run deployThis will:
- Create a DynamoDB table named
users - Create Lambda functions for each API route
- Set up IAM permissions for DynamoDB access
- Create an API Gateway
- Inject environment variables with table names
Test the API
Create a User
bash
curl -X POST https://YOUR_API_URL/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'Response:
json
{
"userId": "abc123def",
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2024-01-15T10:30:00.000Z"
}Get a User
bash
curl https://YOUR_API_URL/users/abc123defResponse:
json
{
"userId": "abc123def",
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2024-01-15T10:30:00.000Z"
}List All Users
bash
curl https://YOUR_API_URL/usersResponse:
json
{
"users": [
{
"userId": "abc123def",
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2024-01-15T10:30:00.000Z"
}
],
"count": 1
}Delete a User
bash
curl -X DELETE https://YOUR_API_URL/users/abc123defResponse: 204 No Content
Key Features Demonstrated
1. Automatic Environment Variables
typescript
const tableName = process.env.KV_USERS; // Auto-injected by Nimbus2. IAM Permissions
Nimbus automatically grants Lambda functions the necessary permissions to:
- Read from DynamoDB (
dynamodb:GetItem,dynamodb:Scan) - Write to DynamoDB (
dynamodb:PutItem) - Delete from DynamoDB (
dynamodb:DeleteItem)
3. DynamoDB Operations
Create (PUT)
typescript
await docClient.send(new PutCommand({
TableName: tableName,
Item: user,
}));Read (GET)
typescript
const result = await docClient.send(new GetCommand({
TableName: tableName,
Key: { userId },
}));List (SCAN)
typescript
const result = await docClient.send(new ScanCommand({
TableName: tableName,
}));Delete
typescript
await docClient.send(new DeleteCommand({
TableName: tableName,
Key: { userId },
}));4. Error Handling
typescript
try {
// DynamoDB operation
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}What Gets Created
When you deploy this example, Nimbus creates:
- DynamoDB Table:
userstable withuserIdas primary key - Lambda Functions: One function per API route
- IAM Roles: With DynamoDB permissions
- API Gateway: REST API with CRUD endpoints
- Environment Variables: Table name injected into Lambda functions
Architecture
Internet
↓
API Gateway
↓
Lambda Functions
↓
DynamoDB TableAdvanced Usage
Custom Primary Key
typescript
const products = nimbus.KV({
name: 'products',
primaryKey: 'productId', // Custom primary key
});Conditional Operations
typescript
// Only create if user doesn't exist
await docClient.send(new PutCommand({
TableName: tableName,
Item: user,
ConditionExpression: 'attribute_not_exists(userId)'
}));Update Operations
typescript
api.route('PUT', '/users/{id}', async (event) => {
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, UpdateCommand } = require('@aws-sdk/lib-dynamodb');
const userId = event.pathParameters?.id;
const updates = JSON.parse(event.body || '{}');
const tableName = process.env.KV_USERS;
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
try {
const result = await docClient.send(new UpdateCommand({
TableName: tableName,
Key: { userId },
UpdateExpression: 'SET #name = :name, #email = :email, updatedAt = :updatedAt',
ExpressionAttributeNames: {
'#name': 'name',
'#email': 'email'
},
ExpressionAttributeValues: {
':name': updates.name,
':email': updates.email,
':updatedAt': new Date().toISOString()
},
ReturnValues: 'ALL_NEW'
}));
return {
statusCode: 200,
body: JSON.stringify(result.Attributes)
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
});Filtering and Querying
typescript
// Get active users only
api.route('GET', '/users/active', async (event) => {
const result = await docClient.send(new ScanCommand({
TableName: tableName,
FilterExpression: '#status = :status',
ExpressionAttributeNames: {
'#status': 'status'
},
ExpressionAttributeValues: {
':status': 'active'
}
}));
return {
statusCode: 200,
body: JSON.stringify({
users: result.Items || [],
count: result.Count || 0
})
};
});Pagination
typescript
api.route('GET', '/users/paginated', async (event) => {
const limit = parseInt(event.queryStringParameters?.limit || '10');
const lastKey = event.queryStringParameters?.lastKey;
const params = {
TableName: tableName,
Limit: limit
};
if (lastKey) {
params.ExclusiveStartKey = JSON.parse(decodeURIComponent(lastKey));
}
const result = await docClient.send(new ScanCommand(params));
const response = {
users: result.Items || [],
count: result.Count || 0
};
if (result.LastEvaluatedKey) {
response.lastKey = encodeURIComponent(JSON.stringify(result.LastEvaluatedKey));
}
return {
statusCode: 200,
body: JSON.stringify(response)
};
});Clean Up
To remove all resources including the DynamoDB table:
bash
npm run destroyThe --force flag is required to delete data resources like DynamoDB tables.
Next Steps
- Try the Authentication example to secure your API
- Explore Storage example for file uploads
- Learn about Timer example for scheduled tasks
Troubleshooting
Common Issues
"Table not found"
- Make sure the deployment completed successfully
- Check that the environment variable
KV_USERSis set
"Access Denied"
- Nimbus should automatically set IAM permissions
- Check CloudWatch logs for detailed error messages
"ValidationException"
- Check that your data matches the expected format
- Ensure required fields are present
"ConditionalCheckFailedException"
- This occurs when conditional operations fail
- Check your condition expressions