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

Options

Eugene Lazutkin edited this page Apr 23, 2026 · 2 revisions

Options

createExpressAdapter(adapter, options?) accepts an optional options object with the following shape:

interface ExpressAdapterOptions<TItem extends Record<string, unknown> = Record<string, unknown>> {
  policy?: Partial<RestPolicy>;
  sortableIndices?: Record<string, string>;
  keyFromPath?: (rawKey: string, adapter: Adapter<TItem>) => Record<string, unknown>;
  exampleFromContext?: (query: Record<string, string>, body: unknown, req: Request) => Record<string, unknown>;
  maxBodyBytes?: number;
}

All options are optional. The factory is generic in TItem so that typed Adapter<Planet, PlanetKey> flows through to keyFromPath's second argument without casts.

policy

Partial overrides for the REST policy. Merged shallowly with defaultPolicy from dynamodb-toolkit/rest-core — only the fields you pass are touched.

createExpressAdapter(adapter, {
  policy: {
    defaultLimit: 25,
    maxLimit: 500,
    maxOffset: 10_000,                 // DoS cap on `?offset=` (since toolkit 3.1.1)
    needTotal: false,
    envelope: {items: 'rows', total: 'count', offset: 'skip', limit: 'take', links: 'links'},
    metaPrefix: '$',
    methodPrefix: '!',
    statusCodes: {
      miss: 410,
      validation: 422,
      consistency: 409,
      throttle: 429,
      transient: 503,
      internal: 500
    }
  }
});

See the parent wiki's REST core page for the full RestPolicy reference.

sortableIndices

Maps the public sort-field name (?sort=field or ?sort=-field) to the GSI that provides that ordering. Without an entry, ?sort=… is ignored (no index set, no error).

createExpressAdapter(adapter, {
  sortableIndices: {
    name:      'by-name-index',
    createdAt: 'by-created-index',
    mass:      'by-mass-index'
  }
});

?sort=-createdAt then resolves to {index: 'by-created-index', descending: true} and is passed to the Adapter's list call.

keyFromPath

Convert the URL :key path segment into a key object. Runs on every keyed route. Default:

(raw, adapter) => ({[adapter.keyFields[0].name]: raw})

Overrides are the idiomatic place for composite keys, type coercion, and key-format validation:

// Composite "partition:sort" keys
createExpressAdapter(adapter, {
  keyFromPath: raw => {
    const [pk, sk] = raw.split(':');
    if (!pk || !sk) {
      throw Object.assign(new Error('Expected key of shape pk:sk'), {status: 400, code: 'BadKey'});
    }
    return {pk, sk};
  }
});

// Numeric partition keys
createExpressAdapter(adapter, {
  keyFromPath: (raw, a) => {
    const n = Number(raw);
    if (!Number.isFinite(n)) throw Object.assign(new Error('Numeric key expected'), {status: 400});
    return {[a.keyFields[0].name]: n};
  }
});

The adapter is passed through so you can read adapter.keyFields when writing generic callbacks. See Composite keys for more patterns.

exampleFromContext

Build the example object passed to Adapter.prepareListInput(example, index). Runs on the collection-level routes that call the Adapter's list machinery: GET /, DELETE /, PUT /-clone, PUT /-move.

Signature:

(query, body, req) => exampleObject

Default: () => ({}) — no example, prepareListInput derives everything from index alone.

createExpressAdapter(adapter, {
  exampleFromContext: (query, _body, req) => ({
    tenant: req.user.tenantId,  // set by upstream auth middleware
    status: query.status || 'active'
  })
});

The three args:

  • query — parsed URL query as Record<string, string>. Nested-object values from Express's qs parser are dropped; array values are collapsed to the first element.
  • body — parsed request body, unknown. Only defined for PUT /-clone / PUT /-move, which have a body; null on GET / / DELETE /.
  • req — the full Express Request. Use it to pull auth info from upstream middleware (req.user), request metadata (req.headers, req.ip), etc.

See Per-tenant filtering for a complete cookbook.

maxBodyBytes

Byte cap for raw request bodies. Only enforced when the adapter is streaming the body itself — i.e. when req.body is undefined. If a body-parser middleware (express.json() or equivalent) has already populated req.body, that parser's own cap applies.

Default: 1048576 (1 MiB), matching dynamodb-toolkit/handler.

// Low-power device endpoint — cap at 64 KiB
createExpressAdapter(adapter, {maxBodyBytes: 64 * 1024});

// Dedicated bulk-load endpoint — allow larger bodies
createExpressAdapter(bulkAdapter, {maxBodyBytes: 16 * 1024 * 1024});

Over-cap requests are rejected with 413 PayloadTooLarge and {code: 'PayloadTooLarge'}. Malformed JSON gives 400 BadJsonBody.

See Body parsing for the full streaming / pre-parsed decision flow.

Interaction with parent HandlerOptions

The options surface is a direct superset of dynamodb-toolkit/handler's HandlerOptions:

  • policy, sortableIndices, keyFromPath, maxBodyBytes — identical shape and semantics.
  • exampleFromContext — extended from the core's (query, body) signature with a third req argument. Callbacks written against the core handler still work (extra args ignored).

Clone this wiki locally