-
Notifications
You must be signed in to change notification settings - Fork 0
Getting started
This page walks through complete working examples: a DynamoDB-backed REST API on Cloudflare Workers, Bun, and Deno.
- A DynamoDB table (or DynamoDB Local running at
http://localhost:8000for experimentation). - Node 20+, Bun, Deno, or a Cloudflare Workers / Deno Deploy account.
- AWS credentials reachable from the target runtime (env vars, IAM role,
wrangler secret, etc.).
npm install dynamodb-toolkit-fetch dynamodb-toolkit @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodbNo framework peer is required — Request, Response, URL, URLSearchParams, ReadableStream, TextDecoder are platform primitives on every target runtime.
// server.js
import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient} from '@aws-sdk/lib-dynamodb';
import {Adapter} from 'dynamodb-toolkit';
import {createFetchAdapter} from 'dynamodb-toolkit-fetch';
const ddb = DynamoDBDocumentClient.from(
new DynamoDBClient({region: 'us-east-1'}),
{marshallOptions: {removeUndefinedValues: true}}
);
const planets = new Adapter({client: ddb, table: 'planets', keyFields: ['name']});
const handler = createFetchAdapter(planets, {mountPath: '/planets'});
Bun.serve({port: 3000, fetch: handler});
console.log('Listening on http://localhost:3000');bun run server.js// server.ts
import {DynamoDBClient} from 'npm:@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient} from 'npm:@aws-sdk/lib-dynamodb';
import {Adapter} from 'npm:dynamodb-toolkit';
import {createFetchAdapter} from 'npm:dynamodb-toolkit-fetch';
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({region: 'us-east-1'}));
const planets = new Adapter({client: ddb, table: 'planets', keyFields: ['name']});
const handler = createFetchAdapter(planets, {mountPath: '/planets'});
Deno.serve({port: 3000}, handler);deno run --allow-net --allow-env server.ts// src/index.js
import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient} from '@aws-sdk/lib-dynamodb';
import {Adapter} from 'dynamodb-toolkit';
import {createFetchAdapter} from 'dynamodb-toolkit-fetch';
let handler; // lazy — reuses the same Adapter across warm invocations
const buildHandler = env => {
const ddb = DynamoDBDocumentClient.from(
new DynamoDBClient({
region: env.AWS_REGION,
credentials: {
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY
}
})
);
const planets = new Adapter({client: ddb, table: 'planets', keyFields: ['name']});
return createFetchAdapter(planets, {mountPath: '/planets'});
};
export default {
fetch(request, env) {
handler ||= buildHandler(env);
return handler(request);
}
};# wrangler.toml
name = "planets-api"
main = "src/index.js"
compatibility_date = "2025-01-01"wrangler dev # local
wrangler deploy # shipNode 20+ ships Request / Response / URL globally. Any wrapper that exposes Fetch will work; @hono/node-server is a light option:
import {serve} from '@hono/node-server';
import {createFetchAdapter} from 'dynamodb-toolkit-fetch';
const handler = createFetchAdapter(planets, {mountPath: '/planets'});
serve({port: 3000, fetch: handler});Assuming the server is running on http://localhost:3000:
# Create
curl -X POST http://localhost:3000/planets/ \
-H 'content-type: application/json' \
-d '{"name":"earth","mass":5.97,"climate":"temperate"}'
# Read one
curl http://localhost:3000/planets/earth
# Partial update (PATCH merges, does not replace)
curl -X PATCH http://localhost:3000/planets/earth \
-H 'content-type: application/json' \
-d '{"population":8.2e9}'
# List with paging
curl 'http://localhost:3000/planets/?offset=0&limit=25'
# Projection
curl 'http://localhost:3000/planets/?fields=name,mass'
# Bulk fetch by name
curl 'http://localhost:3000/planets/-by-names?names=earth,mars,venus'
# Delete
curl -X DELETE http://localhost:3000/planets/plutoSee Routes for the full route pack.
If your adapter owns the whole URL (typical for a single-collection Worker or a dedicated Deno.serve listener), omit mountPath:
const handler = createFetchAdapter(planets);
Deno.serve(handler);
// GET /earth, POST /, PATCH /earth — all hit the adapter directly.The adapter then matches routes against the full URL.pathname.
If the adapter shares a runtime with other routes, pass onMiss: () => null so it can yield control to the host router on off-mount paths:
import {Hono} from 'hono';
import {createFetchAdapter} from 'dynamodb-toolkit-fetch';
const planetsHandler = createFetchAdapter(planets, {
mountPath: '/planets',
onMiss: () => null
});
const app = new Hono();
app.all('/planets/*', async c => (await planetsHandler(c.req.raw)) ?? c.notFound());
app.get('/health', c => c.json({ok: true}));See Composition for the full cookbook.
- Custom error handling — see Error handling.
- Composite keys (partition + sort) — see Composite keys.
- Per-tenant list filtering — see Per-tenant filtering.
- Sharing a runtime with other handlers — see Composition.