Why Ethereum data still needs a database
Even as Ethereum transaction costs climb and the network's throughput limits become harder to ignore, the ecosystem keeps attracting new builders. The chain is great at consensus and deterministic state, but it is not a good place to store application data. That distinction matters when you build a dapp: the smart contracts hold the source of truth for token ownership and transfers, yet the UI and business logic still need a queryable, relational data layer that responds in milliseconds rather than block times.
This walkthrough shows one way to combine two tools that solve different halves of that problem. Redwood.js is the full-stack framework with Apollo Client and Apollo Server built in, and Fauna is the globally distributed, ACID-compliant database with a GraphQL API. We’ll take an existing Ethereum app — Emanator, which uses Superfluid to stream NFTs to auction — and swap its data layer from Prisma/Postgres to Fauna.
The starting point is a fork of the Emanator monorepo, originally built for a Superfluid hackathon. A working version with Fauna integrated lives on the integrating-fauna branch of redwood-eth-with-fauna. We’ll work through the setup, then follow the diffs that make the switch.
What Fauna brings to a dapp stack
Fauna is a serverless database fronted by a native GraphQL interface. The underlying storage is globally distributed: each region holds a partition of the data, and requests replicate asynchronously with every transaction. For application developers, the practical benefits are summarized by three properties:
- Transactional — every request is run as a single ACID transaction
- Multi-document — transactions can span collections without eventual-consistency gaps
- Geo-distributed — reads and writes are served close to the user
Those ACID guarantees are the differentiator against alternatives like Firebase, Cassandra, or MongoDB. To spell them out:
- Atomic — a transaction is all-or-nothing; no partial success
- Consistent — a transaction brings the database from one valid state to another
- Isolation — concurrent transactions behave as if run sequentially
- Durability — committed transactions survive downtime or failure
Because Fauna’s GraphQL layer accepts a schema import, it is a natural fit for a Redwood.js project that already defines its API in SDL files. The integration is not entirely frictionless — Fauna lacks support for custom scalars, which forces a few schema and component edits — but the core query path maps cleanly to FQL, Fauna’s expression-oriented query language.
Project setup
The fork brings a few prerequisites. Install the MetaMask browser extension first, since minting an NFT on the Ethereum side requires a wallet. You will also need an Infura account and project ID: the environment variable INFURA_ENDPOINT_KEY is actually the Infura PROJECT ID, not a full endpoint URL.
To get started:
- Clone
redwood-eth-with-faunaand install dependencies. - Copy the
.env.examplefile to.envand setINFURA_ENDPOINT_KEYplus a placeholder for the Fauna secret key. - Open
api/src/graphql/auctions.sdl.jsand add a missingcontentHashfield of typeStringto the Auction model — a bug in the monorepo fork omits it. - Run Redwood’s database migration command from the schema change.
At this point the original app can start. Minting your first NFT should work, though you may hit an error after the MetaMask confirmation; a page refresh renders the new token. The auction detail view also triggers a Redwood API resolver update on first navigation.
Shut the dev server down before integrating Fauna so Redwood’s hot reloading does not fight your edits.
Importing the schema into Fauna
Fauna’s schema import has one constraint that shapes the whole integration: unlike Redwood’s SDL files, Fauna’s GraphQL API does not support custom scalars. Redwood splits its schema into three files — auctions.sdl.js, bids.sdl.js, and web3.sdl.js — and Emanator defines a custom type in one of them.
The fix is to create a stitched, Redwood-agnostic schema at api/src/graphql/fauna-schema-to-import.gql that uses only native GraphQL scalars. That file is what you paste into the Fauna dashboard when creating the database. After the import, the three SDL files in the project must be edited to match the Fauna schema; otherwise Redwood’s Apollo server and Fauna’s GraphQL API will disagree on field types.
Your Fauna secret key goes into .env as FAUNA_SECRET_KEY. Keep the quotation marks if they are in the example file.
Swapping the data layer
If you prefer to inspect a working state rather than reconstruct every diff, check out the integrating-fauna branch:
The essential changes fall into four files:
Installing drivers
The Fauna JavaScript driver, faunadb, plus graphql-request for direct GraphQL calls, are added to the API workspace dependencies.
Replacing the Prisma client
The Redwood scaffold generates api/src/lib/db.js with a PrismaClient instance. That file is rewritten to create a Fauna client instead. This is a deliberate bypass of Redwood’s default ORM: the Prisma client no longer exists, so any service that imports db will receive the Fauna client.
A small companion file, api/src/lib/fauna-client.js, instantiates the client with the secret and query options referenced across the services.
Rewriting the auctions service with FQL
The most involved edit is api/src/services/auctions/auctions.js. Every db.auction Prisma call is replaced with an FQL expression. The read path queries the auctions index:
- Use
q.Mapandq.Paginateover the matched references to return document data. - The query result is an object keyed by document ID, so destruct into an array of auction objects before returning.
- Fetching one auction becomes a lookup in the
Auctioncollection filtered by the address field.
Creating an auction is where the custom scalar limitation bites. The Redwood service receives an input argument from the create form, whose fields are address, name, owner, winLength, description, and contentHash. But the Auction GraphQL type also requires id, dateTime, status, and highBid. Those four fields are hardcoded in the service so that the document satisfies the full schema. The resulting FQL creates a new document in the Auction collection with the combined field set, then returns the stored document through the Fauna client’s query method.
Finishing the remaining services
The bids.js and web3.js services receive equivalent treatment: Fauna client queries replace Prisma calls. Because the web3.js schema used a custom scalar, its pastAuctions field is commented out of the SDL and out of the service’s query. Keeping that field untouched would fail both the Fauna import and the Redwood/Apollo type checks.
One UI tweak rounds out the integration. In web/src/components/AuctionCell/AuctionCell.js, the auction detail page is updated so that newly created NFT address domains are clickable. Without this, the auction cell throws when rendering the owner address.
Caveats on fresh mint flow
Two known issues remain.
First, after confirming a mint transaction in MetaMask, the web UI shows an error above the create form. There is no clean fix yet; a page refresh renders the new NFT on the right, with the token data read from Fauna.
Second, the auction detail page for a newly minted NFT is still not navigable without hitting the AuctionCell bug. The repository is public on GitHub — the integrating-fauna branch has a working but imperfect version — so both issues are open for community fixes.
For anyone exploring this further, the README of the original Emanator monorepo explains the Superfluid instant distribution agreement, the auction mechanics, and the revenue split that make the smart contract side work.



