Skip to content

IPFS: Decentralized Storage for Web3 Applications

DodaTech Updated 2026-06-22 8 min read

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

  1. 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.

  2. 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.

  3. 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.

  4. How do IPFS gateways work? Gateways translate HTTP requests to IPFS protocol. A URL like https://ipfs.io/ipfs/CID retrieves the content from the IPFS network through the gateway.

  5. 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

Is IPFS free to use?

Adding and retrieving files from IPFS is free. Pinning services charge for guaranteed availability. Running your own IPFS node is free but requires server resources. Public gateways are free but rate-limited and unreliable for production.

How fast is IPFS compared to HTTP?

IPFS is slower than HTTP for the first retrieval because it must find peers and download from multiple sources. Subsequent retrievals from the local cache are instant. CDN-like gateways provide HTTP-speed access for popular content.

Can I update files on IPFS?

IPFS content is immutable. To update, upload a new version (new CID) and use IPNS or a smart contract to point to the new CID. Some applications use a "latest" pointer in a contract to reference the current version.

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