Client API Reference¶
Overview¶
The Dynamite client is the central configuration manager for DynamoDB connections. It handles client initialization, table synchronization, and connection lifecycle management. The Dynamite class provides a clean API for configuring AWS SDK DynamoDB clients and automatically creating tables with their Global Secondary Indexes (GSI).
Class: Dynamite¶
The main client class that manages DynamoDB connections and table operations.
Constructor¶
Creates a new Dynamite client instance with the provided configuration.
Parameters: - config (DynamiteConfig): Configuration object containing DynamoDB client settings and table definitions
Example:
import { Dynamite } from "@arcaelas/dynamite";
import { User, Order, Product } from "./models";
const client = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: {
accessKeyId: "test",
secretAccessKey: "test"
},
tables: [User, Order, Product]
});
Configuration¶
DynamiteConfig Interface¶
interface DynamiteConfig extends DynamoDBClientConfig {
tables: Array<new (...args: any[]) => any>;
}
The configuration interface extends AWS SDK's DynamoDBClientConfig and adds table definitions.
Properties:
| Property | Type | Required | Description |
|---|---|---|---|
tables | Array<Class> | Yes | Array of Table class constructors to register |
region | string | Yes | AWS region (e.g., "us-east-1") |
endpoint | string | No | Custom endpoint URL (for DynamoDB Local) |
credentials | AwsCredentials | No | AWS credentials object |
maxAttempts | number | No | Maximum retry attempts |
requestTimeout | number | No | Request timeout in milliseconds |
AWS Credentials¶
Instance Methods¶
connect()¶
Connects the client and sets it as the global DynamoDB client for Table operations. This method must be called before performing any Table operations.
Example:
const client = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: {
accessKeyId: "test",
secretAccessKey: "test"
},
tables: [User, Order]
});
client.connect();
// Now Table operations are available
const user = await User.create({ name: "John" });
Notes: - Idempotent operation - calling multiple times has no effect - Sets the internal client as the global client for all Table instances - Must be called before any Table.create(), Table.where(), etc.
sync()¶
Synchronizes all declared tables with DynamoDB. This method creates tables if they don't exist, including their Global Secondary Indexes (GSI) for @HasMany relationships.
Returns: - Promise<void>: Resolves when all tables are synchronized
Example:
await client.sync();
// All tables defined in config.tables are now created in DynamoDB
// with their primary keys, sort keys, and GSI indexes
Behavior: - Creates tables with PAY_PER_REQUEST billing mode - Automatically detects and creates GSI for @HasMany relationships - Idempotent - safe to call multiple times - Ignores ResourceInUseException (table already exists) - Throws errors for other failures
Table Creation Details: - Partition Key: Detected from @PrimaryKey() or @Index() decorator - Sort Key: Detected from @IndexSort() decorator (optional) - GSI: Automatically created for each @HasMany relationship with naming pattern GSI{N}_{foreignKey} - Billing Mode: PAY_PER_REQUEST (on-demand) - Attribute Definitions: Automatically inferred (all keys default to String type)
getClient()¶
Returns the underlying AWS SDK DynamoDB client instance.
Returns: - DynamoDBClient: The AWS SDK DynamoDB client
Example:
const awsClient = client.getClient();
// Use for direct AWS SDK operations
import { DescribeTableCommand } from "@aws-sdk/client-dynamodb";
const result = await awsClient.send(
new DescribeTableCommand({ TableName: "users" })
);
Use Cases: - Direct access to AWS SDK operations - Custom commands not supported by Dynamite - Advanced DynamoDB features - Testing and debugging
isReady()¶
Checks if the client is connected and all tables are synchronized.
Returns: - boolean: true if both connect() and sync() have completed successfully
Example:
console.log(client.isReady()); // false
client.connect();
await client.sync();
console.log(client.isReady()); // true
disconnect()¶
Disconnects and cleans up the DynamoDB client. Destroys the underlying AWS SDK client and resets internal state.
Example:
Behavior: - Calls client.destroy() on the underlying AWS SDK client - Resets connected and synced flags - Clears the global client reference if it matches this instance - Safe to call multiple times - Logs warnings if destruction fails
tx()¶
Executes operations within an atomic transaction. If any operation fails, all changes are automatically rolled back.
Type Parameters: - R: Return type of the callback function
Parameters: - callback ((tx: TransactionContext) => Promise<R>): Function containing transactional operations
Returns: - Promise<R>: Result returned by the callback function
Example:
const dynamite = new Dynamite({
region: "us-east-1",
tables: [User, Order]
});
dynamite.connect();
await dynamite.sync();
// Atomic transaction - all operations succeed or all fail
await dynamite.tx(async (tx) => {
const user = await User.create({ name: "John" }, tx);
await Order.create({ user_id: user.id, total: 100 }, tx);
await Order.create({ user_id: user.id, total: 200 }, tx);
// If any create fails, all operations are rolled back
});
Limitations: - Maximum 25 operations per transaction (DynamoDB limit) - Throws error if limit exceeded
Transaction Operations:
// Creating records in transaction
await dynamite.tx(async (tx) => {
await User.create({ name: "John" }, tx);
await User.create({ name: "Jane" }, tx);
});
// Mixed operations
await dynamite.tx(async (tx) => {
const user = await User.create({ name: "John" }, tx);
await user.destroy(tx); // Soft delete in transaction
});
Use Cases: - Creating related records atomically (user + orders) - Ensuring data consistency across multiple tables - Batch operations that must all succeed or all fail - Soft-deleting parent and child records together
Error Handling:
try {
await dynamite.tx(async (tx) => {
await User.create({ name: "John" }, tx);
throw new Error("Simulated failure");
// First create is rolled back
});
} catch (error) {
console.log("Transaction failed, all changes rolled back");
}
Class: TransactionContext¶
Internal class that manages transactional operations. Passed to callbacks in tx().
addPut()¶
Adds a Put operation to the transaction.
Parameters: - table_name (string): DynamoDB table name - item (Record<string, any>): Item to insert
addDelete()¶
Adds a Delete operation to the transaction.
Parameters: - table_name (string): DynamoDB table name - key (Record<string, any>): Primary key of item to delete
commit()¶
Commits all queued operations atomically. Called automatically by tx().
Throws: - Error: If transaction exceeds 25 operations - DynamoDB errors if transaction fails
Configuration Examples¶
Local Development (DynamoDB Local)¶
import { Dynamite } from "@arcaelas/dynamite";
import { User, Order, Product } from "./models";
const client = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: {
accessKeyId: "test",
secretAccessKey: "test"
},
tables: [User, Order, Product]
});
client.connect();
await client.sync();
Docker Setup:
Docker Compose:
version: '3.8'
services:
dynamodb-local:
image: amazon/dynamodb-local
ports:
- "8000:8000"
command: ["-jar", "DynamoDBLocal.jar", "-sharedDb"]
AWS Production Configuration¶
const client = new Dynamite({
region: process.env.AWS_REGION || "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
},
tables: [User, Order, Product],
maxAttempts: 3,
requestTimeout: 3000
});
client.connect();
await client.sync();
Environment Variables¶
# .env file
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
# For local development
DYNAMODB_ENDPOINT=http://localhost:8000
import { Dynamite } from "@arcaelas/dynamite";
import * as dotenv from "dotenv";
dotenv.config();
const config: any = {
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
},
tables: [User, Order, Product]
};
// Add endpoint only for local development
if (process.env.DYNAMODB_ENDPOINT) {
config.endpoint = process.env.DYNAMODB_ENDPOINT;
}
const client = new Dynamite(config);
client.connect();
await client.sync();
Multiple Client Instances¶
You can create multiple Dynamite clients for different configurations or regions.
import { Dynamite } from "@arcaelas/dynamite";
import { User, Order } from "./models";
import { Log, Metric } from "./monitoring";
// Production database client
const production_client = new Dynamite({
region: "us-east-1",
credentials: {
accessKeyId: process.env.PROD_AWS_KEY!,
secretAccessKey: process.env.PROD_AWS_SECRET!
},
tables: [User, Order]
});
// Analytics database client (different region)
const analytics_client = new Dynamite({
region: "us-west-2",
credentials: {
accessKeyId: process.env.ANALYTICS_AWS_KEY!,
secretAccessKey: process.env.ANALYTICS_AWS_SECRET!
},
tables: [Log, Metric]
});
// Connect production client (becomes global)
production_client.connect();
await production_client.sync();
// User and Order operations use production_client
const user = await User.create({ name: "John" });
// Switch to analytics client
analytics_client.connect();
await analytics_client.sync();
// Log and Metric operations now use analytics_client
const log = await Log.create({ message: "User created" });
Important Notes: - Only one client can be the "global" client at a time - Calling connect() on a new client replaces the global client - Table operations always use the current global client - Consider using explicit client passing for multi-client scenarios
Custom Configuration Options¶
const client = new Dynamite({
region: "us-east-1",
endpoint: "https://dynamodb.us-east-1.amazonaws.com",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
},
tables: [User, Order, Product],
// Advanced AWS SDK options
maxAttempts: 5,
requestTimeout: 5000,
// Custom retry strategy
retryMode: "adaptive",
// Enable logging
logger: console
});
Utility Functions¶
setGlobalClient()¶
Sets the global DynamoDB client for Table operations. Typically called internally by Dynamite.connect().
Parameters: - client (DynamoDBClient): AWS SDK DynamoDB client instance
Example:
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { setGlobalClient } from "@arcaelas/dynamite";
const custom_client = new DynamoDBClient({
region: "us-east-1"
});
setGlobalClient(custom_client);
getGlobalClient()¶
Gets the current global DynamoDB client. Throws an error if no client is set.
Returns: - DynamoDBClient: The global DynamoDB client
Throws: - Error: If no global client has been set
Example:
import { getGlobalClient } from "@arcaelas/dynamite";
try {
const client = getGlobalClient();
console.log("Client is configured");
} catch (error) {
console.error("No client configured");
}
hasGlobalClient()¶
Checks if a global DynamoDB client is available.
Returns: - boolean: true if a global client exists
Example:
import { hasGlobalClient } from "@arcaelas/dynamite";
if (hasGlobalClient()) {
console.log("Client is available");
} else {
console.log("No client configured");
}
requireClient()¶
Requires a global client to be available. Throws an error with a localized message if not set.
Returns: - DynamoDBClient: The global DynamoDB client
Throws: - Error: If no global client is configured (Spanish error message)
Example:
import { requireClient } from "@arcaelas/dynamite";
try {
const client = requireClient();
// Use client for operations
} catch (error) {
console.error(error.message); // "DynamoDB client no configurado. Use Dynamite.connect() primero."
}
Error Handling¶
Common Errors¶
ResourceInUseException¶
Thrown when attempting to create a table that already exists.
try {
await client.sync();
} catch (error) {
if (error.name === "ResourceInUseException") {
console.log("Table already exists");
}
}
Note: Dynamite automatically handles this error during sync().
ValidationException¶
Thrown when table schema or attributes are invalid.
try {
await client.sync();
} catch (error) {
if (error.name === "ValidationException") {
console.error("Invalid table schema:", error.message);
}
}
Common Causes: - Missing @PrimaryKey() or @Index() decorator - Reserved keyword used as attribute name - Invalid attribute type - PK and SK with same attribute name
UnrecognizedClientException¶
Thrown when credentials are invalid or DynamoDB endpoint is unreachable.
try {
client.connect();
await client.sync();
} catch (error) {
if (error.name === "UnrecognizedClientException") {
console.error("Invalid credentials or endpoint");
}
}
Solutions: - Verify AWS credentials - Check DynamoDB Local is running (for local development) - Verify endpoint URL is correct - Check network connectivity
Metadata Not Found¶
Thrown when attempting to sync a table without proper decorators.
try {
await client.sync();
} catch (error) {
if (error.message.includes("not registered in wrapper")) {
console.error("Table class missing decorators");
}
}
Solution: Ensure all table classes use @PrimaryKey() or @Index() decorator.
No Global Client¶
Thrown when attempting Table operations without connecting first.
try {
const user = await User.create({ name: "John" });
} catch (error) {
if (error.message.includes("DynamoDB client no configurado")) {
console.error("Call client.connect() first");
}
}
Error Handling Best Practices¶
import { Dynamite } from "@arcaelas/dynamite";
import { User, Order } from "./models";
async function initialize_database() {
const client = new Dynamite({
region: process.env.AWS_REGION || "us-east-1",
endpoint: process.env.DYNAMODB_ENDPOINT,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || "test",
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "test"
},
tables: [User, Order]
});
try {
// Connect client
client.connect();
console.log("Client connected");
// Sync tables
await client.sync();
console.log("Tables synchronized");
// Verify ready state
if (client.isReady()) {
console.log("Database ready for operations");
}
return client;
} catch (error: any) {
// Handle specific errors
if (error.name === "UnrecognizedClientException") {
console.error("Authentication failed. Check credentials.");
} else if (error.name === "ValidationException") {
console.error("Invalid table schema:", error.message);
} else if (error.message?.includes("not registered in wrapper")) {
console.error("Table class missing decorators");
} else {
console.error("Database initialization failed:", error);
}
// Cleanup on failure
client.disconnect();
throw error;
}
}
// Usage
try {
const client = await initialize_database();
// Perform operations
const user = await User.create({ name: "John" });
// Cleanup on shutdown
process.on("SIGINT", () => {
client.disconnect();
process.exit(0);
});
} catch (error) {
console.error("Application failed to start");
process.exit(1);
}
Complete Usage Example¶
Basic Application Setup¶
import { Dynamite } from "@arcaelas/dynamite";
import {
Table,
PrimaryKey,
Default,
CreatedAt,
UpdatedAt,
HasMany,
CreationOptional,
NonAttribute
} from "@arcaelas/dynamite";
// Define models
class User extends Table<User> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
declare name: string;
declare email: string;
@Default(() => "customer")
declare role: CreationOptional<string>;
@CreatedAt()
declare createdAt: CreationOptional<string>;
@UpdatedAt()
declare updatedAt: CreationOptional<string>;
@HasMany(() => Order, "user_id")
declare orders: NonAttribute<Order[]>;
}
class Order extends Table<Order> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
declare user_id: string;
declare total: number;
@Default(() => "pending")
declare status: CreationOptional<string>;
@CreatedAt()
declare createdAt: CreationOptional<string>;
}
// Initialize client
async function setup_database() {
const client = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: {
accessKeyId: "test",
secretAccessKey: "test"
},
tables: [User, Order]
});
// Connect and sync
client.connect();
await client.sync();
console.log("Database ready:", client.isReady());
return client;
}
// Application entry point
async function main() {
const client = await setup_database();
try {
// Create user
const user = await User.create({
name: "John Doe",
email: "john@example.com"
});
console.log("User created:", user.id);
// Create orders
const order1 = await Order.create({
user_id: user.id,
total: 99.99
});
const order2 = await Order.create({
user_id: user.id,
total: 149.99
});
// Query with relationships
const users_with_orders = await User.where({ id: user.id }, {
include: {
orders: {
where: { status: "pending" }
}
}
});
console.log("User orders:", users_with_orders[0].orders.length);
} finally {
// Cleanup on exit
client.disconnect();
}
}
main().catch(console.error);
Express.js Integration¶
import express from "express";
import { Dynamite } from "@arcaelas/dynamite";
import { User } from "./models";
const app = express();
app.use(express.json());
// Initialize database
let client: Dynamite;
async function initialize() {
client = new Dynamite({
region: process.env.AWS_REGION || "us-east-1",
endpoint: process.env.DYNAMODB_ENDPOINT,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || "test",
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "test"
},
tables: [User]
});
client.connect();
await client.sync();
console.log("Database initialized");
}
// API routes
app.get("/users", async (req, res) => {
try {
const users = await User.where({});
res.json(users);
} catch (error) {
res.status(500).json({ error: "Failed to fetch users" });
}
});
app.post("/users", async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (error) {
res.status(500).json({ error: "Failed to create user" });
}
});
// Start server
initialize()
.then(() => {
app.listen(3000, () => {
console.log("Server running on port 3000");
});
})
.catch((error) => {
console.error("Failed to start:", error);
process.exit(1);
});
// Graceful shutdown
process.on("SIGINT", () => {
console.log("Shutting down...");
client.disconnect();
process.exit(0);
});
Best Practices¶
1. Single Client Instance¶
Create one client instance per application and reuse it.
// Good
const client = new Dynamite({ /* config */ });
client.connect();
await client.sync();
// Bad - creates multiple clients unnecessarily
function get_client() {
return new Dynamite({ /* config */ });
}
2. Call sync() Once¶
Call sync() only during application initialization, not before every operation.
// Good - sync once at startup
await client.sync();
const user = await User.create({ name: "John" });
const order = await Order.create({ user_id: user.id });
// Bad - syncing repeatedly
await client.sync();
const user = await User.create({ name: "John" });
await client.sync();
const order = await Order.create({ user_id: user.id });
3. Graceful Shutdown¶
Always disconnect the client on application shutdown.
process.on("SIGINT", () => {
console.log("Shutting down gracefully");
client.disconnect();
process.exit(0);
});
process.on("SIGTERM", () => {
console.log("Shutting down gracefully");
client.disconnect();
process.exit(0);
});
4. Environment-Based Configuration¶
Use environment variables for configuration to separate dev/staging/production.
const client = new Dynamite({
region: process.env.AWS_REGION || "us-east-1",
endpoint: process.env.NODE_ENV === "development"
? "http://localhost:8000"
: undefined,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
},
tables: [User, Order, Product]
});
5. Error Handling¶
Always handle errors during initialization and provide meaningful feedback.
try {
client.connect();
await client.sync();
} catch (error: any) {
if (error.name === "UnrecognizedClientException") {
console.error("Check DynamoDB Local is running: docker run -p 8000:8000 amazon/dynamodb-local");
} else {
console.error("Database initialization failed:", error.message);
}
process.exit(1);
}
6. Testing Setup¶
Use separate clients for testing with isolated configuration.
// test/setup.ts
import { Dynamite } from "@arcaelas/dynamite";
import { User, Order } from "../models";
export async function setup_test_database() {
const client = new Dynamite({
region: "us-east-1",
endpoint: "http://localhost:8000",
credentials: {
accessKeyId: "test",
secretAccessKey: "test"
},
tables: [User, Order]
});
client.connect();
await client.sync();
return client;
}
export async function teardown_test_database(client: Dynamite) {
client.disconnect();
}
See Also¶
- Table API Reference - Complete Table class documentation
- Decorators Reference - All available decorators
- AWS SDK DynamoDB Client - Underlying AWS SDK documentation
- DynamoDB Local - Local development setup