NFT Explorer Template

Dive deep into the NFT explorer template that helps you quickly bootstrap a full-stack NFT explorer dapp!

Introduction

create-web3-dapp comes with pre-built templates that help you boostrap full-stack web3 dapps in a matter of minutes. In this guide, we are going to take a look at one such template: The NFT Explorer Dapp. We'll understand how to use it and how the code works!

How to use the NFT Explorer Template

To use the NFT Explorer Template, follow the steps listed below or check out this video for reference:

In your terminal, navigate to the folder you want to create your project in and run:

npx create-web3-dapp@latest  nft-explorer

🚧

If you've previously installed create-web3-dapp globally via npx create-web3-dapp:

The CLI builder will notify you if a new version has been released. In any case, we always suggest you to run using the latest available version by running npx create-web3-dapp@latest.


2. Insert a project name

4. Choose your chain

Once you have selected your starting template, you'll need to choose the chain you want to configure your project for. Note that you can always modify or add more chains in the future. Current choices include:

  • Ethereum
  • Polygon
  • Arbitrum
  • Optimism

5. Select testnet or mainnet

Select if you want to configure with mainnet or testnet. We generally recommend starting with a testnet if you're not ready to deploy to the live chain yet. Here are the corresponding test networks for each chain:

  • Ethereum -> Goerli
  • Polygon -> Mumbai
  • Arbitrum -> Arb-Goerli
  • Optimism -> Opt-Goerli

6. Create an Alchemy Account or Enter your API key

Then you will be asked if you already have an Alchemy account, selecting yes or no based on if you have the Alchemy account or not.

If you already have an Alchemy account you will be redirected to your Alchemy dashboard from where you can copy and paste your API key into the terminal.

If not, you will be redirected to create a new Alchemy account and after creating the Alchemy account you can copy and paste your API key in the terminal.

We'll use an Alchemy API key to connect to the blockchain and read data.

That's it! Now you're ready to rock! :rocket: To launch CW3D, simply cd into your new project folder and run:

npm run dev. This should open up the app on http://localhost:3000

And you have an NFT explorer dapp ready to go! Just by using the template 🚀


Code Walkthrough

Let's explore the project to understand how the code is working. Open up your project in an IDE or a text editor like VSCode.

At first glance, you can see that it's a NextJS project and has the standard NextJS project structure.

Here are some things to be noted:

  • The components reside in the components folder, and it has two components: navbar.jsx (defines the connect wallet button and logo) and nftGallery.jsx (the main section of the project, displays NFTs).
  • The APIs are defined under the pages/api folder which contains the apis for fetching NFTs by collection and owner. These are named: getNftsForCollection and getNftsForOwner.
  • The index.js is the main file in which we are just rendering the NFTGallery component.
  • The public folder includes the assets for the project and the styles folder includes the styles for the project.

Here are the explanations for the main parts of the project

1. The NFTGallery component

import { useEffect, useState } from "react";
import styles from "../styles/NftGallery.module.css";
import { useAccount } from "wagmi";
import etherscan from "../public/etherscan.svg";
import verified from "../public/verified.svg";

export default function NFTGallery({}) {
	const [nfts, setNfts] = useState();
	const [walletOrCollectionAddress, setWalletOrCollectionAddress] =
		useState("vitalik.eth");
	const [fetchMethod, setFetchMethod] = useState("wallet");
	const [pageKey, setPageKey] = useState(false);
	const [spamFilter, setSpamFilter] = useState(true);
	const [isLoading, setIsloading] = useState(false);
	const { address, isConnected } = useAccount();
	const [chain, setChain] = useState("eth-mainnet");
	
	const changeFetchMethod = (e) => {
		switch (e.target.value) {
			case "wallet":
				setWalletOrCollectionAddress("vitalik.eth");

				break;
			case "collection":
				setWalletOrCollectionAddress(
					"0x8a90CAb2b38dba80c64b7734e58Ee1dB38B8992e"
				);
				break;
			case "connectedWallet":
				setWalletOrCollectionAddress(address);
				break;
		}
		setFetchMethod(e.target.value);
	};
	const fetchNFTs = async (pagekey) => {
		setIsloading(true);
		setNfts();
		const endpoint =
			fetchMethod == "wallet" || fetchMethod == "connectedWallet"
				? "/api/getNftsForOwner"
				: "/api/getNftsForCollection";
		try {
			const res = await fetch(endpoint, {
				method: "POST",
				body: JSON.stringify({
					address:
						fetchMethod == "connectedWallet"
							? address
							: walletOrCollectionAddress,
					pageKey: pagekey ? pagekey : null,
					chain: chain,
					excludeFilter: spamFilter,
				}),
			}).then((res) => res.json());
			setNfts(res.nfts);
			if (res.pageKey) {
				setPageKey(res.pageKey);
			} else {
				setPageKey();
			}
		} catch (e) {}

		setIsloading(false);
	};

	useEffect(() => {
		fetchNFTs();
	}, [fetchMethod]);
	useEffect(() => {
		fetchNFTs();
	}, [spamFilter]);

	return (
		<div className={styles.nft_gallery_page}>
			<div>
				<div className={styles.fetch_selector_container}>
					<h2 style={{ fontSize: "20px" }}>Explore NFTs by</h2>
					<div className={styles.select_container}>
						<select
							defaultValue={"wallet"}
							onChange={(e) => {
								changeFetchMethod(e);
							}}
						>
							<option value={"wallet"}>wallet</option>
							<option value={"collection"}>collection</option>
							<option value={"connectedWallet"}>
								connected wallet
							</option>
						</select>
					</div>
				</div>
				<div className={styles.inputs_container}>
					<div className={styles.input_button_container}>
					<input
				value={walletOrCollectionAddress}
				onChange={(e) => {
					setWalletOrCollectionAddress(e.target.value);
				}}
				placeholder="Insert NFTs contract or wallet address"
			></input>
						<div className={styles.select_container_alt}>
							<select
								onChange={(e) => {
									setChain(e.target.value);
								}}
								defaultValue={"ETH_MAINNET"}
							>
								<option value={"ETH_MAINNET"}>Mainnet</option>
								<option value={"MATIC_MAINNET"}>Polygon</option>
								<option value={"ETH_GOERLI"}>Goerli</option>
								<option value={"MATIC_MUMBAI"}>Mumbai</option>
							</select>
						</div>
						<div
							onClick={() => fetchNFTs()}
							className={styles.button_black}
						>
							<a>Search</a>
						</div>
					</div>
				</div>
			</div>

			{isLoading ? (
				<div className={styles.loading_box}>
					<p>Loading...</p>
				</div>
			) : (
				<div className={styles.nft_gallery}>
					{(nfts?.length &&
						fetchMethod !=
							"collection")&&(
								<div
									style={{
										display: "flex",
										gap: ".5rem",
										width: "100%",
										justifyContent: "end",
									}}
								>
									<p>Hide spam</p>
									<label className={styles.switch}>
										<input
											onChange={(e) =>
												setSpamFilter(e.target.checked)
											}
											checked={spamFilter}
											type="checkbox"
										/>
										<span
											className={`${styles.slider} ${styles.round}`}
										></span>
									</label>
								</div>
							)}

					<div className={styles.nfts_display}>
						{nfts?.length ? (
							nfts.map((nft) => {
								return <NftCard key={nft.tokenId} nft={nft} />;
							})
						) : (
							<div className={styles.loading_box}>
								<p>No NFTs found for the selected address</p>
							</div>
						)}
					</div>
				</div>
			)}

			{pageKey && nfts?.length && (
				<div>
					<a
						className={styles.button_black}
						onClick={() => {
							fetchNFTs(pageKey);
						}}
					>
						Load more
					</a>
				</div>
			)}
		</div>
	);
}
function NftCard({ nft }) {
	return (
		<div className={styles.card_container}>
			<div className={styles.image_container}>
				<img src={nft.media}></img>
			</div>
			<div className={styles.info_container}>
				<div className={styles.title_container}>
					<h3>{nft.title}</h3>
				</div>
				<hr className={styles.separator} />
				<div className={styles.symbol_contract_container}>
					<div className={styles.symbol_container}>
						<p>{nft.symbol}</p>

						{nft.verified == "verified" ? (
							<img
								src={verified.src}
								width="20px"
								height="20px"
							/>
						) : null}
					</div>
					<div className={styles.contract_container}>
						<p className={styles.contract_container}>
							{nft.contract.slice(0, 6)}...
							{nft.contract.slice(38)}
						</p>
						<img src={etherscan.src} width="15px" height="15px" />
					</div>
				</div>

				<div className={styles.description_container}>
					<p>{nft.description}</p>
				</div>
			</div>
		</div>
	);
}
  • The code imports various libraries such as React, a CSS file, and some images. It defines a function called "NFTGallery" which renders a page that displays NFTs, a type of digital asset.

  • The code sets up several pieces of state that the function uses to track the data it needs to display the NFTs. These include nfts, which stores the NFTs that are displayed, walletOrCollectionAddress, which is the address of the wallet or collection the NFTs are fetched from, and isLoading, which is a boolean that indicates whether the NFTs are being loaded or not.

  • The function uses two useEffect hooks to update the NFTs whenever the fetchMethod or spamFilter state changes. The fetchNFTs function is called whenever the user clicks the "search" button or changes one of the input fields.

  • The return statement displays a form where the user can enter an address to search for NFTs. Once the user enters an address and clicks the "search" button, the function fetches the NFTs associated with the address and displays them. If the user toggles the "hide spam" switch, the function will filter out any NFTs that the user considers spam.

2. The getNftsForCollection API

import { Network, Alchemy } from "alchemy-sdk";

export default async function handler(req, res) {
	const { address, pageKey, pageSize, chain } = JSON.parse(
		req.body
	);
	if (req.method !== "POST") {
		res.status(405).send({ message: "Only POST requests allowed" });
		return;
	}
	console.log(chain);
	const settings = {
		apiKey: process.env.ALCHEMY_API_KEY,
		network: Network[chain],
	};
	const alchemy = new Alchemy(settings);

	try {
		const nfts = await alchemy.nft.getNftsForContract(address, {
			pageKey: pageKey ? pageKey : null,
			pageSize: pageSize ? pageSize : null,
		});
		const formattedNfts = nfts.nfts.map((nft) => {
			const { contract, title, tokenType, tokenId, description, media } =
				nft;

			return {
				contract: contract.address,
				symbol: contract.symbol,
				media: media[0]?.gateway
					? media[0]?.gateway
					: "https://via.placeholder.com/500",
				collectionName: contract.openSea?.collectionName,
				verified: contract.openSea?.safelistRequestStatus,
				tokenType,
				tokenId,
				title,
				description,
			};
		});
		 const filteredNFTs = formattedNFTs.filter(
      (nft) => nft.title.length && nft.description.length && nft.media
    );

		res.status(200).json({
			nfts: filteredNFTs,
			pageKey: nfts.pageKey,
		});
		// the rest of your code
	} catch (e) {
		console.warn(e);
		res.status(500).send({
			message: "something went wrong, check the log in your terminal",
		});
	}
}
  • The code defines a function that handles HTTP requests sent to a server. The function receives a request (req) and a response (res) object as input parameters.

  • The function first extracts the address, pageKey, pageSize, and chain properties from the request body, which is expected to be in JSON format.

  • Then, it checks whether the HTTP method of the request is POST, and returns an error response with a status code of 405 if it is not.

  • Next, it creates an instance of the Alchemy class from the "alchemy-sdk" module, using an API key and a network specified in the settings object, which is based on the chain property extracted from the request body.

  • After that, the function calls the getNftsForContract method of the alchemy.nft object, passing the address, pageKey, and pageSize values as arguments. The method returns a list of NFTs, which are then formatted into a new list of objects containing selected properties.

  • The formatted list is then filtered to remove any NFTs with empty title or description properties or missing media property.

  • Finally, the function sends a success response with a status code of 200, containing the filtered list of NFTs and the pageKey value returned by the getNftsForContract method. If an error occurs during the execution of the function, the error is caught and a 500 status code error response is sent back to the client with a message.

3. The getNftsForOwner API

import { Network, Alchemy, NftFilters } from "alchemy-sdk";

export default async function handler(req, res) {
	const { address, pageSize, chain, excludeFilter, pageKey } = JSON.parse(
		req.body
	);
	console.log(chain);
	if (req.method !== "POST") {
		res.status(405).send({ message: "Only POST requests allowed" });
		return;
	}

	const settings = {
		apiKey: process.env.ALCHEMY_API_KEY,
		network: Network[chain],
	};

	const alchemy = new Alchemy(settings);

	try {
		const nfts = await alchemy.nft.getNftsForOwner(address, {
			pageSize: pageSize ? pageSize : 100,
			excludeFilters: excludeFilter && [NftFilters.SPAM],
			pageKey: pageKey ? pageKey : "",
		});

		const formattedNfts = nfts.ownedNfts.map((nft) => {
			const { contract, title, tokenType, tokenId, description, media } =
				nft;

			return {
				contract: contract.address,
				symbol: contract.symbol,
				collectionName: contract.openSea?.collectionName,
				media: media[0]?.gateway
					? media[0]?.gateway
					: "https://via.placeholder.com/500",
				verified: contract.openSea?.safelistRequestStatus,
				tokenType,
				tokenId,
				title,
				description,
			};
		});

		res.status(200).json({ nfts: formattedNfts, pageKey: nfts.pageKey });
	} catch (e) {
		console.warn(e);
		res.status(500).send({
			message: "something went wrong, check the log in your terminal",
		});
	}
}
  • The code defines a function that handles HTTP requests sent to a server. The function receives a request (req) and a response (res) object as input parameters.

  • The function first extracts the address, pageSize, chain, excludeFilter, and pageKey properties from the request body, which is expected to be in JSON format.

  • Then, it checks whether the HTTP method of the request is POST, and returns an error response with a status code of 405 if it is not.

  • Next, it creates an instance of the Alchemy class from the "alchemy-sdk" module, using an API key and a network specified in the settings object, which is based on the chain property extracted from the request body.

  • After that, the function calls the getNftsForOwner method of the alchemy.nft object, passing the address, pageSize, excludeFilter, and pageKey values as arguments. The method returns a list of NFTs owned by the address, which are then formatted into a new list of objects containing selected properties.

  • The formatted list is then sent back to the client as a success response with a status code of 200, along with the pageKey value returned by the getNftsForOwner method.

  • If an error occurs during the execution of the function, the error is caught and a 500 status code error response is sent back to the client with a message.

Conclusion

In conclusion, the NFT Explorer Template is a beautiful template that comes with create-web3-dapp, you can use it to create an NFT explorer dapp in minutes and even customize it further to make your own project with extra features!


ReadMe