A small React app for explaining event sourcing with a DynamoDB-flavoured example.
It uses @rooster212/event-sourced-rules-engine for aggregate orchestration,
event processing rules, and the browser storage adapter.
The app uses a bank account aggregate to show how commands such as CreateAccount,
Deposit, Withdraw, and FreezeAccount become immutable events. Those events are
then committed through the rules engine and replayed in order to build an account
projection with a balance, status, counters, and the latest stream version.
A hosted version of this is available here: https://eventsourcing.jamieroos.dev/
Event sourcing can be hard to explain because the important behaviour is indirect: the application does not simply update a row of current state. It appends facts to an event stream, then derives current state from those facts.
This project makes that flow visible:
- A command represents an intention to change an account.
- The rules engine commits accepted events to an aggregate stream.
- A
Processorrule updates projection state for each event type. - A projection is rebuilt by applying each event in sequence.
- A DynamoDB item preview shows how an event could be stored with partition and sort keys.
The example is deliberately small. It uses the library's browser storage contract with
an in-memory Storage implementation so refreshing the browser still resets the demo.
It is intended as a teaching aid, not as a production event store.
- Command handling for a simple account aggregate.
- Appending immutable account events through
Aggregate.append. - Replaying events with
Processorrules to derive a projection. - Rejected commands becoming explicit audit events, such as
WithdrawalRejected. - A DynamoDB single-table style event item:
PKas the aggregate stream key, for exampleACCOUNT#ACCOUNT-1001.SKas the ordered event key, for exampleEVENT#000003.- metadata showing the originating command and recorded timestamp.
- a secondary index shape for querying by event type.
- DynamoDB deployment concepts such as append-only writes, optimistic concurrency, and projection updates from streams or workers.
- Node.js
20.19.0or newer, or Node.js22.12.0or newer. - npm.
- Access to GitHub Packages for the
@rooster212npm scope. - Docker, if you want to build and run the production container.
The local project was built with Node 24.
The project .npmrc points @rooster212 at GitHub Packages and uses
legacy-peer-deps=true so npm does not install the library's optional AWS/CDK peer
dependencies for this browser-only demo.
Install dependencies:
npm installStart the Vite development server:
npm run devOpen:
http://localhost:5173
Create a production build:
npm run buildThe static site is written to dist/.
To preview the production build locally:
npm run previewBuild the container image for your current machine:
docker build --secret id=npmrc,src=$HOME/.npmrc -t dynamodb-event-sourcing-lab .Run it locally:
docker run --rm -p 8080:80 dynamodb-event-sourcing-labOpen:
http://localhost:8080
The Docker image uses a multi-stage build:
node:24-alpineinstalls dependencies and runsnpm run build.nginx:stable-alpineserves the generateddist/files.- BuildKit uses the native build platform for the Node/Vite build, then selects the nginx runtime image for the requested target platform.
- The
npmrcsecret givesnpm ciaccess to GitHub Packages without copying your token into the image.
To build one target architecture and load it into your local Docker image store:
docker buildx build --platform linux/arm64 --secret id=npmrc,src=$HOME/.npmrc -t dynamodb-event-sourcing-lab --load .
docker buildx build --platform linux/amd64 --secret id=npmrc,src=$HOME/.npmrc -t dynamodb-event-sourcing-lab --load .To publish a multi-architecture image for both ARM64 and AMD64, tag it with your registry name and push it:
docker buildx build \
--platform linux/amd64,linux/arm64 \
--secret id=npmrc,src=$HOME/.npmrc \
-t ghcr.io/rooster212/event-sourced-example-app-banking:latest .
# push to github registry
docker push ghcr.io/rooster212/event-sourced-example-app-banking:latestThe nginx config includes a fallback to index.html, so the app can support client-side
routes if routes are added later.
- The app stores demo events through the rules engine's browser storage adapter, backed
by an in-memory
Storageimplementation. Refreshing the browser resets the demo. - There is no real DynamoDB table in this project.
- The DynamoDB JSON shown in the UI is an educational representation of how events can be shaped for storage.