Example of a NestJS application with PostgreSQL and Axios integrating into multiple Web3 SDKs to demonstrate how to handle complex integration scenarios where you are building on multiple chains and APIs including Ethereum, Solana and Bitcoin(WIP).
This project modularizes popular Web3 SDKs, handles the lifecycle management, abstracts away complexity, adds configuration mgmt and exposes simplified interfaces in REST API, GraphQL, Websocket and SSE(Server-Sent Events) endpoint formats.
These endpoints are consumed by ultra-lightweight, nano-clients including:
- raw html+vanilla.js
- htmx(single-file)
- Swagger Web UI
- GraphQL Playground
The resulting NestJS app will be a Dockerized, stateless, cloud-native, horizontal-scalable, microservice-oriented backend for a decentralized application.
Reactive, async, non-blocking, event-driven back-ends are very powerful for on-chain, blockchain-powered dApps.
Endpoints and ultra light-weight clients for consuming data.
- Swagger: http://127.0.0.1:3000/api
- Script: scripts/get.http
- OpenAPI JSON(Import into Postman): http://127.0.0.1:3000/api-json
- GraphQL Playground: http://localhost:3000/graphql
- Script: scripts/graphql.http
- cURL Shell Script: scripts/ethers/graphql-curl.sh
- Script: scripts/ws.http
- cURL Shell Script: sse-curl.sh
- VanillaJS: rest-finalized-block.html
- VanillaJS: graphql-block-query.html
- VanillaJS: ws-block-number.html
- VanillaJS: ws-finalized-blocks.html
- VanillaJS: block-number.html
- VanillaJS: finalized-blocks.html
- HTMX: htmx-block-number.html
- VanillaJS: sol-rest-block-number.html
- VanillaJS: sol-graphql-block-query.html
- VanillaJS: ws-sol-block-number.html
- VanillaJS: ws-sol-finalized-blocks.html
- VanillaJS: sol-block-number.html
- VanillaJS: WIP
- VanillaJS: WIP
- VanillaJS: WIP
- VanillaJS: WIP
- VanillaJS: WIP
The SDKs used in this example:
- Stablecoin cards: Reap
- Interoperability: Wormhole
- On-chain IoT: Peaq
- EVM: Ethers.js
- Solana: Solana kit
- Bitcoin(WIP): bitcoinjs-lib
Build/install and add Endor cli.
CLI used for running local PostgreSQL and other cloud services.
pnpm install
npm install -g @endorhq/cliStart PostgreSQL:
ℹ️ NOTE: Start each in a separate terminal tab
This will start a local PostgreSQL instance using Endor CLI.
pnpm dbStart service:
Lint test and run dev.
pnpm devSetup .env file:
- Get an RPC node for Ethereum from: https://www.infura.io/
- Get an RPC node for Solana from: https://www.helius.dev/
- Get an RPC node for Bitcoin from: https://www.alchemy.com/
- (OPTIONAL)Login to Reap to provision an API key.
cp .env.example .envREAP_BASE_URL=https://sandbox.api.caas.reap.global/
REAP_API_KEY=
REAP_DOCS=https://reap.readme.io/reference/test-environment
APP_URL=127.0.0.1
PEAQ_RPC_SERVER_URL=https://quicknode1.peaq.xyz
PEAQ_WSS_SERVER_URL=wss://quicknode1.peaq.xyz
ETH_DEV_SEED=
ETH_DEV_ADDRESS=
ETHERS_RPC_SERVER_URL=
ETHERS_RPC_API_KEY=
SOLKIT_RPC_SERVER_URL=
SOLKIT_WS_SERVER_URL=
SOLKIT_RPC_API_KEY=
BITCOIN_RPC_SERVER_URL=
BITCOIN_RPC_API_KEY=Install Foundry CLI:
Foundry is a CLI tool suite for working with Ethereum/Solidity smart
contracts.
We will use cast to create a new wallet.
curl -L https://foundry.paradigm.xyz | bash
foundryupCreate a new EVM wallet:
Set this as your ETH_DEV_SEED. Only use this for dev.
https://getfoundry.sh/cast/reference/wallet
cast wallet newRun local eth node
anvil --fork-url https://reth-ethereum.ithaca.xyz/rpcFaucets:
- https://cloud.google.com/application/web3/faucet/ethereum
- Fund wallet with peaq: https://docs.peaq.xyz/build/getting-started/fund-your-wallet
Open browser: After starting the service, open the following:
- Swagger API Docs: http://127.0.0.1:3000/api
- Home page: http://127.0.0.1:3000/
The command pnpm db abstracts away the complexity of running a local PostgreSQL node.
Here are more details on how it's used.
https://docs.endor.dev/cli/services/postgres/
Endor CLI: A lightweight developer tool to run local cloud services (like PostgreSQL) with one command using reproducible containers. See docs: https://docs.endor.dev/cli/
Endor PostgreSQL
endor run postgresConnect to PostgreSQL via CLI
psql -h localhost -U postgres -d postgres# lint test and run dev
pnpm dev
# development
$ pnpm run start
# watch mode
$ pnpm run start:dev
# production mode
$ pnpm run start:prodhttps://docs.nestjs.com/cli/overview
Create a new resource:
- Generate a module
- Generate a controller
- Generate a service
- OPTIONAL: Generate an entity class/interface
- OPTIONAL: Generate Data Transfer Objects
nest g resource new-resourceCreate a new project:
Create a new project using
- pnpm
- Typescript
--strictflag
Optional:
-c custom-schematic
nest n project-name -p pnpm -l TS --strict# unit tests
$ pnpm run test
# e2e tests
$ pnpm run test:e2e
# test coverage
$ pnpm run test:covIn this example, we use ethers.js to check for new blocks
and use an Observable to emit to blocks to a html file and display them with just a couple lines of vanilla JS.
We used the nest.js Server Sent Event controller functionality to create
a SSE endpoint /ethers/sse/block-number/.
Minimal client using a SINGLE .html file: client/src/sse/ethers/block-number.html
- Raw HTML with vanilla.js
- Uses
EventSourceAPI - https://developer.mozilla.org/en-US/docs/Web/API/EventSource - Uses
picocssdelivered via public CDN for styling
Setting up an EventSource to the NestJS SSE Endpoint
const eventSource = new EventSource('http://127.0.0.1:3000/ethers/sse/block-number/', {
withCredentials: false,
});HTMX further abstracts away logic, using custom HTML tags to replace even the Javascript from the previous example with vanilla.js.
Minimal client using .htmx: client/src/sse/ethers/htmx-block-number.html
- Raw HTML with htmx
- Uses
picocssdelivered via public CDN for styling
Setting up SSE with htmx
<article id="messages" hx-ext="sse" sse-connect="http://127.0.0.1:3000/ethers/sse/block-number/"
sse-swap="message"></article>We use, ethers.js to poll for new blocks and use a rxjs Observable to emit blocks
via a nest.js Websocket gateway, with the ws adapter and display the results using the vanilla.js Websocket API
on the client side.
Example minimal client using .html file: client/src/ws/ethers/ws-block-number.html
- Raw HTML with vanilla.js
- Uses
WebSocketAPI - https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API - Uses
picocssdelivered via public CDN for styling - Nest.js Websocket Gateway
- ws Adapter
Setting up a Websocket to the NestJS WS Gateway
// For Ethers
const ethersSocket = new WebSocket('ws://localhost:3000');
// For Solkit
const solkitSocket = new WebSocket('ws://localhost:3000');We use, ethers.js to get a block-by-block number and use a rxjs Observable to emit the block
via a nest.js GraphQL Resolver with an Apollo Server.
Example minimal client using .html file: client/src/graphql/ethers/graphql-block-query.html
- Raw HTML with vanilla.js
- Uses
FetchAPI - https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API - Uses
picocssdelivered via public CDN for styling - Nest.js GraphQL Resolver
- Apollo Driver
Making a GraphQL request to the NestJS GraphQL Resolver
const res = await fetch('http://127.0.0.1:3000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: query,
variables: { blockNumber: blockNumber }
})
});Related info and links.
Examples:
Format /[version]/[domain]/[resource]/[resource_id]/[hiearchical_resource]/[hiearchical_resource_id]
[GET] /v1/treasury/customers/{customer_id}/orders[POST] /v1/treasury/customers/{customer_id}/orders[GET] /v1/treasury/customers/{customer_id}/orders/{order_id}[PUT] /v1/treasury/customers/{customer_id}/orders/{order_id}[DELETE] /v1/treasury/customers/{customer_id}/orders/{order_id}[PATCH] /v1/treasury/customers/{customer_id}/orders/{order_id}
- Versioning (
/v1/)- Allows backward compatibility when introducing breaking changes
- Domain (
/treasury/)- Organizes APIs by business domain or bounded context
- Resource (
/customers/)- Everything is a resource
- collection → item → subcollection → subitem
- Hierarchical Resource (
/orders/)- Parent-child relationships:
customers ONEtoMANY orders - Child access in parent scope (Mirror database schema)
- Parent-child relationships:
GOOD
# Clear hierarchy and context
GET /v1/treasury/customers/123/orders/456BAD
# vs ambiguous flat structure
GET /v1/orders/456 // Which customer? No context.This project is licensed under the MIT License—see the LICENSE file for details.
Maintained by Hella Labs.