Skip to content
This repository was archived by the owner on Jul 17, 2026. It is now read-only.

Getting started

Eugene Lazutkin edited this page Apr 20, 2026 · 1 revision

Getting started

This page walks through complete working examples: a DynamoDB-backed REST API on Cloudflare Workers, Bun, and Deno.

Prerequisites

  • A DynamoDB table (or DynamoDB Local running at http://localhost:8000 for 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.).

Install

npm install dynamodb-toolkit-fetch dynamodb-toolkit @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

No framework peer is required — Request, Response, URL, URLSearchParams, ReadableStream, TextDecoder are platform primitives on every target runtime.

Bun.serve

// 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

Deno.serve

// 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

Cloudflare Workers

// 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      # ship

Node (with a Fetch server)

Node 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});

Try it

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/pluto

See Routes for the full route pack.

Without a mount prefix

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.

Composing with a host router (Hono, itty-router)

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.

Next steps

Clone this wiki locally