A modern Web3 NFT gallery application. Upload NFT images to IPFS via Pinata, generate ERC-721 compatible metadata with ETH pricing, and explore a responsive gallery powered directly by IPFS.
Built with React + Vite · Pinata V3 + Legacy Pinning · Public IPFS · Vercel-ready
- Images → Pinata V3 (
/v3/files) - Metadata →
pinJSONToIPFS(legacy) - Stored permanently on public IPFS
- Includes name, description, category, and ETH price
- Stored entirely on IPFS (no database)
- NFTs fetched from Pinata (
/data/pinList) - Works across devices and browsers
- Search by name/category
- Filter by category
- Sort: newest, oldest, price ↑↓
- Click-to-zoom
- Direct metadata link (IPFS)
- Persisted in localStorage (UI only)
PINATA_JWTnever exposed to browser
- Serverless
/apiroutes included
| Layer | Technology |
|---|---|
| Frontend | React 18 + Vite 5 |
| Styling | TailwindCSS + CSS variables |
| Animation | Framer Motion |
| Toasts | react-hot-toast |
| IPFS | Pinata V3 + pinJSONToIPFS |
| Backend | Express / Vercel Functions |
| HTTP | axios |
| Storage | IPFS (Pinata as source of truth) |
nft-artworks
├── api/
│ ├── nfts.js
│ ├── upload-image.js
│ └── upload-metadata.js
├── public/
│ ├── apple-touch-icon.svg
│ ├── favicon.svg
│ └── nft-artworks-og.svg
├── src/
│ ├── components/
│ │ ├── CategoryFilter.jsx
│ │ ├── ImageCard.jsx
│ │ ├── ImagePreviewModal.jsx
│ │ ├── Loader.jsx
│ │ ├── Navbar.jsx
│ │ ├── SearchBar.jsx
│ │ └── UploadModal.jsx
│ ├── hooks/
│ │ └── useUpload.js
│ ├── pages/
│ │ ├── Docs.jsx
│ │ └── Gallery.jsx
│ ├── services/
│ │ └── pinataService.js
│ ├── utils/
│ │ ├── metadataBuilder.js
│ │ └── storage.js
│ ├── App.jsx
│ ├── index.css
│ └── main.jsx
├── .env.example
├── .gitignore
├── index.html
├── LICENSE
├── package-lock.json
├── package.json
├── postcss.config.js
├── README.md
├── server.cjs
├── tailwind.config.js
├── vercel.json
└── vite.config.js
- Node.js ≥ 18
- npm ≥ 9
- Pinata account
npm installcp .env.example .envPINATA_JWT=your_pinata_jwt
PINATA_GATEWAY=your-gateway.mypinata.cloud
VITE_PINATA_GATEWAY=your-gateway.mypinata.cloud
API_PORT=3001Terminal 1
node server.cjsTerminal 2
npm run devOpen:
http://localhost:5173
| Variable | Description |
|---|---|
PINATA_JWT |
Server-side Pinata authentication |
PINATA_GATEWAY |
Gateway for building IPFS URLs |
VITE_PINATA_GATEWAY |
Same gateway (frontend use) |
API_PORT |
Local API server port |
PINATA_JWTis never exposed to the browser.
Create key at: https://app.pinata.cloud/keys
- Files → Write ✅
- pinJSONToIPFS ✅
- pinList ✅
Image Upload:
Browser → /api/upload-image → Pinata V3 (/v3/files)
Metadata Upload:
Browser → /api/upload-metadata → pinJSONToIPFS
Gallery:
Browser → /api/nfts → Pinata /data/pinList → fetch metadata JSON
-
Pinata = source of truth
-
No database required
-
No reliance on localStorage for NFT data
-
All NFTs persist across:
- refresh
- devices
- browsers
-
Import repo in Vercel
-
Set:
- Framework: Vite
- Build:
npm run build - Output:
dist
-
Add environment variables:
PINATA_JWTPINATA_GATEWAYVITE_PINATA_GATEWAY
-
Deploy
[ ] Upload image → returns CID
[ ] Upload metadata → returns CID
[ ] NFT appears in gallery
[ ] Refresh → NFT still exists
[ ] Open on phone → same NFTs visible
[ ] Pinata dashboard shows files
[ ] No JWT exposed in browser
This project was built as part of the Pinata Challenge, focusing on decentralized storage using IPFS.
Note: This version improves the original submission by adding NFT metadata, pricing, and a fully IPFS-powered gallery instead of image-only uploads.
This project is open-sourced software licensed under the MIT license.
