DEV Community

Cover image for I Built My First NFT Gallery in 2 Days (And Didn’t Lose My Mind)
Laurina Ayarah
Laurina Ayarah

Posted on

I Built My First NFT Gallery in 2 Days (And Didn’t Lose My Mind)

I built my first NFT gallery in 2 days. No blockchain degree, no Web3 bootcamp, just me, some API docs, and a lot of confused Googling.

Two days ago, I had no idea how to fetch NFT data. Now I have a working gallery that displays any wallet’s NFTs. Here’s exactly how I did it (and all the dumb mistakes I made along the way)…

Here’s what I built: nft-gallery

And here’s how I did it, including all the stupid mistakes I made so you don’t have to…

What I Actually Built

It’s simple: you connect your wallet, and it shows all your NFTs in a grid. That’s it. Nothing fancy.

But getting there? That was a journey.

The Stack (What I Used and Why)

*I went with Next.js because:
*

  • I already know React
  • Typescript (tsx)
  • API routes make backend stuff easy
  • Deployment to Vercel is literally one click

For wallet connection, I used RainbowKit:

  • Handles multiple wallets (MetaMask, Coinbase, WalletConnect)
  • Looks clean out of the box
  • Saves you from writing 100 lines of connection logic

*For fetching NFT data: Moralis API
*

  • Free tier is generous
  • One API call gets all NFTs from a wallet
  • Documentation is actually readable

Day 1: Fighting With Wallet Connection

The goal: Get a “Connect Wallet” button working.

Reality: Spent way too long reading RainbowKit docs.

The setup looks scary:

'use client'

import '@rainbow-me/rainbowkit/styles.css'
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { mainnet } from 'wagmi/chains'

const config = getDefaultConfig({
      appName: 'NFT Gallery',
  projectId: '6344a99eb87ffdddeaac02a39972d21d', // from reown.com / WalletConnect Cloud
  chains: [mainnet],
  ssr: true,
})

const queryClient = new QueryClient()

export default function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <RainbowKitProvider modalSize="compact">
          {children}
        </RainbowKitProvider>
      </QueryClientProvider>
    </WagmiProvider>
  )
}
Enter fullscreen mode Exit fullscreen mode

But here’s the thing, most of it is copy-paste from their docs. You just need:

  1. A WalletConnect project ID (free from reown.com takes 2 minutes)
  2. Copy their config code
  3. Wrap your app in their providers

My mistake: I forgot to actually wrap my app in the providers. Spent 20 minutes staring at a blank screen, wondering why nothing worked. Check your layout.tsx first. Don’t be me.

Once it’s set up, though? The connect button is literally just <ConnectButton />. That's it.

It handles MetaMask, Coinbase Wallet, WalletConnect, everything. I didn’t have to write any of that logic.

Day 2: Okay, Now, How Do I Get the NFTs?
Connected wallet? Check.
Knowing what to do next? (give me a sec, let me breathe)…

So…

I needed to fetch NFT data. But from where? and how?

Enter: Moralis API.

The free tier gives you 40,000 requests/month, more than enough for a side project.

I created a Next.js API route (because I didn’t want to expose my API key):

// app/api/nfts/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const address = searchParams.get('address');

  // Call Moralis
  const url = `https://deep-index.moralis.io/api/v2.2/${address}/nft?chain=eth&format=decimal`;

  const res = await fetch(url, {
    headers: {
      'X-API-Key': apiKey,
    },
  });

  const data = await res.json();
  return NextResponse.json(data);
}
Enter fullscreen mode Exit fullscreen mode

What I learned:

  • Moralis returns EVERYTHING about an NFT (metadata, images, contract info)
  • Some NFTs don’t have images (yes, really)
  • IPFS URLs are… interesting

What broke: First tried calling Moralis directly from the frontend. CORS said no. That’s why you need the API route.

P.S. CORS means Cross-Origin Resource Sharing. It’s basically a security rule built into browsers that controls which websites are allowed to talk to each other.

Making It Not Look Terrible
Now I had NFT data. Time to actually display it.

{nfts.map((nft, i) => {
  const image = 
    nft.normalized_metadata?.image ||
    nft.collection_logo ||
    nft.collection_banner_image;

  return (
    <div className="bg-[#141414] rounded-2xl...">
      {image ? (
        <img src={image} alt={nft.name || "NFT"} />
      ) : (
        <div>No Image</div>
      )}
      <h2>{nft.name || "Unnamed NFT"}</h2>
    </div>
  );
})}
Enter fullscreen mode Exit fullscreen mode

*Things that surprised me:
*

  1. Not every NFT has an image. Some are just… data? Had to add a “No Image” fallback or the whole thing broke.

  2. NFT names can be null. Because why would every NFT have a name? That would make too much sense.

  3. Loading takes time. 2–3 seconds to fetch. If you don’t show a loading state, users think it’s broken.

  4. Tailwind Grid is actually nice. grid gap-6 grid-cols-1 md:grid-cols-3 - boom, responsive without writing media queries.

The Demo Wallet Trick
I wanted people to see it work without connecting their wallets. So I used a random public wallet address as a fallback:

const walletAddress = isConnected
  ? address
  : "0xED5AF388653567Af2F388E6224dC7C4b3241C544";
Enter fullscreen mode Exit fullscreen mode

Just Googled “Ethereum wallet with NFTs” and grabbed one. Now visitors see NFTs immediately, but can still connect to view their own.

Deployment (The Only Easy Part)

git push origin main
vercel deploy
Enter fullscreen mode Exit fullscreen mode

Done. 5 minutes. Vercel is magic.

What Still Annoys Me

  • Error messages are generic and boring
  • No search bar
  • Some wallets have HUNDREDS of NFTs, no pagination
  • The code could be cleaner
  • Dark mode is the only mode (should add a toggle)

But it works. And that’s what matters.

Honestly?
Building this taught me that Web3 frontend development is just… frontend development. With some extra APIs.

You don’t need to understand blockchain internals. You don’t need to write Solidity. You just need to:

  • Connect wallet (libraries handle this)
  • Fetch data (APIs handle this)
  • Display it nicely (React + Tailwind handle this)

Is my code perfect? No.
Is it production-ready? Also no.
Did I learn a ton? Yes.

Live demo: nft-gallery (let me know what you think)
Code: GitHub (checkout my GitHub)

If you build something with this, drop the link below. I’d really love to see it. And if you get stuck on some weird error, comment it —maybe I experienced it too!

Check out this article on my Medium

Top comments (0)