IPFS: Decentralized Storage for Web3 Applications
In this tutorial, you'll learn IPFS for decentralized file storage including content addressing, pinning, gateways, and integration with smart contracts for NFT metadata. Why it matters: Traditional HTTP storage is centralized and fragile, but IPFS provides a decentralized, content-addressed system where files are immutable and censorship-resistant, essential for Web3 applications like NFTs and dApps. By the end, you'll host and retrieve files on IPFS.
IPFS (InterPlanetary File System) is a peer-to-peer hypermedia protocol for storing and sharing data in a distributed file system, using content-addressing instead of location-addressing.
Understanding Content Addressing
Instead of identifying files by location (URL), IPFS identifies files by their cryptographic hash (CID).
flowchart LR A[File: cat.jpg] --> B[Hash with SHA-256] B --> C[CID: QmXfTmdYTiBjxRKFfK5kMPF5xQLRPCv3GSFg4K8KjF9XqZ] C --> D[IPFS Network] D --> E[Node 1: Has file] D --> F[Node 2: Has file] D --> G[Node 3: Requests file] G -->|Ask for CID| D D -->|Nearest peer responds| G G --> H[Retrieve file by hash] H --> I[Verify hash matches] I -->|Match| J[File is authentic] I -->|Does not match| K[File is rejected]
const { create } = require("ipfs-http-client");
async function contentAddressingDemo() {
const ipfs = create({ url: "https://ipfs.infura.io:5001/api/v0" });
// Same content always produces the same hash
const content1 = "Hello, IPFS!";
const { cid: cid1 } = await ipfs.add(content1);
console.log("CID for 'Hello, IPFS!':", cid1.toString());
// Different content produces different hash
const content2 = "Hello, IPFS World!";
const { cid: cid2 } = await ipfs.add(content2);
console.log("CID for 'Hello, IPFS World!':", cid2.toString());
// Same content again - same hash
const { cid: cid3 } = await ipfs.add(content1);
console.log("Duplicate produces same CID:", cid3.toString());
console.log("CIDs match:", cid1.toString() === cid3.toString());
}
contentAddressingDemo();
// Expected output:
// CID for 'Hello, IPFS!': QmU1dQjENxq3A2hPqJzVm5oX3zqL5qJ2vY7pKa8vZ6WXhP
// CID for 'Hello, IPFS World!': QmRqPfKPQJXKCV7s7e5vGjH9Q8qRy2KYZmG5J7vW8xYzL
// Duplicate produces same CID: QmU1dQjENxq3A2hPqJzVm5oX3zqL5qJ2vY7pKa8vZ6WXhP
// CIDs match: true
Installing and Using IPFS
Set up an IPFS node or use a public gateway service.
# Install IPFS CLI
wget https://dist.ipfs.tech/kubo/v0.26.0/kubo_v0.26.0_linux-amd64.tar.gz
tar -xvzf kubo_v0.26.0_linux-amd64.tar.gz
cd kubo
sudo bash install.sh
# Initialize IPFS node
ipfs init
# Start IPFS daemon
ipfs daemon &
# Add a file
echo "Hello from IPFS!" > hello.txt
ipfs add hello.txt
# Expected output:
# added QmU1dQjENxq3A2hPqJzVm5oX3zqL5qJ2vY7pKa8vZ6WXhP hello.txt
# Retrieve by CID
ipfs cat QmU1dQjENxq3A2hPqJzVm5oX3zqL5qJ2vY7pKa8vZ6WXhP
# Expected output: Hello from IPFS!
# Pin a file (keep it accessible)
ipfs pin add QmU1dQjENxq3A2hPqJzVm5oX3zqL5qJ2vY7pKa8vZ6WXhP
Using IPFS with JavaScript
Use the ipfs-http-client library to interact with IPFS from JavaScript.
const { create } = require("ipfs-http-client");
const fs = require("fs");
// Connect to IPFS via Infura gateway
const ipfs = create({ url: "https://ipfs.infura.io:5001/api/v0" });
async function ipfsOperations() {
// Add text
const textResult = await ipfs.add("Hello, decentralized world!");
console.log("Text CID:", textResult.cid.toString());
// Add a JSON object
const metadata = {
name: "Example NFT",
description: "A sample NFT metadata",
image: "ipfs://QmXYZ...",
attributes: [
{ trait_type: "Rarity", value: "Common" }
]
};
const jsonResult = await ipfs.add(JSON.stringify(metadata));
console.log("JSON CID:", jsonResult.cid.toString());
// Add a file from disk
const fileStream = fs.createReadStream("./image.png");
const fileResult = await ipfs.add({ path: "image.png", content: fileStream });
console.log("File CID:", fileResult.cid.toString());
// Retrieve content by CID
const chunks = [];
for await (const chunk of ipfs.cat(jsonResult.cid)) {
chunks.push(chunk);
}
const retrieved = JSON.parse(Buffer.concat(chunks).toString());
console.log("Retrieved metadata:", retrieved.name);
}
ipfsOperations().catch(console.error);
// Expected output:
// Text CID: Qm...
// JSON CID: Qm...
// File CID: Qm...
// Retrieved metadata: Example NFT
Pinning and Persistence
Files on IPFS are only available as long as at least one node pins them. Pinning services ensure persistence.
| Pinning Service | Free Tier | Paid Tier | Features |
|---|---|---|---|
| Pinata | 1 GB | $20/month | Easy UI, API keys |
| Web3.Storage | 5 GB | Pay-as-you-go | Filecoin-backed |
| Filebase | 5 GB | $5/month | S3-compatible API |
| Infura | 5 GB | Pay-as-you-go | Integrated with Ethereum |
const pinataSDK = require("@pinata/sdk");
const fs = require("fs");
const pinata = new pinataSDK("YOUR_API_KEY", "YOUR_API_SECRET");
async function pinToPinata() {
// Pin a JSON object
const metadata = {
name: "MyPinnedNFT",
description: "Permanently pinned NFT metadata",
image: "ipfs://QmXYZ...",
};
const result = await pinata.pinJSONToIPFS(metadata, {
pinataMetadata: {
name: "nft-metadata.json",
keyvalues: {
collection: "MyCollection",
tokenId: "1"
}
},
pinataOptions: {
cidVersion: 1
}
});
console.log("Pinned to IPFS via Pinata");
console.log("IPFS hash:", result.IpfsHash);
console.log("URL:", `https://gateway.pinata.cloud/ipfs/${result.IpfsHash}`);
// Pin a file
const readableStream = fs.createReadStream("./nft-image.png");
const fileResult = await pinata.pinFileToIPFS(readableStream, {
pinataMetadata: { name: "nft-image.png" }
});
console.log("Image pinned:", fileResult.IpfsHash);
}
pinToPinata();
// Expected output:
// Pinned to IPFS via Pinata
// IPFS hash: QmXfTmdYTiBjxRKFfK5kMPF5xQLRPCv3GSFg4K8KjF9XqZ
// URL: https://gateway.pinata.cloud/ipfs/QmXfTmdYTiBjxRKFfK5kMPF5xQLRPCv3GSFg4K8KjF9XqZ
IPFS Gateways
Gateways bridge HTTP clients to IPFS, allowing browsers to load IPFS content via regular URLs.
function getGatewayUrl(cid, gateway = "https://ipfs.io") {
// Multiple gateway options
const gateways = {
ipfs: `https://ipfs.io/ipfs/${cid}`,
pinata: `https://gateway.pinata.cloud/ipfs/${cid}`,
dweb: `https://${cid}.ipfs.dweb.link`,
cloudflare: `https://cloudflare-ipfs.com/ipfs/${cid}`,
infura: `https://infura-ipfs.io/ipfs/${cid}`,
};
return gateways;
}
async function testGateways(cid) {
const urls = getGatewayUrl(cid);
for (const [name, url] of Object.entries(urls)) {
try {
const response = await fetch(url);
if (response.ok) {
console.log(`${name}: OK (${url})`);
} else {
console.log(`${name}: FAILED (${response.status})`);
}
} catch (error) {
console.log(`${name}: ERROR (${error.message})`);
}
}
}
testGateways("QmXfTmdYTiBjxRKFfK5kMPF5xQLRPCv3GSFg4K8KjF9XqZ");
// Expected output:
// ipfs: OK (https://ipfs.io/ipfs/QmXfTmdYTiBjxRKFfK5kMPF5xQLRPCv3GSFg4K8KjF9XqZ)
// pinata: OK (https://gateway.pinata.cloud/ipfs/QmXfTmdYTiBjxRKFfK5kMPF5xQLRPCv3GSFg4K8KjF9XqZ)
// cloudflare: OK (https://cloudflare-ipfs.com/ipfs/QmXfTmdYTiBjxRKFfK5kMPF5xQLRPCv3GSFg4K8KjF9XqZ)
IPFS with Smart Contracts
Store IPFS hashes in smart contracts for immutable references to off-chain data.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract IPFSRegistry {
struct Document {
string cid;
address uploader;
uint256 timestamp;
}
mapping(bytes32 => Document) public documents;
event DocumentStored(bytes32 indexed hash, string cid, address indexed uploader);
function storeDocument(string calldata _cid) external {
bytes32 docHash = keccak256(abi.encodePacked(_cid, msg.sender, block.timestamp));
documents[docHash] = Document({
cid: _cid,
uploader: msg.sender,
timestamp: block.timestamp
});
emit DocumentStored(docHash, _cid, msg.sender);
}
function getDocument(bytes32 _hash) external view returns (Document memory) {
require(bytes(documents[_hash].cid).length > 0, "Document not found");
return documents[_hash];
}
function verifyDocument(bytes32 _hash, string calldata _cid) external view returns (bool) {
return keccak256(abi.encodePacked(documents[_hash].cid)) == keccak256(abi.encodePacked(_cid));
}
}
Expected behavior: Users upload documents to IPFS, then register the CID on-chain. The contract provides tamper-proof proof of existence and timestamping.
IPFS for NFT Metadata
The most common Web3 use case for IPFS is storing NFT metadata and images.
const { create } = require("ipfs-http-client");
const pinataSDK = require("@pinata/sdk");
// Generate NFT collection metadata
async function generateNFTMetadata(collectionName, totalSupply, baseImageCid) {
const metadataFiles = [];
for (let i = 1; i <= totalSupply; i++) {
const metadata = {
name: `${collectionName} #${i}`,
description: `A unique NFT from the ${collectionName} collection`,
image: `ipfs://${baseImageCid}/${i}.png`,
attributes: [
{ trait_type: "Token ID", value: i },
{ trait_type: "Collection", value: collectionName },
]
};
const { cid } = await ipfs.add(JSON.stringify(metadata));
metadataFiles.push({ tokenId: i, cid: cid.toString() });
console.log(`Generated metadata for token ${i}: ${cid}`);
}
return metadataFiles;
}
// Expected output per token:
// Generated metadata for token 1: Qm...
// Generated metadata for token 2: Qm...
// Set base URI in contract
async function updateContractBaseURI(contractAddress, baseCid) {
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
const signer = new ethers.Wallet("0x...", provider);
const contract = new ethers.Contract(contractAddress, [
"function setBaseURI(string memory) external]
], signer);
const tx = await contract.setBaseURI(`ipfs://${baseCid}/`);
await tx.wait();
console.log("Base URI updated to:", `ipfs://${baseCid}/`);
}
Common Errors and Misunderstandings
1. Assuming IPFS Is Permanent Without Pinning
Files are only available while at least one node hosts them. Without pinning, garbage collection removes files. Always use pinning services for production.
2. Confusing CID Versions
CIDv0 (starts with Qm) and CIDv1 (starts with b) use different formats. Gateways support both, but some tools expect one version.
3. Not Using IPFS for Dynamic Content
IPFS is designed for static, immutable content. For dynamic data, use IPNS (InterPlanetary Name System) or a mutable pointer contract.
4. Gateway Rate Limiting
Public gateways rate-limit requests. For production dApps, use dedicated gateways or run your own IPFS node.
5. Metadata Change Issues
Once an NFT metadata CID is set in a contract, it cannot be changed. Triple-check metadata before minting or use a proxy URI pattern.
Practice Questions
What is content addressing in IPFS? Files are identified by their cryptographic hash (CID) rather than their location. The hash is derived from the file content, so the same file always produces the same CID.
Why is pinning necessary in IPFS? IPFS nodes garbage-collect unpinned files. Pinning marks files to be kept permanently. Without pinning, files can disappear when hosting nodes go offline.
What is the difference between IPFS and Filecoin? IPFS provides content-addressed storage and retrieval. Filecoin adds an incentive layer, paying storage providers to keep files. Filecoin is built on IPFS.
How do IPFS gateways work? Gateways translate HTTP requests to IPFS protocol. A URL like
https://ipfs.io/ipfs/CIDretrieves the content from the IPFS network through the gateway.What is an IPNS name? IPNS provides mutable pointers to IPFS content. A single IPNS name can point to different CIDs over time, allowing updating content while keeping a stable reference.
Challenge
Build a decentralized document signing dApp using Solidity and IPFS. Users upload documents to IPFS, register the CID on-chain, other users sign the document hash with their wallet, and signatures are verified on-chain. Include a React frontend that uploads to IPFS via Pinata.
Real-World Task
Create a Web3 media application using React and IPFS that allows users to upload images to IPFS, store the CIDs in a smart contract, browse a gallery of uploaded images via IPFS gateways, and claim ownership of their uploads. Use Pinata for pinning and ethers.js for contract interaction.
Frequently Asked Questions
Next Steps
After mastering IPFS, explore Oracles and Chainlink for connecting smart contracts to external data, then build full-stack dApps integrating decentralized storage and real-world data.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro