TypeScript Full-Stack
Build an NFT-gated API with Express backend and React frontend. Users authenticate with their Solana wallet to access protected content.
$ what-you-will-build
NFT-Gated API
Protect API endpoints that only NFT holders can access
React Dashboard
Frontend with wallet connection and protected content display
Production Ready
Complete with error handling, TypeScript, and best practices
$ prerequisites
- [✓] Node.js 18+ installed
- [✓] Basic TypeScript knowledge
- [✓] Solana wallet (Phantom, Backpack, or Solflare)
- [✓] Text editor (VS Code recommended)
$ step-by-step-guide
Project Setup
# Create project directory mkdir nft-gated-api cd nft-gated-api # Initialize projects mkdir server client cd server && npm init -y cd ../client && npm create vite@latest . -- --template react-ts
This creates a monorepo structure with separate server and client directories.
Install Dependencies
# Server dependencies cd server npm install @openkitx403/server express cors dotenv npm install -D @types/express @types/cors tsx typescript # Client dependencies cd ../client npm install @openkitx403/client @solana/web3.js
Write the Code
import express from 'express';
import cors from 'cors';
import { createOpenKit403, inMemoryLRU } from '@openkitx403/server';
import { Connection, PublicKey } from '@solana/web3.js';
const app = express();
app.use(cors());
// Initialize OpenKit403
const openkit = createOpenKit403({
issuer: 'nft-gated-api',
audience: 'http://localhost:3000',
ttlSeconds: 60,
replayStore: inMemoryLRU(),
// Token gate: require specific NFT
tokenGate: async (address: string) => {
const connection = new Connection('https://api.mainnet-beta.solana.com');
const pubkey = new PublicKey(address);
// Check if wallet owns NFT from specific collection
const NFT_COLLECTION = new PublicKey('YOUR_NFT_MINT_ADDRESS');
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(
pubkey,
{ mint: NFT_COLLECTION }
);
return tokenAccounts.value.length > 0;
}
});
// Public route
app.get('/api/public', (req, res) => {
res.json({ message: 'This is public data' });
});
// Protected routes
const protectedRouter = express.Router();
protectedRouter.use(openkit.middleware());
protectedRouter.get('/profile', (req, res) => {
const user = (req as any).openkitx403User;
res.json({
address: user.address,
username: `NFT Holder ${user.address.slice(0, 6)}`,
tier: 'Premium',
joinedAt: new Date().toISOString()
});
});
protectedRouter.get('/exclusive-content', (req, res) => {
res.json({
content: 'This is exclusive content only for NFT holders!',
secretCode: 'HOLDER-' + Math.random().toString(36).substr(2, 9)
});
});
app.use('/api/protected', protectedRouter);
const PORT = 3000;
app.listen(PORT, () => {
console.log(`✅ Server running on http://localhost:${PORT}`);
});Run the Application
# Terminal 1 - Start server cd server npx tsx server.ts # Terminal 2 - Start client cd client npm run dev
• Server runs on http://localhost:3000
• Client runs on http://localhost:5173
• Open client in browser and connect your wallet
$ expected-output
[SUCCESS]
✅ Wallet connected: 5Gv8q5t...
✅ Authentication successful
✅ Profile loaded
✅ Exclusive content displayed
$ next-steps
Deploy to Production
Deploy server to Railway/Render and client to Vercel
Add Features
Implement token-gating, role-based access, webhooks
Download Complete Code
Get the complete working example with additional features, tests, and documentation.