This project uses an NFT usecase to illustrate how to implement a Web3 application from scratch.
We choose to use the Express Node.js application framework to implement the backend API service, and Vue.js JavaScript framework to implement the web user interface.
The application can verify that a crypto wallet owner really owns a specified NFT token. In the application's web UI, a user can enter an NFT contract address and a token ID to be verified. The UI will send a message signing request to the user's wallet plugin (assuming that user is logged in to a MetaMask Chrome plugin), and prompt the user to sign a message containing the NFT contract address and token ID. After the user signs the message by using the private key in the wallet, the signature will be sent to a backend API service for verification. The API service will extract the signer's public address from the signature, and query the blockchain to retrieve the owner of the specified NFT token, and then return both the verified signer's address and the token owner's address. If these 2 addresses match, it proves that wallet owner must be the current owner of the specified NFT token.
I use zsh
on a Mac, and installed Node.js
by using nvm i.e.,
touch ~/.zshrc
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install node
Verify that Node.js
and npm
are installed correctly, i.e.,
nvm -v
npm -v
node -v
Install MetaMask plugin for Chrome from here, and then create an account if you do not already have a wallet.
Create an account at infura.io, and then create an Ethereum project. Copy the endpoint URL for the rinkeby
test network, and use the URL in the API server configuration file, api/config.json.
For development by default, this application will start a backend API server on port 3070
, and a Vue web UI proxy on port 8082
. You can start these processes in separate terminals as follows.
# start the Node.js API server on port 3070
cd api
npm install
npm run dev
# start the Vue.js UI proxy on port 8082
cd nft-app
npm install
npm run serve
View the web UI in a Chrome browser at http://localhost:8082/. Login to your wallet in MetaMask browser plugin. In the web UI, enter a Token ID of 1
through 4
, and select an NFT contract, then click the Sign
button to see the verification result, which should look like the following:
In an empty project folder, e.g., ~/jsprojects/vue-nft-delivery
, create a JavaScript project from scratch, i.e,
mkdir api -P && cd api
npm init
Enter the following parameters when prompted by the command npm init
:
package name: (api)
version: (1.0.0)
description: backend for nft-delivery
entry point: (index.js) server.js
test command: test
git repository: https://github.com/johndoe/vue-nft-delivery
keywords: nft
author: John Doe
license: (ISC)
This will create a file package.json, which you can edit if you want to change any of the parameters.
We use the Express framework to implement backend service APIs. Besides, for convenience during the development, we use nodemon to restart the API server automatically whenever the code is updated. So, add these dependencies for runtime and dev-only.
npm install --save express
npm install --save-dev nodemon
The service APIs will use ethers.js to verify message signature, and use web3.js to connect to an Ethereum node and call smart contract methods. For development, we use webpack to build production bundle of the application scripts and assets. So add these dependencies as well.
npm install --save ethers web3
npm install --save-dev webpack webpack-cli
We are now ready to write the API code. All APIs are implemented in server.js. If you are not familiar with the express
framework, you may find the references at the end of this doc helpful.
The server.js
implements 2 APIs:
/api/contracts
will return a list of pre-configured NFT contracts whose tokens can be verified by this application./api/verify
will verify the user's signature of a NFT token ID, and retrieve the owner of a specified NFT token from an Ethereum node.
It also uses express.static to serve static web content in a /public
folder, as well as the application bundle of the web UI in /nft-app
, which we'll implement in the next section.
During the development, the API server will listen on an HTTP port, but for production build, we should change it to use HTTPS.
The configuration parameters are read from the file config.json, which includes
port
is the API server's listen port.infura
is the URL of Ethereum node in infura. You can create a free account ininfura
, and then create a project, and copy the project's endpoint for the Ethereum network where your NFT is minted and/or traded.contracts
is a list of NFT contracts to be handled by this application. The sample entries list 2 NFT contracts that I created on therinkeby
test network. This list will be displayed as a selection list on the web UI that we'll implement in the next section. If you are interested in how to create such NFT contracts from scratch, refer to the Sample NFT project.
The API /api/verify
uses the contract ABI of the standard ERC-721 specification to fetch the owner address of a specified token. Thus, we store a copy of the ABI definition in erc721.json.
Create webpack.config.js for production bundle. Note that we'll build the production bundle to a parent ../dist
folder where we can add the web UI bundle later. We also added a rule to avoid the build error Can't resolve 'electron'
for web3.js
.
Edit the scripts in package.json for development and production. The production start
script assumes that the application bundle is produced in the parent ../dist
folder.
Start the API server using command npm run dev
, which uses nodemon
to watch files, and so the API server will automatically restart whenever the code is updated.
Verify the API server by sending a request from another terminal: curl http://localhost:3070/api/contracts
.
Build the production bundle using command npm run build
. Start the production server using command npm run start
.
In the project folder, e.g., ~/jsprojects/vue-nft-delivery
, create a Vue.js
project named nft-app
:
# install Vue CLI globally if it is not done already
npm install -g @vue/cli
# create new project
vue create nft-app
We use the Bootstrap Vue 3 framework to build responsive web UI. It uses BootstrapVue
web components as described here, and Bootstrap
CSS classes as described here.
The web UI uses ethers.js to sign messages, and uses axios to call backend APIs over HTTP/HTTPS. So add these dependencies:
cd nft-app
# dependencies for message signature and verification
npm install --save axios ethers
# dependencies for UI components and css classes
npm install --save bootstrap bootstrap-vue-3
We are now ready to write code for the web UI. If you are not familiar with the vue.js
or bootstrap-vue
framework, you may find the references at the end of this doc helpful.
Edit the scripts in package.json to make it serve
on port 8082
.
Edit the configuration file vue.config.js to specify the URL of the backend API server as specified in the previous section, i.e., http://localhost:3070. It also specifies the output folder for production bundle, which we'll build later in this section.
Edit the file main.js to register bootstrap-vue
, so it will be available for all the application components.
The file SigService.js implements service functions for the UI to interact with MetaMask wallet for signature calculation, and to call backend service APIs.
The only Vue.js UI component for this application is implemented in SignMessage.vue. When the component is loaded, it calls the backend API /api/contracts
and uses the returned result to initialize the NFT contract selection list. When the user clicks the Sign
button, it send a request to MetaMask wallet where the user can sign a message by using the private key in the wallet. When the signature is updated, a watcher method will pick the updated signature and call the backend API /api/verify
, and then display the verification result. When HTTPS transport is used by the backend API server, this approach could be a reliable mechanism for web3 user authentication. For even better security, however, we may add a step to request a random nounce
from the backend server, and so the user will sign a dynamically generated random message, instead of a static NFT token ID.
Edit the root component App.vue to include the SignMessage
as a child component under the root.
Note that the file .eslintrc.js contains an ESLint
rule to disable the syntax check for multi-word-component-names
, and so component names do not have to use multiple words. It is not necessary for this project because the only component of the project, SignMessage
contains 2 words. However, if you add a component with a single-word name, e.g., Header
, you will see a compilation error:
error Component name "Header" should always be multi-word vue/multi-word-component-names
The additional ESLint rule in .eslintrc.js would be necessary to override the default behavior.
Start the web UI proxy server by using the following command. It'll pick up code changes automatically, and thus you do not have to restart it during development.
cd nft-app
npm run serve
View the web UI in a Chrome browser at http://localhost:8082/, and start testing.
For production, we can build the applicaton bundle to the same ../dist
folder and so the same Node.js
server will serve both the API service and the web UI, as well as other static content in a public
folder. As described in the API service implementation server.js, the API server bundle will be output to the root of ../dist
, and the web UI bundle will be output to the sub-folder ../dist/nft-app
. The following scripts will perform the build tasks.
cd ../api
npm run build
cd ../nft-app
npm run build
Start the production application server using the following command.
cd ../dist
node serer.bundle.js
View the web UI in a Chrome browser at http://localhost:3070/. Login to your wallet in MetaMask browser plugin. In the web UI, enter a Token ID of 1
through 4
, and select an NFT contract, then click the Sign
button to see the verification result.
You can add any other static HTML pages, such as sample files under /public to the folder ../dist/public
, and so they can be viewed at http://localhost:3070/public.
If you update the server port and/or other configuration parameters in config.json, you'll have to rebuild the production bundles, and then restart the Node.js
server.
- Node.js Express Tutorial
- Setup HTTPS for Express Server
- Express API Reference
- Webpack Tutorial
- Web3 Call Smart Contract
- Web3.js API Reference
- BootstrapVue Tutorial
- Bootstrap css Reference
- BootstrapVue Component Reference
- Web Application with Node.js and Vue
- Vue.js and Node.js Example
- Ethers MetaMask Message Signature
- Write mobile web3 crypto apps on iOS and Android